シンプルなsprintf ASN1コーデック

画像 ASN.1トランスポート構文は、有効な型の変数の値をネットワーク経由で送信するためのバイトシーケンスに変換する独自の方法を定義しています。 ASN.1では、Basic Encoding Rules(BER)と呼ばれます。 ルールは再帰的であるため、複合オブジェクトのエンコードは、コンポーネントオブジェクトのエンコードされたシーケンスのチェーンです。 ASN.1プロトコルはシンプルで理解できる言語でデータ構造を説明します。



送信される各値(基本型と派生型の両方)は、3つのフィールドで構成されます。





データフィールドの長さを常に指定する場合(これが良いトーンの規則だと思います)、データフィールドの終わりのフラグは使用されません。



ASN.1にはさまざまなプログラミング言語用の有料および無料のコンパイラが多数ありますが、非常にシンプルなものを手元に用意したいと思います。



ソフトウェア開発者の大多数は、 ASN.1標準複合体を見つけています。 私も最近までそう思いました。 PKI / PKI /暗号化の分野でほぼ毎日、X509証明書、証明書要求、失効した証明書のリストの形式でASN1構造を扱います。 そしてリストは続きます。 そのため、PKCS#11トークン/スマートカードでキーペアを生成し、PKCS#10形式で証明書要求を作成するユーティリティに取り組んでいる間、私は当然、特に証明書要求に書き込むための公開鍵のasn1-構造を形成する必要がありました:



C-Sequence C-Sequence (<>) Object Identifier (<>) <oid public key> C-Sequence (<>) Object Identifier (<>) <oid  > Object Identifier (<>) <oid > Bit String (<>) <  >
      
      





暗号情報保護ツールとしてロシアの暗号化をサポートするPKCS#11トークンを使用したため、この構造のソース資料は次のテンプレートに従ってトークンから取得されました。



  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)} }
      
      





この構造から直接、公開キー値を含むCKA_VALUE属性の値、および署名パラメーターとハッシュパラメーターのOIDを含むCKA_GOSTR3410PARAMSおよびCKA_GOSTR3411PARAMS属性の値は、asn1-publickeyinfoの入力に使用されます。



属性CKA_KEY_TYPEは、値CKK_GOSTR3410およびCKK_GOSTR3410_512をとることができます(GOST R 34.10-2001の署名アルゴリズムが動作し続ける条件で)、キーペアアルゴリズムを明確に定義します。 CKA_KEY_TYPE属性の値がCKK_GOSTR3410_512である場合、もちろん、キーの長さが512ビット(oid = 1.2.643.7.1.1.1.2)のGOST R 34.10-2012アルゴリズムを一意に指します。 しかし、それが単にCKK_GOSTR3410と等しい場合、このキーがどのタイプのキーに属するかについてあいまいさがあります:GOST R 34.10-2001またはそれでもGOST R 34.10-2012であり、キーの長さは256ビットです。 CKA_GOSTR3411PARAMS属性は、このあいまいさを解決するのに役立ちます。



TK-26の推奨事項に従ってトークンのパラメーターCKA_GOSTR3410PARAMSおよびCKA_GOSTR3411PARAMSが、oidによってエンコードされたオブジェクト識別子として格納されていることに注意してください。たとえば:



\ x06 \ x06 \ x2a \ x85 \ x03 \ x02 \ x02 \ x13、ゼロバイトはシーケンスのタイプを決定します(0x06はオブジェクト識別子、下の表を参照)、2番目のバイトは長さを示します(一般的な場合、長さは数バイトかかることがあります、ただし、以下で詳しく説明します)oidがバイナリ形式で保存されるデータフィールド。



