
A sintaxe de transporte ASN.1 define uma maneira exclusiva de converter valores de variáveis de tipos válidos em uma sequência de bytes para transmissão em uma rede. No ASN.1, é chamado de Regras Básicas de Codificação (BER). As regras são recursivas, portanto, a codificação de objetos compostos é um encadeamento de sequências codificadas de objetos componentes. O protocolo ASN.1 descreve a estrutura de dados em uma
linguagem simples e compreensível .
Cada valor transmitido - do tipo básico e derivado - consiste em três campos:
- identificador;
- comprimento do campo de dados (em bytes);
- campo de dados.
Se você sempre especificar o comprimento do campo de dados (acho que essa é a regra do bom tom), a bandeira do final do campo de dados não será usada.
Existem muitos compiladores diferentes para o ASN.1, pagos e gratuitos, para diferentes linguagens de programação, mas gostaríamos de ter algo muito simples em mãos.
A grande maioria dos desenvolvedores de software encontra o
complexo padrão
ASN.1 . Eu também pensava assim até recentemente. Trabalhando no campo de PKI / PKI / criptografia quase todos os dias, você lida com estruturas ASN1 na forma de certificados X509, solicitações de certificados, listas de certificados revogados. E a lista continua. Portanto, enquanto trabalhava em um utilitário para criar uma solicitação de certificado no formato PKCS # 10 com a geração de um par de chaves em um token / smartcard PKCS # 11, naturalmente tive que formar, em particular, a estrutura asn1 da chave pública para gravá-la na solicitação de certificado :
C-Sequence C-Sequence (<>) Object Identifier (<>) <oid public key> C-Sequence (<>) Object Identifier (<>) <oid > Object Identifier (<>) <oid > Bit String (<>) < >
Como usamos o token PKCS # 11 com suporte para criptografia russa como ferramenta de proteção de informações criptográficas, o material de origem dessa estrutura foi obtido do token de acordo com o seguinte modelo:
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)} }
Diretamente dessa estrutura, os valores do atributo CKA_VALUE que contêm o valor da chave pública e os atributos CKA_GOSTR3410PARAMS e CKA_GOSTR3411PARAMS que contêm os oids do parâmetro de assinatura e o parâmetro hash serão usados para preencher asn1-publickeyinfo.
O atributo CKA_KEY_TYPE, que pode assumir os valores CKK_GOSTR3410 e CKK_GOSTR3410_512 (nas condições em que o algoritmo de assinatura do GOST R 34.10-2001 continua a operar) define ambiguamente o algoritmo do par de chaves. Se o valor do atributo CKA_KEY_TYPE for igual a CKK_GOSTR3410_512, é claro que ele apontará exclusivamente para o algoritmo GOST R 34.10-2012 com um comprimento de chave de 512 bits (oid = 1.2.643.7.1.1.1.2). Mas se é simplesmente igual a CKK_GOSTR3410, existe uma ambiguidade sobre a que tipo de chave essa chave pertence: GOST R 34.10-2001 ou ainda é GOST R 34.10-2012 com um comprimento de chave de 256 bits. O atributo CKA_GOSTR3411PARAMS ajuda a resolver essa ambiguidade.
Imediatamente, observamos que os parâmetros CKA_GOSTR3410PARAMS e CKA_GOSTR3411PARAMS no token de acordo com as recomendações do TK-26 são armazenados na forma de um identificador de objeto codificado por oid, por exemplo:
\ x06 \ x06 \ x2a \ x85 \ x03 \ x02 \ x02 \ x13, em que o byte zero determina o tipo de sequência (0x06 é o identificador do objeto, veja a tabela abaixo), o segundo byte indica o comprimento (no caso geral, o comprimento pode levar vários bytes, mas mais sobre isso abaixo) o campo de dados no qual oid é armazenado em formato binário.
Se este parâmetro contiver o oid do algoritmo de hash GOST R 34.10-2012 com um comprimento de 256 bits (oid = 1.2.643.7.1.1.2.2, no formato binário "\ x2a \ x 85 \ x 03 \ x 07 \ x 01 \ x 01 \ x 02 \ x02 "), o tipo de chave deve ser definido como GOST R 34.10-2012 com um comprimento de chave de 256 bits. Caso contrário, é a chave do GOST R 34.10-2001. O algoritmo para determinar o tipo de chave pode ser assim:
. . . 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; . . . } } . . .
Agora, temos todos os dados de origem para criar a estrutura de chave pública asn1.
Lembre-se de que cada elemento da estrutura asn1 consiste em três campos:
- identificador;
- comprimento do campo de dados (em bytes);
- campo de dados.
Aqui está
uma tabela de codificação para alguns tipos de identificadores usados em PKI / PKI:
Nome do tipo | Breve descrição | Representação de tipo na codificação DER |
---|
SEQUÊNCIA | Usado para descrever uma estrutura de dados que consiste em vários tipos. | 30 |
INTEGER | Inteiro | 02 |
IDENTIFICADOR DE OBJETOS | Uma sequência de números inteiros. | 06 |
UTCTime | Tipo temporário, contém 2 dígitos para determinar o ano | 17 |
Tempo generalizado | Tipo de horário estendido, contém 4 dígitos para indicar o ano. | 18 |
SET | Descreve a estrutura de dados de vários tipos. | 31 |
UTF8String | Descreve os dados da string. | 0C |
Nulo | Na verdade NULL | 05 |
BIT STRING | Digite para armazenar uma sequência de bits. | 03 |
OCTET STRING | Digite para armazenar uma sequência de bytes | 04 |
Ao trabalhar com estruturas asn1, o maior choque para os não iniciados é causado pelo método de codificação do comprimento do campo de dados, especialmente quando ele é formado, e se levarmos em conta a arquitetura do computador (littleendien, bigendien). Esta é uma
ciência inteira. E assim, no processo de discutir o algoritmo para formar esse campo, surgiu a idéia de usar a função sprintf, que levará em conta a arquitetura e como o número de bytes para armazenar o comprimento é determinado pode ser visto pelo código da função, que prepara um buffer com o identificador de tipo de dados e o comprimento dos dados:
unsigned char *wrap_id_with_length(unsigned char type,
A função retorna um ponteiro para um buffer com uma estrutura asn1, alocada levando em consideração o comprimento dos dados. Resta copiar esses dados no buffer recebido com um deslocamento pelo comprimento do cabeçalho. O comprimento do cabeçalho é retornado através do parâmetro lenasn.
Para verificar como essa função funciona, escreveremos um utilitário simples:
#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); }
Salve-o junto com a função wrap_id_with_length no arquivo wrap_id_with_length.c.
Nós transmitimos:
$cc –o wrap_id_with_length wrap_id_with_length.c $
Execute o programa resultante com vários dados de origem. O tipo de dados é especificado por um número decimal:
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$
Você pode verificar a correção da formação do cabeçalho usando qualquer calculadora:

Estamos todos prontos para formar qualquer estrutura ASN1. Mas primeiro, faça pequenas alterações na função wrap_id_with_length e chame-a de
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; }
Como você pode ver, as mudanças são mínimas. Como parâmetros de entrada, os próprios dados são adicionados, que dentro da função são compactados em uma estrutura asn1. Além disso, dois buffers podem ser alimentados na entrada de uma só vez. Parece-nos conveniente.
Antes de apresentar um caso de teste, fornecemos os códigos de mais três funções. A primeira função
oid2buffer converte oid s da forma decimal pontilhada em codificação DER. Precisamos desta função para converter, em particular, oids do par de chaves (veja acima).
O texto da função está aqui:char estático * oid2buffer (char * oid_str, sem assinatura por muito tempo * len) {
char * curstr;
char * curstr1;
char * nextstr;
unsigned int firstval;
unsigned int secondval;
int não assinado;
charf sem sinal [5];
int count;
char não assinado 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 (curva);
curstr = nextstr + 1;
nextstr = strchr (curstr, '.');
if (nextstr) {
* nextstr = '\ 0';
}
secondval = atoi (curva);
if (firstval> 2) {
* len = 0;
return NULL;
}
if (secondval> 39) {
* len = 0;
return NULL;
}
oid_hex [0] = (caractere não assinado) ((firstval * 40) + secondval);
i = 1;
while (nextstr) {
curstr = nextstr + 1;
nextstr = strchr (curstr, '.');
if (nextstr) {
* nextstr = '\ 0';
}
memset (buf, 0, sizeof (buf));
val = atoi (curva);
contagem = 0;
if (curstr [0]! = '0')
while (val) {
buf [contagem] = (val & 0x7f);
val = val >> 7;
count ++;
}
mais {
buf [contagem] = (val & 0x7f);
val = val >> 7;
count ++;
}
while (count--) {
if (count) {
oid_hex [i] = buf [contagem] | 0x80;
} mais {
oid_hex [i] = buf [contagem];
}
i ++;
}
}
res = (char *) malloc (i);
se (res) {
memcpy (res, oid_hex, i);
* len = i;
}
livre (curstr1);
return res;
}
As outras duas funções permitem que o buffer binário seja convertido em uma contagem hexadecimal (buffer2hex) e vice-versa (hex2buffer).
Estas funções estão aqui:caractere estático *
buffer2hex (caracter não assinado const * src, tamanho_t len)
{
int i;
char * dest;
char * res;
dest = (char *) malloc (len * 2 + 1);
res = dest;
se (dest)
{
para (i = 0; i <len; i ++, dest + = 2)
sprintf (dest, "% 02X", src [i]);
}
return res;
}
vazio estático *
hex2buffer (const char * string, size_t * r_length)
{
const char * s;
buffer char * não assinado;
tamanho size_t;
buffer = malloc (strlen (string) / 2 + 1);
comprimento = 0;
para (s = string; * s; s + = 2)
{
if (! hexdigitp (s) ||! hexdigitp (s + 1)) {
fprintf (stderr, "dígitos hexadecimais inválidos em \"% s \ "\ n", string);
}
((caractere não assinado *) buffer) [length ++] = xtoi_2 (s);
}
* comprimento_ r = comprimento;
buffer de retorno;
}
Essas funções são muito convenientes para depuração e, provavelmente, muitas as possuem.
E agora voltamos à solução da tarefa, obtendo uma estrutura asn1 da chave pública. Escreveremos um utilitário que irá gerar e salvar a estrutura ASN1 da chave pública no arquivo ASN1_PIBINFO.der.
Este utilitário está localizado aqui: #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); }
Para verificar o resultado, usaremos os utilitários derdump e pp do pacote NSS.
O primeiro utilitário nos mostrará a estrutura asn1 da chave pública:
$ 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 $
O segundo mostrará o conteúdo da chave:
$ 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 $
Quem desejar pode verificar, por exemplo, o utilitário openssl, de preferência com um mecanismo
GOST conectado :
$ /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 $
Como você pode ver, a estrutura ASN1 resultante é testada com sucesso em qualquer lugar.
O algoritmo e o utilitário propostos para a formação de estruturas asn1 não requerem o uso de compiladores e bibliotecas de extensão ASN1 (o mesmo openssl) e mostraram ser muito convenientes de usar. Vamos lembrá-los no próximo artigo, quando os desejos de
Pas forem atendidos e será apresentado um utilitário gráfico que não apenas “analisa os certificados” e verifica sua validade, mas também gera um par de chaves nos tokens PKCS # 11, gera e assina uma solicitação para um certificado qualificado. Com essa solicitação, você pode ir com segurança à CA para obter um certificado. Antes das perguntas, observo imediatamente que, no último caso, o token deve ser certificado como um sistema de proteção de informações criptográficas no sistema de certificação do FSB da Rússia.