
La syntaxe de transport ASN.1 définit un moyen unique de convertir les valeurs des variables de types valides en une séquence d'octets pour la transmission sur un réseau. Dans ASN.1, il est appelé règles de codage de base (BER). Les règles sont récursives, donc le codage des objets composites est un chaînage de séquences codées d'objets composants. Le protocole ASN.1 décrit la structure des données dans un
langage simple et compréhensible .
Chaque valeur transmise - de type basique et dérivé - se compose de trois champs:
- identifiant;
- longueur du champ de données (en octets);
- champ de données.
Si vous spécifiez toujours la longueur du champ de données (je considère que c'est une bonne pratique), alors l'indicateur de fin du champ de données n'est pas utilisé.
Il existe de nombreux compilateurs différents pour ASN.1, à la fois payants et gratuits, pour différents langages de programmation, mais nous aimerions avoir quelque chose de très simple à portée de main.
La grande majorité des développeurs de logiciels trouvent le
complexe standard
ASN.1 . Je le pensais aussi jusqu'à récemment. Travaillant dans le domaine de PKI / PKI / cryptographie presque tous les jours, vous traitez avec des structures ASN1 sous la forme de certificats X509, de demandes de certificats, de listes de certificats révoqués. Et la liste continue. Et donc, tout en travaillant sur un utilitaire pour créer une demande de certificat au format PKCS # 10 avec la génération d'une paire de clés sur un token / carte à puce PKCS # 11, j'ai naturellement dû former, en particulier, la structure asn1 de la clé publique pour l'écrire dans la demande de certificat :
C-Sequence C-Sequence (<>) Object Identifier (<>) <oid public key> C-Sequence (<>) Object Identifier (<>) <oid > Object Identifier (<>) <oid > Bit String (<>) < >
Étant donné que nous avons utilisé le jeton PKCS # 11 avec prise en charge de la cryptographie russe comme outil de protection des informations cryptographiques, le matériau source de cette structure a été obtenu à partir du jeton conformément au modèle suivant:
CK_BYTE gostr3410par[12]; CK_BYTE gostr3411par[12]; CK_ULONG gostr3410par_len; CK_ULONG gostr3411par_len; CK_BYTE pubkey[128]; CK_ULONG pubkeu_len; CK_KEY_TYPE key_type; CK_ATTRIBUTE templ_pk[] = { . . . {CKA_GOSTR3410PARAMS, gostr3410par, sizeof(gostr3410par)}, {CKA_GOSTR3411PARAMS, gostr3411par, sizeof(gostr3410par)}, {CKA_VALUE, pubkey, sizeof(pubkey)}, {CKA_KEY_TYPE, &key_type, sizeof(key_type)} }
Directement à partir de cette structure, les valeurs de l'attribut CKA_VALUE contenant la valeur de clé publique et les valeurs des attributs CKA_GOSTR3410PARAMS et CKA_GOSTR3411PARAMS qui contiennent les oids du paramètre de signature et du paramètre de hachage seront utilisées pour remplir asn1-publickeyinfo.
L'attribut CKA_KEY_TYPE, qui peut prendre les valeurs CKK_GOSTR3410 et CKK_GOSTR3410_512 (dans les conditions où l'algorithme de signature de GOST R 34.10-2001 continue de fonctionner) définit de manière ambiguë l'algorithme de paire de clés. Si la valeur de l'attribut CKA_KEY_TYPE est égale à CKK_GOSTR3410_512, alors, bien sûr, il pointe uniquement vers l'algorithme GOST R 34.10-2012 avec une longueur de clé de 512 bits (oid = 1.2.643.7.1.1.1.2.2). Mais s'il est simplement égal à CKK_GOSTR3410, alors il y a une ambiguïté sur le type de clé auquel cette clé appartient: GOST R 34.10-2001 ou encore GOST R 34.10-2012 avec une longueur de clé de 256 bits. L'attribut CKA_GOSTR3411PARAMS permet de résoudre cette ambiguïté.
Immédiatement, nous notons que les paramètres CKA_GOSTR3410PARAMS et CKA_GOSTR3411PARAMS sur le jeton conformément aux recommandations du TK-26 sont stockés en tant qu'identifiant d'objet codé par oid, par exemple:
\ x06 \ x06 \ x2a \ x85 \ x03 \ x02 \ x02 \ x13, où l'octet zéro détermine le type de séquence (0x06 est l'identifiant d'objet, voir le tableau ci-dessous), le deuxième octet indique la longueur (dans le cas général, la longueur peut prendre plusieurs octets, mais plus sur celui ci-dessous) le champ de données dans lequel oid est stocké sous forme binaire.
Si ce paramètre contient l'oid de l'algorithme de hachage GOST R 34.10-2012 d'une longueur de 256 bits (oid = 1.2.643.7.1.1.2.2, sous forme binaire "\ x2a \ x 85 \ x 03 \ x 07 \ x 01 \ x 01 \ x 02 \ x02 "), le type de clé doit être défini comme GOST R 34.10-2012 avec une longueur de clé de 256 bits. Sinon, c'est la clé de GOST R 34.10-2001. L'algorithme pour déterminer le type de clé peut ressembler à ceci:
. . . for (curr_attr_idx = 0; curr_attr_idx < (sizeof(templ_pk)/sizeof(templ_pk[0])); curr_attr_idx++){ curr_attr = &templ_pk[curr_attr_idx]; if (!curr_attr->pValue) { continue; } swith (curr_attr->type) { . . . case CKA_VALUE: pubkey_len = curr_attr->ulValueLen; break; case CKA_GOSTR3410PARAMS: gostr3410par_len = curr_attr->ulValueLen; break; case CKA_GOSTR3410PARAMS: gostr3411par_len = curr_attr->ulValueLen; break; case CKA_KEY_TYPE: ulattr = curr_attr->pValue; if (*ulattr == CKK_GOSTR3410) { if (!memmem(gostr3411par), gostr3411par_len,"\x06\x08\x2a\x85\x03\x07", 6)) { strcpy(oid_key_type, "1.2.643.2.2.19"); memcpy(oid_key_type_asn1("\x06\x06\x2a\x85\x03\x02\x02\x13", 8); } else { strcpy(oid_key_type, ("1 2 643 7 1 1 1 1"); memcpy(oid_key_type_asn1 ("\x06\x08\x2a\x85\x03\x07\x01\x01\x01\x01", 10); } } else if (*ulattr == CKK_GOSTR3410_512) { strcpy(oid_key_type, ("1 2 643 7 1 1 1 2"); memcpy(oid_key_type_asn1 ("\x06\x08\x2a\x85\x03\x07\x01\x01\x01\x02", 10); } else { fprintf(stderr, "tclpkcs11_perform_pki_keypair CKK_GOSTR ERROR\n"); return (-1) } break; . . . } } . . .
Nous avons maintenant toutes les données source pour créer la structure de clé publique asn1.
Rappelons que chaque élément de la structure asn1 se compose de trois champs:
- identifiant;
- longueur du champ de données (en octets);
- champ de données.
Voici
un tableau de codage pour certains types d'identifiants utilisés dans PKI / PKI:
Nom du type | Brève description | Représentation de type dans le codage DER |
---|
SÉQUENCE | Utilisé pour décrire une structure de données composée de différents types. | 30 |
ENTIER | Entier | 02 |
IDENTIFICATEUR D'OBJET | Une séquence d'entiers. | 06 |
UTCTime | Type temporaire, contient 2 chiffres pour déterminer l'année | 17 |
Temps généralisé | Type de temps étendu, contient 4 chiffres pour indiquer l'année. | 18 |
SET | Décrit la structure des données de différents types. | 31 |
UTF8String | Décrit les données de chaîne. | 0C |
Null | En fait NULL | 05 |
BIT STRING | Type pour stocker une séquence de bits. | 03 |
OCTET STRING | Type de stockage d'une séquence d'octets | 04 |
Lorsque vous travaillez avec des structures asn1, le plus grand choc pour les non-initiés est causé par la méthode de codage de la longueur du champ de données, surtout lorsqu'il est formé, et si l'on prend en compte l'architecture informatique (littleendien, bigendien). C'est toute une
science . Et donc, dans le processus de discussion de l'algorithme pour former ce champ, l'idée est venue d'utiliser la fonction sprintf, qui elle-même prendra en compte l'architecture, et comment le nombre d'octets pour stocker la longueur est déterminé peut être vu par le code de fonction, qui prépare un tampon avec l'identifiant de type de données et la longueur des données:
unsigned char *wrap_id_with_length(unsigned char type,
La fonction renvoie un pointeur sur un tampon avec une structure asn1, alloué en tenant compte de la longueur des données. Il reste à copier ces données dans le tampon reçu avec un décalage de la longueur de l'en-tête. La longueur de l'en-tête est retournée via le paramètre lenasn.
Afin de vérifier le fonctionnement de cette fonction, nous écrirons un utilitaire simple:
#include <stdio.h> #include <stdlib.h> #define digitp(p) (*(p) >= '0' && *(p) <= '9') #define hexdigitp(a) (digitp (a) \ || (*(a) >= 'A' && *(a) <= 'F') \ || (*(a) >= 'a' && *(a) <= 'f')) #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) int main (int argc, char *argv[]) { unsigned char *hdrasn; unsigned char type; unsigned long length; unsigned long lenasn; if (argc != 3) { fprintf (stderr, "Usage: wrap_id_with_length <id> <length>\n"); exit(-1); } type = atoi(argv[1]); length = atol(argv[2]); fprintf (stderr, "<id=%02x> <length=%lu>\n", type, length); if (length == 0) { fprintf (stderr, "Bad length=%s\nUsage: wrap_id_with_length <id> <length>\n", argv[2]); exit(-1); } hdrasn = wrap_id_with_length(type, length, &lenasn); fprintf (stderr, "Length asn1-buffer=%lu, LEN_HEADER=%lu, LEN_DATA=%lu\n", lenasn, lenasn - length, length); }
Enregistrez-le avec la fonction wrap_id_with_length dans le fichier wrap_id_with_length.c.
Nous diffusons:
$cc –o wrap_id_with_length wrap_id_with_length.c $
Exécutez le programme résultant avec diverses données source. Le type de données est spécifié par un nombre décimal:
bash-4.3$ ./wrap_id_with_length 06 8 <id=06> <length=8> ASN1 - :0608 Length asn1-buffer=10, LEN_HEADER=2, LEN_DATA=8 bash-4.3$ ./wrap_id_with_length 06 127 <id=06> <length=127> ASN1 - :067f Length asn1-buffer=129, LEN_HEADER=2, LEN_DATA=127 bash-4.3$ ./wrap_id_with_length 48 128 <id=30> <length=128> ASN1 - :308180 Length asn1-buffer=131, LEN_HEADER=3, LEN_DATA=128 bash-4.3$ ./wrap_id_with_length 48 4097 <id=30> <length=4097> ASN1 - :30821001 Length asn1-buffer=4101, LEN_HEADER=4, LEN_DATA=4097 bash-4.3$
Vous pouvez vérifier l'exactitude de la formation de l'en-tête à l'aide de n'importe quelle calculatrice:

Nous sommes tous prêts à former n'importe quelle structure ASN1. Mais d'abord, apportez de petites modifications à la fonction wrap_id_with_length et appelez-la
wrap_for_asn1: unsigned char *wrap_for_asn1(unsigned char type, unsigned char *prefix, unsigned long prefix_len, unsigned char *wrap, unsigned long wrap_len, unsigned long *lenasn){ unsigned long length; int buflen = 0; unsigned char *buf; char *format; const char buf_for_len[100]; const char *s; char f0[] = "%02x%02x"; char f1[] = "%02x81%02x"; char f2[] = "%02x82%04x"; char f3[] = "%02x83%06x"; char f4[] = "%02x84%08x"; length = prefix_len + wrap_len; buflen += ( length <= 0x80 ? 1: length <= 0xff ? 2: length <= 0xffff ? 3: length <= 0xffffff ? 4: 5); buf = malloc(length + buflen); switch (buflen - 1) { case 0: format = f0; break; case 1: format = f1; break; case 2: format = f2; break; case 3: format = f3; break; case 4: format = f4; break; }
Comme vous pouvez le voir, les changements sont minimes. En tant que paramètres d'entrée, les données elles-mêmes sont ajoutées, qui à l'intérieur de la fonction sont regroupées dans une structure asn1. De plus, deux tampons peuvent être introduits simultanément dans l'entrée. Cela nous semble commode.
Avant de présenter un cas de test, nous donnons les codes de trois fonctions supplémentaires. La première fonction
oid2buffer convertit les oid s de la forme décimale en pointillés au codage DER. Nous avons besoin de cette fonction pour convertir, en particulier, les oids de la paire de clés (voir ci-dessus).
Le texte de la fonction est ici:char statique * oid2buffer (char * oid_str, unsigned long * len) {
char * curstr;
char * curstr1;
char * nextstr;
unsigned int firstval;
unsigned int secondval;
unsigned int val;
charf non signé [5];
int count;
caractère non signé oid_hex [100];
char * res;
int i;
if (oid_str == NULL) {
* len = 0;
return NULL;
}
* len = 0;
curstr = strdup ((const char *) oid_str);
curstr1 = curstr;
nextstr = strchr (curstr, '.');
if (nextstr == NULL) {
* len = 0;
return NULL;
}
* nextstr = '\ 0';
firstval = atoi (curstr);
curstr = nextstr + 1;
nextstr = strchr (curstr, '.');
if (nextstr) {
* nextstr = '\ 0';
}
secondval = atoi (curstr);
if (firstval> 2) {
* len = 0;
return NULL;
}
if (secondval> 39) {
* len = 0;
return NULL;
}
oid_hex [0] = (caractère non signé) ((firstval * 40) + secondval);
i = 1;
while (nextstr) {
curstr = nextstr + 1;
nextstr = strchr (curstr, '.');
if (nextstr) {
* nextstr = '\ 0';
}
memset (buf, 0, sizeof (buf));
val = atoi (curstr);
count = 0;
if (curstr [0]! = '0')
while (val) {
buf [count] = (val & 0x7f);
val = val >> 7;
count ++;
}
sinon {
buf [count] = (val & 0x7f);
val = val >> 7;
count ++;
}
while (count--) {
if (count) {
oid_hex [i] = buf [count] | 0x80;
} else {
oid_hex [i] = buf [count];
}
i ++;
}
}
res = (char *) malloc (i);
if (res) {
memcpy (res, oid_hex, i);
* len = i;
}
libre (curstr1);
return res;
}
Les deux autres fonctions permettent de convertir le tampon binaire en un nombre hexadécimal (buffer2hex) et vice versa (hex2buffer).
Ces fonctions sont ici:caractère statique *
buffer2hex (const unsigned char * src, size_t len)
{
int i;
char * dest;
char * res;
dest = (char *) malloc (len * 2 + 1);
res = dest;
si (dest)
{
pour (i = 0; i <len; i ++, dest + = 2)
sprintf (dest, "% 02X", src [i]);
}
return res;
}
vide statique *
hex2buffer (const char * string, size_t * r_length)
{
const char * s;
tampon char * non signé;
taille_t longueur;
tampon = malloc (strlen (chaîne) / 2 + 1);
longueur = 0;
pour (s = chaîne; * s; s + = 2)
{
if (! hexdigitp (s) ||! hexdigitp (s + 1)) {
fprintf (stderr, "chiffres hexadécimaux invalides dans \"% s \ "\ n", chaîne);
}
((tampon non signé *)) [longueur ++] = xtoi_2 (s);
}
* r_length = longueur;
tampon de retour;
}
Ces fonctions sont très pratiques pour le débogage, et très probablement, beaucoup les ont.
Et maintenant, nous revenons à la solution de la tâche, obtenant une structure asn1 de la clé publique. Nous allons écrire un utilitaire qui va générer et enregistrer la structure asn1 de la clé publique dans le fichier ASN1_PIBINFO.der.
Cet utilitaire se trouve ici: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <stdint.h> #include <string.h> #define digitp(p) (*(p) >= '0' && *(p) <= '9') #define hexdigitp(a) (digitp (a) \ || (*(a) >= 'A' && *(a) <= 'F') \ || (*(a) >= 'a' && *(a) <= 'f')) #define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \ *(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1)) /* oid2buffer*/ /* buffer2hex hex2buffer*/ /* wrap_for_asn1*/ int main() { int fd; unsigned char *asn, *asn1, *asn2, *asn3, *pubkeyalgo; unsigned char* pubkey_bin; // char gost3410par[] = "\x06\x7\x2a\x85\x03\x02\x02\x23\x01"; unsigned long gost3410par_len = sizeof(gost3410par) - 1; char gost3411par[] = "\x06\x8\x2a\x85\x03\x07\x01\x01\x02\x02"; unsigned long gost3411par_len = sizeof(gost3411par) - 1; unsigned char pubkey_hex[] = "9af03570ed0c54cd4953f11ab19e551022cd48603326c1b9b630b1cff74e5a160ba1718166cc22bf70f82bdc957d924c501b9332491cb3a36ce45770f05487b5"; char pubkey_oid_2001[] = "1.2.643.2.2.19"; char pubkey_oid_2012_256[] = "1.2.643.7.1.1.1.1"; char pubkey_oid_2012_512[] = "1.2.643.7.1.1.1.2"; unsigned long pubkey_len, pubkey_len_full, len10, len11, len12, lenalgo; unsigned char *pkalgo; unsigned long pkalgo_len; uint16_t x = 1; /* 0x0001 */ printf("%s\n", *((uint8_t *) &x) == 0 ? "big-endian" : "little-endian"); ////pubkeyinfo // if (!memmem(gost3411par, 8, "\x2a\x85\x03\x07", 4)) { // 34.11-94, 34.10-2001 - 1.2.643.2.2.19 pubkeyalgo = (unsigned char *)oid2buffer(pubkey_oid_2001, &lenalgo); } else if (!memcmp(gost3411par, "\x2a\x85\x03\x07\x01\x01\x02\x02", 8)){ // 34.11-2012-256, 34.10-2012-256 - 1.2.643.7.1.1.1.1 pubkeyalgo = (unsigned char *)oid2buffer(pubkey_oid_2012_256, &lenalgo); } else { // 34.11-2012-512, 34.10-2012-512 - 1.2.643.7.1.1.1.2 pubkeyalgo = (unsigned char *)oid2buffer(pubkey_oid_2012_512, &lenalgo); } pubkey_bin =(unsigned char*)hex2buffer((const char *)pubkey_hex, &pubkey_len); // asn1 = wrap_for_asn1_bin('\x04', (unsigned char *)"", 0, pubkey_bin, pubkey_len, &pubkey_len); asn = wrap_for_asn1_bin('\x03', (unsigned char *)"\x00", 1, asn1, pubkey_len, &pubkey_len_full); fprintf(stderr, "PUBLIC_VALUE=%s\n", buffer2hex(asn, pubkey_len_full)); free(asn1); // asn3 = wrap_for_asn1_bin('\x30', (unsigned char*)gost3410par, gost3410par_len, (unsigned char *)gost3411par, gost3411par_len, &len12); fprintf(stderr, "\nPARAMS len12=%lu, FULL=%s\n", len12, buffer2hex(asn3, len12)); // pkalgo = wrap_for_asn1_bin('\x06', (unsigned char *)"", 0, pubkeyalgo, lenalgo, &pkalgo_len); // asn2 = wrap_for_asn1_bin('\x30', pkalgo, pkalgo_len, asn3, len12, &len11); fprintf(stderr, "PubKEY=%s\n", buffer2hex(asn3, len11)); asn1 = wrap_for_asn1_bin('\x30', asn2, len11, asn, pubkey_len_full, &len10); free(asn2); free(asn3); fprintf(stderr, "\n%s\n", buffer2hex(asn1, len10)); fd = open ("ASN1_PUBINFO.der", O_TRUNC|O_RDWR|O_CREAT,S_IRWXO); write(fd, asn1, len10); close(fd); free(asn1); chmod("ASN1_PUBINFO.der", 0666); }
Pour vérifier le résultat, nous utiliserons les utilitaires derdump et pp du package NSS.
Le premier utilitaire nous montrera la structure asn1 de la clé publique:
$ derdump -i ASN1_PUBINFO.der C-Sequence (102) C-Sequence (31) Object Identifier (8) 1 2 643 7 1 1 1 2 (GOST R 34.10-2012 Key 512) C-Sequence (19) Object Identifier (7) 1 2 643 2 2 35 1 Object Identifier (8) 1 2 643 7 1 1 2 2 (GOST R 34.11-2012 256) Bit String (67) 00 04 40 9a f0 35 70 ed 0c 54 cd 49 53 f1 1a b1 9e 55 10 22 cd 48 60 33 26 c1 b9 b6 30 b1 cf f7 4e 5a 16 0b a1 71 81 66 cc 22 bf 70 f8 2b dc 95 7d 92 4c 50 1b 93 32 49 1c b3 a3 6c e4 57 70 f0 54 87 b5 $
Le second affichera le contenu de la clé:
$ pp -t pk -i ASN1_PUBINFO.der Public Key: Subject Public Key Info: Public Key Algorithm: GOST R 34.10-2012 512 Public Key: PublicValue: 9a:f0:35:70:ed:0c:54:cd:49:53:f1:1a:b1:9e:55:10: 22:cd:48:60:33:26:c1:b9:b6:30:b1:cf:f7:4e:5a:16: 0b:a1:71:81:66:cc:22:bf:70:f8:2b:dc:95:7d:92:4c: 50:1b:93:32:49:1c:b3:a3:6c:e4:57:70:f0:54:87:b5 GOSTR3410Params: OID.1.2.643.2.2.35.1 GOSTR3411Params: GOST R 34.11-2012 256 $
Ceux qui le souhaitent peuvent vérifier, par exemple, avec l'utilitaire openssl, de préférence avec un moteur
GOST connecté :
$ /usr/local/lirssl_csp_64/bin/lirssl_static asn1parse -inform DER -in ASN1_PUBINFO.der 0:d=0 hl=2 l= 102 cons: SEQUENCE 2:d=1 hl=2 l= 31 cons: SEQUENCE 4:d=2 hl=2 l= 8 prim: OBJECT :GOST R 34.10-2012 with 512 bit modulus 14:d=2 hl=2 l= 19 cons: SEQUENCE 16:d=3 hl=2 l= 7 prim: OBJECT :id-GostR3410-2001-CryptoPro-A-ParamSet 25:d=3 hl=2 l= 8 prim: OBJECT :GOST R 34.11-2012 with 256 bit hash 35:d=1 hl=2 l= 67 prim: BIT STRING $
Comme vous pouvez le voir, la structure ASN1 résultante est testée avec succès partout.
L'algorithme et l'utilitaire proposés pour former des structures asn1 ne nécessitent pas l'utilisation de compilateurs ASN1 et de bibliothèques d'extension (le même openssl) et se sont avérés très pratiques à utiliser. Nous les rappellerons dans le prochain article, lorsque les souhaits de
Pas seront satisfaits et qu'un utilitaire graphique sera présenté qui non seulement «analyse les certificats» et vérifie leur validité, mais génère également une paire de clés sur les jetons PKCS # 11, génère et signe une demande de certificat qualifié. Avec cette demande, vous pouvez vous rendre en toute sécurité à l'AC pour obtenir un certificat. Avant les questions, je note immédiatement que dans ce dernier cas, le jeton doit être certifié en tant que système de protection des informations cryptographiques dans le système de certification du FSB de Russie.