このパラメーターに256ビット長のGOST R 34.10-2012ハッシュアルゴリズムのoidが含まれる場合(oid = 1.2.643.7.1.1.2.2、バイナリ形式 "\ x2a \ x 85 \ x 03 \ x 07 \ x 01 \ x 01 \ x 02 \ x02 ")、キータイプはGOST R 34.10-2012に設定し、キーの長さは256ビットにする必要があります。 それ以外の場合は、GOST R 34.10-2001のキーです。 キーのタイプを決定するアルゴリズムは次のようになります。



 . . . 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)) { /*    34.10-2001*/ 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 { /*    34.10-2012-256*/ 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) { /*    34.10-2012-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; . . . } } . . .
      
      





これで、asn1公開キー構造を作成するためのすべての入力データができました。



asn1-structureの各要素は3つのフィールドで構成されていることを思い出してください。





PKI / PKIで使用されるいくつかのタイプの識別子のコーディングテーブルを次に示します



タイプ名 簡単な説明 DERエンコーディングの型表現
シーケンス さまざまなタイプで構成されるデータ構造を記述するために使用されます。 30
整数 整数 02
オブジェクト識別子 整数のシーケンス。 06
UTCTime 一時タイプ、年を決定するための2桁 17
一般化時間 拡張時間タイプ。年を示す4桁が含まれます。 18
セット さまざまなタイプのデータ構造を記述します。 31
UTF8String 文字列データについて説明します。 0C
ヌル 実際にはNULL 05
ビット列 ビットシーケンスを格納するための型。 03
オクテットストリング バイトシーケンスを格納するためのタイプ 04


asn1-structuresを使用する場合、特にフィールドが形成されるとき、およびコンピューターのアーキテクチャ(littleendien、bigendien)を考慮に入れると、データフィールドの長さをエンコードする方法が未開始者に最大の衝撃を与えます。 これは全体の科学です。 したがって、このフィールドを形成するためのアルゴリズムを議論する過程で、アーキテクチャを考慮したsprintf関数を使用する考えが思い浮かび、長さを格納するバイト数がどのように決定されるかは、データ型識別子とデータ長を備えたバッファを準備する関数コードによって見ることができます:



 unsigned char *wrap_id_with_length(unsigned char type, //  unsigned long length, //  unsigned long *lenasn) //  asn1- { // unsigned long length; int buflen = 0; unsigned char *buf; char *format; 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"; /*        */ buflen = ( length < 0x80 ? 1: length <= 0xff ? 2: length <= 0xffff ? 3: length <= 0xffffff ? 4: 5); /*   asn-*/ buf = malloc(length + buflen); // buf = malloc(buflen); /*        sprintf*/ 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; } // sprintf    little  bigendian       sprintf((char*)buf_for_len, (const char *)format, type, length); length = 0; /* asn1-*/ fprintf(stderr, "ASN1 - :%s\n", buf_for_len); /*     */ for (s=(const char *)buf_for_len; *s; s +=2 ) { if (!hexdigitp (s) || (!hexdigitp (s+1) && hexdigitp (s+1) != 0) ){ fprintf (stderr, "invalid hex digits in \"%s\"\n", buf_for_len); *lenasn = 0; return NULL; } ((unsigned char*)buf)[length++] = xtoi_2 (s); } *lenasn = length; return (buf); }
      
      





この関数は、データの長さに基づいて割り当てられたasn1構造を持つバッファーへのポインターを返します。 このデータを、ヘッダーの長さのオフセットで受信バッファーにコピーします。 ヘッダーの長さは、lenasnパラメーターを介して返されます。



この機能がどのように機能するかを確認するために、簡単なユーティリティを作成します。



 #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); }
      
      





wrap_id_with_length.cファイルにwrap_id_with_length関数とともに保存します。



放送します:



 $cc –o wrap_id_with_length wrap_id_with_length.c $
      
      





結果のプログラムをさまざまなソースデータで実行します。 データ型は10進数で指定されます。



 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$
      
      





任意の計算機を使用して、ヘッダー形成の正確性を確認できます。







ASN1構造を形成する準備ができました。 ただし、最初にwrap_id_with_length関数に小さな変更を加えて呼び出します



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; } // sprintf    little  bigendian    sprintf((char*)buf_for_len, (const char *)format, type, length); length = 0; for (s=buf_for_len; *s; s +=2 ) { if (!hexdigitp (s) || (!hexdigitp (s+1) && hexdigitp (s+1) != 0) ){ fprintf (stderr, "invalid hex digits in \"%s\"\n", buf_for_len); } ((unsigned char*)buf)[length++] = xtoi_2 (s); } if (prefix_len > 0) { memcpy(buf + length, prefix, prefix_len); } memcpy(buf + length + prefix_len, wrap, wrap_len); *lenasn = (unsigned long)(length + prefix_len + wrap_len); return (buf); }
      
      







ご覧のとおり、変更は最小限です。 入力パラメーターとして、データ自体が追加され、関数内でasn1-structureにパックされます。 さらに、2つのバッファーを一度に入力に供給することができます。 便利だと思います。



テストケースを提示する前に、さらに3つの関数のコードを示します。 最初のoid2buffer関数は、oidをドット付き10進数形式からDERエンコードに変換します。 この関数は、特にキーペアのOIDを変換するために必要になります(上記参照)。



関数のテキストは次のとおりです。
static char * oid2buffer(char * oid_str、unsigned long * len){

char * curstr;

char * curstr1;

char * nextstr;

unsigned int firstval;

unsigned int secondval;

unsigned int val;

unsigned char buf [5];

intカウント;

unsigned char oid_hex [100];

char * res;

int i;

if(oid_str == NULL){

* len = 0;

NULLを返します。

}

* len = 0;

curstr = strdup((const char *)oid_str);

curstr1 = curstr;

nextstr = strchr(curstr、 '。');

if(nextstr == NULL){

* len = 0;

NULLを返します。

}

* nextstr = '\ 0';

firstval = atoi(curstr);

curstr = nextstr + 1;

nextstr = strchr(curstr、 '。');

if(nextstr){

* nextstr = '\ 0';

}

secondval = atoi(curstr);

if(firstval> 2){

* len = 0;

NULLを返します。

}

if(secondval> 39){

* len = 0;

NULLを返します。

}

oid_hex [0] =(unsigned char)((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);

カウント= 0;

if(curstr [0]!= '0')

while(val){

buf [count] =(val&0x7f);

val = val >> 7;

カウント++;

}

その他{

buf [count] =(val&0x7f);

val = val >> 7;

カウント++;

}

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;

}

無料(curstr1);

解像度を返す;

}



他の2つの関数を使用すると、バイナリバッファーを16進カウント(buffer2hex)に変換したり、その逆(hex2buffer)に変換したりできます。



これらの機能は次のとおりです。
静的文字*

buffer2hex(const unsigned char * src、size_t len)

{

int i;

char * dest;

char * res;

dest =(char *)malloc(len * 2 + 1);

res = dest;

if(dest)

{

for(i = 0; i <len; i ++、dest + = 2)

sprintf(dest、 "%02X"、src [i]);

}

解像度を返す;

}



静的ボイド*

hex2buffer(const char * string、size_t * r_length)

{

const char * s;

unsigned char * buffer;

size_t長さ;



buffer = malloc(strlen(文字列)/ 2 + 1);

長さ= 0;

for(s =文字列; * s; s + = 2)

{

if(!hexdigitp(s)||!hexdigitp(s + 1)){

fprintf(stderr、「\ "%s \" \ n」の無効な16進数、文字列);

}

((unsigned char *)buffer)[length ++] = xtoi_2(s);

}

* r_length =長さ;

戻りバッファ。

}



これらの関数はデバッグに非常に便利であり、多くの関数に確実に備わっています。



そして、タスクのソリューションに戻り、公開キーのasn1構造を取得します。 asn1公開キー構造を生成し、ASN1_PIBINFO.derファイルに保存するユーティリティを作成します。



このユーティリティは次の場所にあります。
 #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); }
      
      







結果を検証するには、NSSパッケージのderdumpおよびppユーティリティを使用します。



最初のユーティリティは、公開キーのasn1構造を表示します。



 $ 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 $
      
      





2番目には、キーの内容が表示されます。



 $ 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 $
      
      





たとえば、 接続されたGOSTエンジンでopensslユーティリティを使用することをお勧めします。



 $ /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 $
      
      





ご覧のとおり、結果のASN1構造はどこでも正常にテストされています。



asn1構造を形成するために提案されたアルゴリズムとユーティリティは、ASN1コンパイラと拡張ライブラリ(同じopenssl)の使用を必要とせず、非常に使いやすいことが判明しました。 次の記事でそれらを思い出します。Pasの希望が満たされ、「証明書の解析」とその有効性を検証するだけでなく、PKCS#11トークンでキーペアを生成し、資格のある証明書の要求を生成して署名するグラフィカルユーティリティが表示されます。 このリクエストを使用すると、証明書を取得するためにCAに安全にアクセスできます。 質問に先立ち、私は、後者の場合、ロシアのFSBの認証システムで暗号情報保護システムとしてトークンを認証する必要があることをすぐに指摘します。



All Articles