- PVSM.RU - https://www.pvsm.ru -

Инфраструктура открытых ключей: библиотека GCrypt как альтернатива OpenSSL с поддержкой российской криптографии

Инфраструктура открытых ключей: библиотека GCrypt как альтернатива OpenSSL с поддержкой российской криптографии - 1 Приближается вторая половина 2018 года и скоро должен наступить «2000-й год» [1] в ИОК на базе российской криптографии. Это связано с тем, что

использование схемы подписи ГОСТ Р 34.10-2001 для формирования подписи после 31 декабря 2018 года не допускается!

Уже сегодня не имеет смысла получать сертификаты с подписью по ГОСТ Р 34.10-2001.
В тоже время очень много сервисов или приложений разработано на базе OpenSSL, который поддерживал работу с ГОСТ Р 34.10-2001.
Но сегодня в стандартной версии openssl отсутствует поддержка как ГОСТ Р 34.11-2012, так и ГОСТ Р 34.10-2012. Более того в версии 1.1 поддержка криптографии ГОСТ исключена [2] из стандартной поставки («The GOST engine was out of date and therefore it has been removed.»).
Все это заставляет искать альтернативные пути для работы с сертификатами, с электронной подписью («сообщениями формата CMS») и другими объектами ИОК на базе новой российской криптографии.
Одним из таких возможных путей является использование библиотеки GCrypt [3]. В этой библиотеки реализована поддержка новых алгоритмов ГОСТ Р 34.11-2012 (алгоритмы хэширования) и ГОСТ Р 34.10-2012 (алгоритмы подписи).

Генерация ключевой пары

Итак, начинаем с генерации ключевой пары, содержащей как закрытый, так и открытый ключи. На данный момент в российской криптографии присутствует три типа ключей подписи с соответствующими oid-ами

— ГОСТ Р 34.10-2001 с длиной ключа 256 бит, oid 1.2.643.2.2.19 (0x2a, 0x85, 0x03, 0x02, 0x02, 0x13)
— ГОСТ Р 34.10-2012 с длиной ключа 256 бит (далее ГОСТ Р 34.10-12-256), oid 1.2.643.7.1.1.1.1 (0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x01, 0x01)
— ГОСТ Р 34.10-2012 с длиной ключа 512 бит (далее ГОСТ Р 34.10-12-512), oid 1.2.643.7.1.1.1.2 (0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x01, 0x02)

И сразу отметим, что с точки зрения математики, алгоритмов генерации и их реализации, ключи ГОСТ Р 34.10-2001 и ГОСТ Р 34.10-12-256 абсолютно идентичны! Так же как идентичны алгоритмы формирования электронной подписи на их основе. О каком из этих ключей идет речь можно судить только по информации о ключе, содержащейся, например, в сертификате. Так зачем потребовалось два разных oid-а? Только подчеркнуть тот факт, что при формировании электронной подписи с ключом ГОСТ Р 34.10-2001, должен использоваться хэш, полученный по ГОСТ Р 34.10-94, а при использовании ключа ГОСТ Р 34.10-12-256 должен использоваться хэш, получаемый по ГОСТ Р 34.10-212 с длиной 256 бит. Хотя, чтобы подчеркнуть это, есть оказывается соответствующие oid-ы:

— 1.2.643.2.2.3 (0x2a, 0x85, 0x03, 0x02, 0x02, 0x03) — алгоритм подписи ГОСТ Р 34.10-2001 с ключом 256 с хэшированием ГОСТ Р 34.11-94;
— 1.2.643.7.1.1.3.2 (0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x03, 0x02) — алгоритм подписи ГОСТ Р 34.10-2012 с ключом 256 с хэшированием ГОСТ Р 34.11-2012;
— 1.2.643.7.1.1.3.3 (0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x03, 0x03) — алгоритм подписи ГОСТ Р 34.10-2012 с ключом 512 с хэшированием ГОСТ Р 34.11-2012.

Таким образом, имеется некоторый перебор с oid-ами, но что есть, то есть.
Ключи семейства ГОСТ относятся к семейству ключей на эллиптических кривых. Для генерации ключевой пары необходимо указать базовую точку на эллиптической кривой.
Техническим комитетом по стандартизации «Криптографическая защита информации» (ТК 26 [4]) рекомендованы для использования две базовые точки для ключей ГОСТ Р 34.10-2012-512:

— GOST2012-tc26-A (nickname в терминологии libgcrypt) с oid-ом 1.2.643.7.1.2.1.2.1 (0x2a, 0x85, 0x03, 0x07, 0x01, 0x02, 0x01, 0x02, 0x01);
— GOST2012-tc26-B с oid-ом 1.2.643.7.1.2.1.2.2 (0x2a, 0x85, 0x03, 0x07, 0x01, 0x02, 0x01, 0x02, 0x02);

Для ключей ГОСТ Р 34.10 с длиной 256 бит рекомендованы три базовые точки:

— GOST2001-CryptoPro-A с oid-ом 1.2.643.2.2.35.1 (0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x01);
— GOST2001-CryptoPro-B с oid-ом 1.2.643.2.2.35.2 (0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x02);
— GOST2001-CryptoPro-C с oid-ом 1.2.643.2.2.35.3 (0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x03).

Для ключей ГОСТ Р 34.10 с длиной 256 бит определены еще два oid-а для базовых точек:

— GOST2001-CryptoPro-XchA с oid-ом 1.2.643.2.2.36.0 (0x2a, 0x85, 0x03, 0x02, 0x02, 0x24, 0x00);
— GOST2001-CryptoPro-XchB с oid-ом 1.2.643.2.2.36.1 (0x2a, 0x85, 0x03, 0x02, 0x02, 0x24, 0x01).

Однако в реальности эти oid-ы ссылаются на бозовые точки GOST2001-CryptoPro-A с oid-ом 1.2.643.2.2.35.1 и GOST2001-CryptoPro-C с oid-ом 1.2.643.2.2.35.3 соответственно.
И это следует учитывать при обработке базовых точек с этими oid-ами.
Для генерации ключевой пары используется функция gcry_pk_genkey следующего вида:

gcry_error_t gcry_pk_genkey (gcry sexp t *key_pair, gcry sexp t key_spec ).

В переменной parms должны быть установлены параметры для генерации ключевой пары в формате внутреннего S-выражения [5] (sexp). Для генерации ключевой пары по ГОСТ параметры задаются в виде следующего S-выражения:

(genkey (есс (curve базовая_точка))), где 

ecc определяет генерацию ключевой пары на эллиптических кривых, а базовая точка должна указывать конкретную точку, рекомендуемую ТК-26. Именно указанная базовая точка определяет какая ключевая пара будет сгенерирована. Если указать, например, GOST2012-tc26-A или GOST2012-tc26-В, то будет сгенерирована ключевая пара по ГОСТ Р 34.10-2012 с длиной ключа 512 бит. Вместо nickname базовой точки можно указывать напрямую oid:

(genkey (есс (curve «1.2.643.2.2.35.3»)))

В последнем случае предполагается генерация ключевой пары по ГОСТ Р 34.10 с длиной ключа 256 бит и базовой точкой GOST2001-CryptoPro-C.
Ниже приведен пример программы на языке C, который демонстрирует генерацию ключей,

GenKey.c

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <gcrypt.h>
/*Функция печати S-выражений*/
static void
show_sexp (const char *prefix, gcry_sexp_t a)
{
  char *buf;
  size_t size;
  if (prefix)
    fputs (prefix, stderr);
  size = gcry_sexp_sprint (a, GCRYSEXP_FMT_ADVANCED, NULL, 0);
  buf = gcry_xmalloc (size);
  gcry_sexp_sprint (a, GCRYSEXP_FMT_ADVANCED, buf, size);
  fprintf (stderr, "%.*s", (int)size, buf);
  gcry_free (buf);
}
int main(int argc, char* argv[]) {
    gpg_error_t err;
    gcry_sexp_t key_spec, key_pair, pub_key, sec_key;
/*Базовые точки*/
    char *curve_gost[] = {"GOST2001-CryptoPro-A", "GOST2001-CryptoPro-B", "GOST2001-CryptoPro-C", "GOST2012-tc26-A", "GOST2012-tc26-B", NULL};
/*Задаем параметры для генерации ключевой пары*/
    err = gcry_sexp_build (&key_spec, NULL,
                               "(genkey (ecc (curve %s)))", curve_gost[1]);
    if (err) {
         fprintf(stderr, "creating S-expression failed: %sn", gcry_strerror (err));
    	exit (1);
    }
    err = gcry_pk_genkey (&key_pair, key_spec);
    if (err){
        fprintf(stderr, "creating %s  key failed: %sn", argv[1], gcry_strerror (err));
        exit(1);
    }
/*Печать S-выражения ключевой пары*/
    show_sexp ("ECC GOST key pair:n", key_pair);
/*Сохраняем публичный ключ*/
    pub_key = gcry_sexp_find_token (key_pair, "public-key", 0);
    if (! pub_key) {
        fprintf(stderr, "public part missing in keyn");
        exit(1);
    }
/*Печать S-выражения публичного ключа*/
    show_sexp ("ECC GOST public key:n", pub_key);
/*Сохраняем закрытый ключ*/
    sec_key = gcry_sexp_find_token (key_pair, "private-key", 0);
    if (! sec_key){
        fprintf(stderr, "private part missing in keyn");
        exit(1);
    }
/*Печать S-выражения закрытого ключа*/
    show_sexp ("ECC GOST private key:n", sec_key);
/*Освобождаем память, занимаемую ключевой парой*/    
    gcry_sexp_release (key_pair);
/*Освобождаем память, занимаемую параметрами ключевой пары*/    
    gcry_sexp_release (key_spec);
}

Для трансляции примера необходимо выпонить команду:

$cc –o GenKey GenKey.c –lgcrypt
$

После запуска модуля GenKey получим

ключевую пару

ECC GOST key pair:
(key-data 
 (public-key 
  (ecc 
   (curve GOST2001-CryptoPro-B)
   (q #043484CF83F837AAC7ABD4707DE27F5A1F6161120C0D77B63DFFC7D50A7772A12D1E836E6257766E8B83209DD59845F8080BA29E9A86D0A6B6C2D68F44650B3A14#)
   )
  )
 (private-key 
  (ecc 
   (curve GOST2001-CryptoPro-B)
   (q #043484CF83F837AAC7ABD4707DE27F5A1F6161120C0D77B63DFFC7D50A7772A12D1E836E6257766E8B83209DD59845F8080BA29E9A86D0A6B6C2D68F44650B3A14#)
   (d #1ABB5A62BFF88C97567B467C6F4017242FE344B4F4BC8906CE40A0F9D51CBE48#)
   )
  )
 )
ECC GOST public key:
(public-key 
 (ecc 
  (curve GOST2001-CryptoPro-B)
  (q #043484CF83F837AAC7ABD4707DE27F5A1F6161120C0D77B63DFFC7D50A7772A12D1E836E6257766E8B83209DD59845F8080BA29E9A86D0A6B6C2D68F44650B3A14#)
  )
 )
ECC GOST private key:
(private-key 
 (ecc 
  (curve GOST2001-CryptoPro-B)
  (q #043484CF83F837AAC7ABD4707DE27F5A1F6161120C0D77B63DFFC7D50A7772A12D1E836E6257766E8B83209DD59845F8080BA29E9A86D0A6B6C2D68F44650B3A14#)
  (d #1ABB5A62BFF88C97567B467C6F4017242FE344B4F4BC8906CE40A0F9D51CBE48#)
  )
 )

Теперь, имея на руках закрытый ключ, можно создавать электронную подпись.

Хэширование документа

Создание электронной подписи (ЭП) документа начинается с получения значения хэша от подписываемого документа. Для подписи документа может быть выбран один из алгоритмов:

— 1.2.643.2.2.3 (0x2a, 0x85, 0x03, 0x02, 0x02, 0x03) — алгоритм подписи ГОСТ Р 34.10-2001 с ключом 256 с хэшированием по ГОСТ Р 34.11-94 с длиной хэша 256 бит;
— 1.2.643.7.1.1.3.2 (0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x03, 0x02) — алгоритм подписи ГОСТ Р 34.10-2012 с ключом 256 с хэшированием по ГОСТ Р 34.11-2012 с длиной хэша 256 бит;
— 1.2.643.7.1.1.3.3 (0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x03, 0x03) — алгоритм подписи ГОСТ Р 34.10-2012 с ключом 512 с хэшированием по ГОСТ Р 34.11-2012 с длиной хэша 512 бит.

Эти алгоритмы определяют не только тип закрытого ключа, который будет использован для получения ЭП, но и алгоритм хэш-функции. В библиотеке GCrypt реализованы все три типа функций. Алгоритм хэширования ГОСТ Р 34.11-94 (с oid-ом параметра 1.2.643.2.2.30.1 — 0x2a, 0x85, 0x03, 0x02, 0x02, 0x1e, 0x01) реализован под nickname-ом GOSTR3411_CP, алгоритм ГОСТ Р 34.11-2012 с длиной 256 бит (oid 1.2.43.7.1.1.2.2 — 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x02) реализован под nickname-ом STRIBOG256 и алгоритм ГОСТ Р 34.11-2012 с длиной 512 бит (oid 1.2.43.7.1.1.2.3 — 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x03) реализован под nickname-ом STRIBOG512.
Ниже приведен код модуля на языке C, для вычисления значения хэш от документа, хранящегося в файле

digest_gcrypt.c :

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <gcrypt.h>
int main(int argc, char* argv[]) {
    gpg_error_t err;
    int algo = GCRY_MD_NONE;
    int i;
    unsigned char *h;
    size_t size;
    gcry_md_hd_t hd;
    FILE *fp;
    unsigned char buf[1024];	
    char *dgst_gost[] = {"GOSTR3411_CP", "STRIBOG256", "STRIBOG512", NULL};
    i = 0;
/*Проверка параметров хэш-функции*/
    if (argc == 3)
        algo = gcry_md_map_name (argv[1]);
    if (algo == GCRY_MD_NONE) {
    	fprintf(stderr, "Usage: digest_gcrypt <nick_name_digest> <file_for_digest>n");
        fprintf(stderr, "<nick_name_digest>=");
    	while (dgst_gost[i] != NULL){
            if (i > 0 ) 
                fprintf(stderr, " | ", dgst_gost[i]);              
    	    fprintf(stderr, "%s", dgst_gost[i]);
    	    i++;
    	}
        fprintf(stderr, "n");
    	exit (1);
    }
/*Создаем контекст хэш функции*/
    err = gcry_md_open(&hd, algo, 0);
    if (err) { 
fprintf (stderr, "LibGCrypt error %sn",
       gcry_strerror (err));
        exit (1);
    }
/*Открываем подписываемый файл*/
    if (!strcmp (argv[2], "-"))
        fp = stdin;
    else
        fp = fopen (argv[2], "r");

    if (fp == NULL) {
          fprintf(stderr, "Cannot fopen file=%sn", argv[2]);
          exit(1);
    }
/*Считаем хэш содержимого файла*/
    while (!feof (fp)) {
          size = fread (buf, 1, sizeof(buf), fp);
          gcry_md_write (hd, buf, size);
    }
/*Сохраняем значение хэш*/
    h  = gcry_md_read(hd, 0);
/*Печатаем значение хэш*/
    printf("Длина хеш %s = %d (в байтах)n", argv[1], gcry_md_get_algo_dlen (algo));
    printf("Хэш от файла  %s:n", argv[2]);
    for (i = 0; i < gcry_md_get_algo_dlen (algo); i++)
        printf("%02x", h[i]);
    printf("n");
    fflush(stdout);
/*Закрываем контекст*/
    gcry_md_reset(hd);
    gcry_md_close(hd);
}

Транслируем этот модуль и получаем утилиту вычисления хэш:

$cc -o digest_file digest_file.c  -lgcrypt
$./digest_file 
Usage: digest_gcrypt <nick_name_digest> <file_for_digest>
<nick_name_digest>=GOSTR3411_CP | STRIBOG256 | STRIBOG512
$./digest_file  STRIBOG256 digest_file.c 
Длина хеш STRIBOG256 = 32 (в байтах)
Хэш от файла  digest_file.c:
f6818dfb26073747266dc721c332d703eb21f2b17e3433c809e0e23b68443d4a
$

Формирование электронной подписи и ее проверка

Теперь, когда имеется закрытый ключ и хэш документа, мы можем сформировать и электронную подпись документа:

gcry_error_t gcry_pk_sign (gcry sexp t *r_sig, gcry sexp t data, gcry sexp t skey ), где

r_sig -sexp-переменная, в которой будет сохранена электронная подпись,
skey – sexp-переменная с закрытым ключом (см. выше), который используется для формирования подписи,
data – sexp-переменная, в которой указываются тип подписи (в нашем случае gost) и хэш подписываемого документа.
Здесь следует помнить, что значение хэш, поступающее на вход gcry_pk_sign для формирования подписи ГОСТ Р 34.10 должно быть в формате big-endian, т.е. фактически перевернуто (как сказал один товарищ: «Российская традиция рассматривать байты дайджеста в порядке little-endian»). С учетом этого замечания подготовка sexp-переменной data выглядит следующим образом:

...
gcry_sexp_t data;
unsigned char c;
int len_xy;
gcry_mpi_t x;
…
/*Проверяем архитектуру и инвертируем хэш*/
    printf("%sn", *((unsigned char *) &arch) == 0 ? "Архитектура big-endian" : "Архитекткра little-endian");
    len_xy =  *((unsigned char *) &arch) == 0 ? 0:gcry_md_get_algo_dlen (algo);   
 for (i = 0; i < (len_xy/2); i++) {
            	    c = *(h + i);
            	    *(h + i) = *(h + len_xy - i - 1);
            	    *(h + len_xy - i - 1) = c;
    }
fprintf(stderr, "Хэш длина=%dn", gcry_md_get_algo_dlen (algo));
    for (i = 0; i < gcry_md_get_algo_dlen (algo); i++)
        printf("%02X", h[i]);
    fflush(stdout);
/*Сохраняем хэш в mpi-переменной*/
    x = gcry_mpi_set_opaque_copy(NULL, h, gcry_md_get_algo_dlen (algo) * 8);
/*Инициализируем sexp-переменную data исходными данными – тип и хэш*/
    err = gcry_sexp_build (&data, NULL, "(data (flags gost) (value %m))", x);
/* Печатаем содержимое переменной data */
    show_sexp ("data :n", data);

Все готово для получения подписи и ее просмотра

…
 gcry_sexp_t sig_r, sig_s;
…
/*Подписываем хэш*/
    err = gcry_pk_sign (&sig, data, sec_key);
    if (err) {
        fprintf(stderr, "signing failed: %sn", gcry_strerror (err));
	exit(1);
    }
/* Печатаем подпись */
    show_sexp ("ECC GOST SIG:n", sig);
/*Выбираем и печаем составные части подписи r и s*/
    sig_r = gcry_sexp_find_token (sig, "r", 0);
    if (!sig_r) {
        fprintf(stderr, "r part missing in sign");
        exit(1);
    }
    show_sexp ("ECC GOST Sig R-part:n", sig_r);
    sig_s = gcry_sexp_find_token (sig, "s", 0);
    if (! sig_s) {
        fprintf(stderr, "s part missing in sign");
        exit(1);
    }
    show_sexp ("ECC GOST Sig S-part:n", sig_s);
…

Проверить подпись можно следующим образом:

…
    err = gcry_pk_verify (sig, data, pub_key);
    if (err) {
          putchar ('n');
          show_sexp ("seckey:n", sec_key);
          show_sexp ("data:n", data);
          show_sexp ("sig:n", sig);
          fprintf(stderr, "verify failed: %sn", gcry_strerror (err));
          exit(1);
    }
…

Проверка электронной подписи ГОСТ Р 34.10 в сертификатах

image [6]Одними из главных объектов инфраструктуры открытых ключей (ИОК) являются сертификаты X509. Рекомендации ТК-26 по составу и структуре сертификатов изложены в документе «ТЕХНИЧЕСКАЯ СПЕЦИФИКАЦИЯ ИСПОЛЬЗОВАНИЯ АЛГОРИТМОВ ГОСТ Р 34.10, ГОСТ Р 34.11 В ПРОФИЛЕ СЕРТИФИКАТА И СПИСКЕ ОТЗЫВА СЕРТИФИКАТОВ (CRL) ИНФРАСТРУКТУРЫ ОТКРЫТЫХ КЛЮЧЕЙ X.509 (Утверждена решением заседания технического комитета по стандартизации «Криптографическая защита информации» (Протокол N13 от 24.04.2014 г.) ». Именно в соответствии с этими рекомендациями выпускают сертификаты все аккредитованные в Минкомсвязи России УЦ [6].
Для проверки подписи в сертификате необходимо (см. выше) получить хэш проверяемого сертификата, его подпись и открытый ключ корневого сертификата. Сразу отметим, что для самоподписанного сертификата все эти данные хранятся в одном сертификате.
Для работы с объектами ИОК/PKI (сертификаты, CMS, запросы Инфраструктура открытых ключей: библиотека GCrypt как альтернатива OpenSSL с поддержкой российской криптографии - 3 [3]
и т.п.), как правило, используется библиотека KSBA, которая на данный момент не поддерживает рекомендации ТК-26, хотя опыт [3] такой поддержки есть. В принципе, ничто не мешает добавить поддержку рекомендаций ТК-26 в проект ksba.
На данном этапе, этапе тестирования GCrypt, для работы с объектами ИОК/PKI (сертификаты и т.п.) с россицйской криптографией удобно использовать скриптовые языки типа Python [7], Tcl и т.д. Был выбран скриптовый язык Tcl [8]. На нем легко и просто написать прототип программы, который затем перенести на язык C. В составе Tcl имеется пакет PKI, который содержит процедуры разбора (parse) объектов ИОК, в частности, процедура разбора сертификатов ::pki::x509::parse_cert. На базе процедуры parse_cert была разработана процедура parse_gost_cert, которая находится в файле

parse_cert_gost_oid.tcl

proc parse_cert_gost {cert} {
#    parray ::pki::oids
#puts "parse_cert_gost=$cert"
    set cert_seq ""
    if { [string range $cert 0 9 ] == "-----BEGIN" } {
	array set parsed_cert [::pki::_parse_pem $cert "-----BEGIN CERTIFICATE-----" "-----END CERTIFICATE-----"]
	set cert_seq $parsed_cert(data)
    } else {
#FORMAT DER
	set cert_seq $cert
    }
    set finger [::sha1::sha1 $cert_seq]
    set ret(fingerprint) $finger

    binary scan  $cert_seq H* certdb 
    set ret(certdb) $certdb
#puts "CERTDB=$certdb"
	array set ret [list]

	# Decode X.509 certificate, which is an ASN.1 sequence
	::asn::asnGetSequence cert_seq wholething
	::asn::asnGetSequence wholething cert
	set ret(cert) $cert

	set ret(cert) [::asn::asnSequence $ret(cert)]
if {0} {
    set ff [open "/tmp/tbs.der" w]
    fconfigure $ff -translation binary
    puts -nonewline $ff $ret(cert)
    close $ff
}
	binary scan $ret(cert) H* ret(cert)

	::asn::asnPeekByte cert peek_tag
	if {$peek_tag != 0x02} {
		# Version number is optional, if missing assumed to be value of 0
		::asn::asnGetContext cert - asn_version
		::asn::asnGetInteger asn_version ret(version)
		incr ret(version)
	} else {
		set ret(version) 1
	}

	::asn::asnGetBigInteger cert ret(serial_number)
	::asn::asnGetSequence cert data_signature_algo_seq
		::asn::asnGetObjectIdentifier data_signature_algo_seq ret(data_signature_algo)
	::asn::asnGetSequence cert issuer
	::asn::asnGetSequence cert validity
		::asn::asnGetUTCTime validity ret(notBefore)
		::asn::asnGetUTCTime validity ret(notAfter)
	::asn::asnGetSequence cert subject
	::asn::asnGetSequence cert pubkeyinfo

	binary scan  $pubkeyinfo H* pubkeyinfoG
	set ret(pubkeyinfo) $pubkeyinfoG


		::asn::asnGetSequence pubkeyinfo pubkey_algoid

	binary scan  $pubkey_algoid H* pubkey_algoidG
	set ret(pubkey_algoid) $pubkey_algoidG

			::asn::asnGetObjectIdentifier pubkey_algoid ret(pubkey_algo)
		::asn::asnGetBitString pubkeyinfo pubkey

	set extensions_list [list]
	while {$cert != ""} {
		::asn::asnPeekByte cert peek_tag

		switch -- [format {0x%02x} $peek_tag] {
			"0xa1" {
				::asn::asnGetContext cert - issuerUniqID
			}
			"0xa2" {
				::asn::asnGetContext cert - subjectUniqID
			}
			"0xa3" {
				::asn::asnGetContext cert - extensions_ctx
				::asn::asnGetSequence extensions_ctx extensions
				while {$extensions != ""} {
					::asn::asnGetSequence extensions extension
						::asn::asnGetObjectIdentifier extension ext_oid

						::asn::asnPeekByte extension peek_tag
						if {$peek_tag == 0x1} {
							::asn::asnGetBoolean extension ext_critical
						} else {
							set ext_critical false
						}

						::asn::asnGetOctetString extension ext_value_seq

					set ext_oid [::pki::_oid_number_to_name $ext_oid]

					set ext_value [list $ext_critical]

					switch -- $ext_oid {
						id-ce-basicConstraints {
							::asn::asnGetSequence ext_value_seq ext_value_bin

							if {$ext_value_bin != ""} {
								::asn::asnGetBoolean ext_value_bin allowCA
							} else {
								set allowCA "false"
							}

							if {$ext_value_bin != ""} {
								::asn::asnGetInteger ext_value_bin caDepth
							} else {
								set caDepth -1
							}
						
							lappend ext_value $allowCA $caDepth
						}
						default {
							binary scan $ext_value_seq H* ext_value_seq_hex
							lappend ext_value $ext_value_seq_hex
						}
					}

					lappend extensions_list $ext_oid $ext_value
				}
			}
		}
	}
	set ret(extensions) $extensions_list

	::asn::asnGetSequence wholething signature_algo_seq
	::asn::asnGetObjectIdentifier signature_algo_seq ret(signature_algo)
	::asn::asnGetBitString wholething ret(signature)

	# Convert values from ASN.1 decoder to usable values if needed
	set ret(notBefore) [::pki::x509::_utctime_to_native $ret(notBefore)]
	set ret(notAfter) [::pki::x509::_utctime_to_native $ret(notAfter)]
	set ret(serial_number) [::math::bignum::tostr $ret(serial_number)]
	set ret(data_signature_algo) [::pki::_oid_number_to_name $ret(data_signature_algo)]
	set ret(signature_algo) [::pki::_oid_number_to_name $ret(signature_algo)]
	set ret(pubkey_algo) [::pki::_oid_number_to_name $ret(pubkey_algo)]
	set ret(issuer) [::pki::x509::_dn_to_string $issuer]
	set ret(subject) [::pki::x509::_dn_to_string $subject]
	set ret(signature) [binary format B* $ret(signature)]
	binary scan $ret(signature) H* ret(signature)

	# Handle RSA public keys by extracting N and E
#puts "PUBKEY_ALGO=$ret(pubkey_algo)"
	switch -- $ret(pubkey_algo) {
		"rsaEncryption" {
			set pubkey [binary format B* $pubkey]
			binary scan $pubkey H* ret(pubkey)

			::asn::asnGetSequence pubkey pubkey_parts
			::asn::asnGetBigInteger pubkey_parts ret(n)
			::asn::asnGetBigInteger pubkey_parts ret(e)

			set ret(n) [::math::bignum::tostr $ret(n)]
			set ret(e) [::math::bignum::tostr $ret(e)]
			set ret(l) [expr {int([::pki::_bits $ret(n)] / 8.0000 + 0.5) * 8}]
			set ret(type) rsa
		} 
		"GostR2012_256" -
		"GostR2012_512" -
		"GostR2001" -
		"1.2.643.2.2.19" -
		"1.2.643.7.1.1.1.1" -
		"1.2.643.7.1.1.1.2" {
#	gost2001, gost2012-256,gost2012-512
			set pubkey [binary format B* $pubkey]
#puts "LL=[string length $pubkey]"
if {[string length $pubkey] < 100} {
    set pubk [string range $pubkey 2 end]
} else {
    set pubk [string range $pubkey 3 end]
}
set pubkey_revert [string reverse $pubk]
binary scan $pubkey_revert H* ret(pubkey_rev)
			binary scan $pubkey H* ret(pubkey)
			set ret(type) gost
			::asn::asnGetSequence pubkey_algoid pubalgost
#OID - параметра
			::asn::asnGetObjectIdentifier pubalgost ret(paramkey)
	set ret(paramkey) [::pki::_oid_number_to_name $ret(paramkey)]
#OID - Функция хэша
			::asn::asnGetObjectIdentifier pubalgost ret(hashkey)
	set ret(hashkey) [::pki::_oid_number_to_name $ret(hashkey)]
#puts "ret(paramkey)=$ret(paramkey)n"
#puts "ret(hashkey)=$ret(hashkey)n"
		}
	}
	return [array get ret]
}

proc set_nick_for_oid {} {
#    set ::pki::oids(1.2.643.2.2.19) "gost2001pubKey"
#    set ::pki::oids(1.2.643.2.2.3)  "gost2001withGOST3411_94"
    set ::pki::oids(1.2.643.100.1)  "OGRN"
    set ::pki::oids(1.2.643.100.5)  "OGRNIP"
    set ::pki::oids(1.2.643.3.131.1.1) "INN"
    set ::pki::oids(1.2.643.100.3) "SNILS"
#Алгоритмы подписи
    set ::pki::oids(1.2.643.2.2.3) "ГОСТ Р 34.10-2001-256"
    set ::pki::oids(1.2.643.7.1.1.3.2) "ГОСТ Р 34.10-2012-256"
    set ::pki::oids(1.2.643.7.1.1.3.3) "ГОСТ Р 34.10-2012-512"
#    set ::pki::oids(1.2.643.2.2.3) "gost"
#    set ::pki::oids(1.2.643.7.1.1.3.2) "gost"
#    set ::pki::oids(1.2.643.7.1.1.3.3) "gost"
#Алгоритмы ключа
#    set ::pki::oids(1.2.643.2.2.19) "ГОСТ Р 34.10-2001"
#    set ::pki::oids(1.2.643.7.1.1.1.1) "ГОСТ Р 34.10-2012 256 байт"
#    set ::pki::oids(1.2.643.7.1.1.1.2) "ГОСТ Р 34.10-2012 512 байт"
    set ::pki::oids(1.2.643.2.2.19) "GostR2001"
    set ::pki::oids(1.2.643.7.1.1.1.1) "GostR2012_256"
    set ::pki::oids(1.2.643.7.1.1.1.2) "GostR2012_512"
#Oid-ы параметров ключа ГОСТ Р 34.10-2001 и ГОСТ Р 34.10-2012-256
    set ::pki::oids(1.2.643.2.2.35.0) "GOST2001-test"
    set ::pki::oids(1.2.643.2.2.35.1) "GOST2001-CryptoPro-A"
    set ::pki::oids(1.2.643.2.2.35.2) "GOST2001-CryptoPro-B"
    set ::pki::oids(1.2.643.2.2.35.3) "GOST2001-CryptoPro-C"
#    { "GOST2001-CryptoPro-A",     set ::pki::oids("GOST2001-CryptoPro-XchA" },
#    { "GOST2001-CryptoPro-C",     set ::pki::oids("GOST2001-CryptoPro-XchB" },
    set ::pki::oids(1.2.643.2.2.36.0) "GOST2001-CryptoPro-A"
    set ::pki::oids(1.2.643.2.2.36.1) "GOST2001-CryptoPro-C"
#Oid-ы параметров ключа ГОСТ Р 34.10-2012-512
    set ::pki::oids(1.2.643.7.1.2.1.2.1) "GOST2012-tc26-A"
    set ::pki::oids(1.2.643.7.1.2.1.2.2) "GOST2012-tc26-B"
#Nick для хэш
    set ::pki::oids(1.2.643.7.1.1.2.2) "STRIBOG256"
    set ::pki::oids(1.2.643.7.1.1.2.3) "STRIBOG512"
    set ::pki::oids(1.2.643.2.2.30.1) "GOSTR3411_CP"
}

Процедура parse_gost_cert предназначена для разбора сертификатов на базе российской криптографии.
В этом файле находится также процедура присвоения nickname-мов oid-ам российской криптографии с учетом проекта GCrypt:

proc set_nick_for_oid {} {
    set ::pki::oids(1.2.643.100.1)  "OGRN"
    set ::pki::oids(1.2.643.100.5)  "OGRNIP"
    set ::pki::oids(1.2.643.3.131.1.1) "INN"
    set ::pki::oids(1.2.643.100.3) "SNILS"
#Алгоритмы подписи
    set ::pki::oids(1.2.643.2.2.3) "ГОСТ Р 34.10-2001-256"
    set ::pki::oids(1.2.643.7.1.1.3.2) "ГОСТ Р 34.10-2012-256"
    set ::pki::oids(1.2.643.7.1.1.3.3) "ГОСТ Р 34.10-2012-512"
#Алгоритмы ключа
    set ::pki::oids(1.2.643.2.2.19) "GostR2001"
    set ::pki::oids(1.2.643.7.1.1.1.1) "GostR2012_256"
    set ::pki::oids(1.2.643.7.1.1.1.2) "GostR2012_512"
#Oid-ы параметров ключа ГОСТ Р 34.10-2001 и ГОСТ Р 34.10-2012-256
    set ::pki::oids(1.2.643.2.2.35.0) "GOST2001-test"
    set ::pki::oids(1.2.643.2.2.35.1) "GOST2001-CryptoPro-A"
    set ::pki::oids(1.2.643.2.2.35.2) "GOST2001-CryptoPro-B"
    set ::pki::oids(1.2.643.2.2.35.3) "GOST2001-CryptoPro-C"
    set ::pki::oids(1.2.643.2.2.36.0) "GOST2001-CryptoPro-A"
    set ::pki::oids(1.2.643.2.2.36.1) "GOST2001-CryptoPro-C"
#Oid-ы параметров ключа ГОСТ Р 34.10-2012-512
    set ::pki::oids(1.2.643.7.1.2.1.2.1) "GOST2012-tc26-A"
    set ::pki::oids(1.2.643.7.1.2.1.2.2) "GOST2012-tc26-B"
#Nick для хэш
    set ::pki::oids(1.2.643.7.1.1.2.2) "STRIBOG256"
    set ::pki::oids(1.2.643.7.1.1.2.3) "STRIBOG512"
    set ::pki::oids(1.2.643.2.2.30.1) "GOSTR3411_CP"
}

Процедура parse_gost_cert позволяет получить TBS-сертификат [9], подпись сертификата, тип подписи, открытый ключ. Если мы рассматриваем самоподписанный сертификат, то этого достаточно для проверки подписи. Если же мы проверяем подпись сертификата, подписанного (выпущенного) другим сертификатом (issuer и subject сертификата не совпадают), то процедура получения информации для проверки подписи выглядит следующим образом:
— из проверяемого сертификата извлекаем его TBS-сертификат, тип подписи и саму подпись:
— из корневого сертификата извлекаем открытый ключ.
Самое ответственное при подготовке исходных данных для проверки подписи сертификата, это строгое следование рекомендациям TK-26. Для значения открытого ключа они звучат так:

Представление открытого ключа GostR3410-2012-256-PublicKey идентично представлению открытого ключа ГОСТ Р 34.10-2001 [IETF RFC 4491], и ДОЛЖНО содержать 64 октета, где первые 32 октета содержат координату x в представлении little-endian, и вторые 32 октета содержат координату y в представлении little-endian.
Представление открытого ключа GostR3410-2012-512-PublicKey ДОЛЖНО содержать
128 октетов, где первые 64 октета содержат координату x в представлении little-endian, и вторые 64 октета содержат координату у в представлении little-endian

При выгрузке подписи следует руководствоваться следующим:

Алгоритм подписи ГОСТ Р 34.10-2012 с длиной хэш-кода 256 бит используется для формирования цифровой подписи в форме двух 256-битных чисел, r и s. Её представление в виде строки октетов (OCTET STRING) идентично представлению подписи ГОСТ Р 34.10-2001 [IETF RFC 4491] и состоит из 64 октетов; при этом первые 32 октета содержат число s в представлении big-endian (старший октет записывается первым), а вторые 32 октета содержат число r в представлении big-endian.
Алгоритм подписи ГОСТ Р 34.10-2012 с длиной хэш-кода 512 используется для формирования цифровой подписи в форме двух 512-битных чисел, открытые ключи согласно r и s. Её представление в виде строки октетов (OCTET STRING) состоит из 128 октетов; при этом первые 64 октета содержат число s в представлении big-endian (старший октет записывается первым), а вторые 64 октета содержат число r в представлении big-endian.

С учетом этих рекомендаций Tcl-модуль parse_certs_for_verify_load.tcl подготовки исходных данных для проверки подписи сертификата

выглядит следующим образом:


#!/usr/bin/tclsh
#загружаем пакет PKI
package require pki
#загружаем файл с процедурой разбора ГОСТ-ых сертификатов
source parse_cert_gost_oid.tcl
if {$argc != 2} {
    puts "Usage: parse_certs_for_verify_load.tcl <проверяемый сертификат> <корневой сертификат>"
    exit 1
}
#Проверяемый сертификат
if {[file exists "[lindex $argv 0]"] == 0 } {
    puts "Usage: parse_certs_for_verify_load.tcl <проверяемый сертификат> <корневой сертификат>"
    puts "Отсутствует файл [lindex $argv 0]"
    exit 1
}
#Корневой сертификат
if {[file exists "[lindex $argv 1]"] == 0 } {
    puts "Usage: parse_certs_for_verify_load.tcl <проверяемый сертификат> <корневой сертификат>"
    puts "Отсутствует файл [lindex $argv 1]"
    exit 1
}
#Устанавливаем nick-и для ГОСТ-овых oid-ов
set_nick_for_oid

set file [lindex $argv 0]
set f [open $file r]
set cert [read $f]
close $f
#READ DER-format
if { [string range $cert 0 9 ] != "-----BEGIN" } {
	set fd [open $file]
	chan configure $fd -translation binary
	set cert [read $fd]
	close $fd
}

array set cert_user [parse_cert_gost $cert]
#Читай рекомендации ТК-26
set len_sign [expr [string length $cert_user(signature)] /2]
set sign_r [string range $cert_user(signature) $len_sign end]
set sign_s [string range $cert_user(signature) 0 [expr $len_sign - 1]]

#puts "Корневой сертификат: $file"
set file [lindex $argv 1]
set f [open $file r]
set cert [read $f]
close $f
#READ DER
    if { [string range $cert 0 9 ] != "-----BEGIN" } {
	set fd [open $file]
	chan configure $fd -translation binary
	set cert [read $fd]
	close $fd
    }

#Распарсиваем в массив корневой сертификат
array set cert_ca [parse_cert_gost $cert]
#Читай рекомендации ТК-26
set len_key [expr [string length $cert_ca(pubkey_rev)]/2]
set key_pub_left [string range $cert_ca(pubkey_rev) $len_key end]
set key_pub_right [string range $cert_ca(pubkey_rev) 0 [expr $len_key - 1]]
puts "/*Делаем C-код: Исходные данные для проверки подписи сертификата*/"
#TBS-проверяемого сертификата
puts "char tbc[] = "[string toupper $cert_user(cert)]";"
#Тип хэш-функции
puts "char hash_type[] = "$cert_ca(hashkey)";"
#Открытый ключ корневого сертификата
puts "unsigned char pub_key_ca[] = "(public-key ""
puts ""(ecc ""
puts "" (curve $cert_ca(paramkey))""
puts "" (q #04[string toupper $key_pub_left$key_pub_right]#)""
puts "")""
puts "")";"
#Подпись проверяемого сертификата
puts "unsigned char sig_cert[] = "(sig-val"" 
puts ""($cert_ca(type) ""
puts "" (r #[string toupper $sign_r]#)""
puts "" (s #[string toupper $sign_s]#)""
puts "")""
puts "")";"
puts "/*Сохраните вывод в файле TEST_from_TCL.h*/"
puts "/*Оттранслируйте модуль TEST_from_Tcl.c: cc -o TEST_from_Tcl TEST_from_Tcl.c -lgcrypt и выполните модуль TEST_from_Tcl*/"

Для тестирования возьмем два реальных сертификата:

сертификат "УЦ 1 ИС ГУЦ.pem"

-----BEGIN CERTIFICATE-----
MIIGrDCCBlugAwIBAgILAOvBBVQAAAAAAFkwCAYGKoUDAgIDMIIBSjEeMBwGCSqG
SIb3DQEJARYPZGl0QG1pbnN2eWF6LnJ1MQswCQYDVQQGEwJSVTEcMBoGA1UECAwT
Nzcg0LMuINCc0L7RgdC60LLQsDEVMBMGA1UEBwwM0JzQvtGB0LrQstCwMT8wPQYD
VQQJDDYxMjUzNzUg0LMuINCc0L7RgdC60LLQsCwg0YPQuy4g0KLQstC10YDRgdC6
0LDRjywg0LQuIDcxLDAqBgNVBAoMI9Cc0LjQvdC60L7QvNGB0LLRj9C30Ywg0KDQ
vtGB0YHQuNC4MRgwFgYFKoUDZAESDTEwNDc3MDIwMjY3MDExGjAYBggqhQMDgQMB
ARIMMDA3NzEwNDc0Mzc1MUEwPwYDVQQDDDjQk9C+0LvQvtCy0L3QvtC5INGD0LTQ
vtGB0YLQvtCy0LXRgNGP0Y7RidC40Lkg0YbQtdC90YLRgDAeFw0xNjAzMTYxMjAy
NTFaFw0yNzA3MTIxMjAyNTFaMIIBITEaMBgGCCqFAwOBAwEBEgwwMDc3MTA0NzQz
NzUxGDAWBgUqhQNkARINMTA0NzcwMjAyNjcwMTEeMBwGCSqGSIb3DQEJARYPZGl0
QG1pbnN2eWF6LnJ1MTwwOgYDVQQJDDMxMjUzNzUg0LMuINCc0L7RgdC60LLQsCDR
g9C7LiDQotCy0LXRgNGB0LrQsNGPINC0LjcxLDAqBgNVBAoMI9Cc0LjQvdC60L7Q
vNGB0LLRj9C30Ywg0KDQvtGB0YHQuNC4MRUwEwYDVQQHDAzQnNC+0YHQutCy0LAx
HDAaBgNVBAgMEzc3INCzLiDQnNC+0YHQutCy0LAxCzAJBgNVBAYTAlJVMRswGQYD
VQQDDBLQo9CmIDEg0JjQoSDQk9Cj0KYwYzAcBgYqhQMCAhMwEgYHKoUDAgIjAQYH
KoUDAgIeAQNDAARAx70Y7WYQ4ODtdiSSx3MJnr1GQBEIExiPO/LWj1TRKES1OcDI
YgtdOBGVYSvbsStl10jkAOG0OpnGsd2by4m+LaOCA0MwggM/MA8GA1UdEwEB/wQF
MAMBAf8wHQYDVR0OBBYEFBGIaV7vyOlz23pXNbzSAfMF/qfRMAsGA1UdDwQEAwIB
hjCCAYsGA1UdIwSCAYIwggF+gBSLmDuJGFHo75wCeLjqyNQgslXJXaGCAVKkggFO
MIIBSjEeMBwGCSqGSIb3DQEJARYPZGl0QG1pbnN2eWF6LnJ1MQswCQYDVQQGEwJS
VTEcMBoGA1UECAwTNzcg0LMuINCc0L7RgdC60LLQsDEVMBMGA1UEBwwM0JzQvtGB
0LrQstCwMT8wPQYDVQQJDDYxMjUzNzUg0LMuINCc0L7RgdC60LLQsCwg0YPQuy4g
0KLQstC10YDRgdC60LDRjywg0LQuIDcxLDAqBgNVBAoMI9Cc0LjQvdC60L7QvNGB
0LLRj9C30Ywg0KDQvtGB0YHQuNC4MRgwFgYFKoUDZAESDTEwNDc3MDIwMjY3MDEx
GjAYBggqhQMDgQMBARIMMDA3NzEwNDc0Mzc1MUEwPwYDVQQDDDjQk9C+0LvQvtCy
0L3QvtC5INGD0LTQvtGB0YLQvtCy0LXRgNGP0Y7RidC40Lkg0YbQtdC90YLRgIIQ
NGgeQMtB7zOpoLfIdpKaKTBZBgNVHR8EUjBQMCagJKAihiBodHRwOi8vcm9zdGVs
ZWNvbS5ydS9jZHAvZ3VjLmNybDAmoCSgIoYgaHR0cDovL3JlZXN0ci1wa2kucnUv
Y2RwL2d1Yy5jcmwwJgYFKoUDZG8EHQwb0JrRgNC40L/RgtC+LdCf0YDQviBDU1Ag
My42MCUGA1UdIAQeMBwwCAYGKoUDZHEBMAgGBiqFA2RxAjAGBgRVHSAAMIHGBgUq
hQNkcASBvDCBuQwj0J/QkNCa0JwgwqvQmtGA0LjQv9GC0L7Qn9GA0L4gSFNNwrsM
INCf0JDQmiDCq9CT0L7Qu9C+0LLQvdC+0Lkg0KPQpsK7DDbQl9Cw0LrQu9GO0YfQ
tdC90LjQtSDihJYgMTQ5LzMvMi8yLTk5OSDQvtGCIDA1LjA3LjIwMTIMONCX0LDQ
utC70Y7Rh9C10L3QuNC1IOKEliAxNDkvNy8xLzQvMi02MDMg0L7RgiAwNi4wNy4y
MDEyMAgGBiqFAwICAwNBAKVYokUvb7XAMPJF38ZPKO2BFBldmGEfqsfmsiO35Y52
kTkx512H3YLqWMrOLjIfVMJhc+DTCNeXWY6bhK4/DRU=
-----END CERTIFICATE-----

и его корневой сертификат "ГУЦ Минкомсвязь.pem":

-----BEGIN CERTIFICATE-----
MIIFGTCCBMigAwIBAgIQNGgeQMtB7zOpoLfIdpKaKTAIBgYqhQMCAgMwggFKMR4w
HAYJKoZIhvcNAQkBFg9kaXRAbWluc3Z5YXoucnUxCzAJBgNVBAYTAlJVMRwwGgYD
VQQIDBM3NyDQsy4g0JzQvtGB0LrQstCwMRUwEwYDVQQHDAzQnNC+0YHQutCy0LAx
PzA9BgNVBAkMNjEyNTM3NSDQsy4g0JzQvtGB0LrQstCwLCDRg9C7LiDQotCy0LXR
gNGB0LrQsNGPLCDQtC4gNzEsMCoGA1UECgwj0JzQuNC90LrQvtC80YHQstGP0LfR
jCDQoNC+0YHRgdC40LgxGDAWBgUqhQNkARINMTA0NzcwMjAyNjcwMTEaMBgGCCqF
AwOBAwEBEgwwMDc3MTA0NzQzNzUxQTA/BgNVBAMMONCT0L7Qu9C+0LLQvdC+0Lkg
0YPQtNC+0YHRgtC+0LLQtdGA0Y/RjtGJ0LjQuSDRhtC10L3RgtGAMB4XDTEyMDcy
MDEyMzExNFoXDTI3MDcxNzEyMzExNFowggFKMR4wHAYJKoZIhvcNAQkBFg9kaXRA
bWluc3Z5YXoucnUxCzAJBgNVBAYTAlJVMRwwGgYDVQQIDBM3NyDQsy4g0JzQvtGB
0LrQstCwMRUwEwYDVQQHDAzQnNC+0YHQutCy0LAxPzA9BgNVBAkMNjEyNTM3NSDQ
sy4g0JzQvtGB0LrQstCwLCDRg9C7LiDQotCy0LXRgNGB0LrQsNGPLCDQtC4gNzEs
MCoGA1UECgwj0JzQuNC90LrQvtC80YHQstGP0LfRjCDQoNC+0YHRgdC40LgxGDAW
BgUqhQNkARINMTA0NzcwMjAyNjcwMTEaMBgGCCqFAwOBAwEBEgwwMDc3MTA0NzQz
NzUxQTA/BgNVBAMMONCT0L7Qu9C+0LLQvdC+0Lkg0YPQtNC+0YHRgtC+0LLQtdGA
0Y/RjtGJ0LjQuSDRhtC10L3RgtGAMGMwHAYGKoUDAgITMBIGByqFAwICIwEGByqF
AwICHgEDQwAEQI+lv3kQI8jWka1kMVdbvpvFioP0Pyn3Knmp+2XD6KgPWnXEIlSR
X8g/IYracDr51YsNc2KE3C7mkH6hA3M3ofujggGCMIIBfjCBxgYFKoUDZHAEgbww
gbkMI9Cf0JDQmtCcIMKr0JrRgNC40L/RgtC+0J/RgNC+IEhTTcK7DCDQn9CQ0Jog
wqvQk9C+0LvQvtCy0L3QvtC5INCj0KbCuww20JfQsNC60LvRjtGH0LXQvdC40LUg
4oSWIDE0OS8zLzIvMi05OTkg0L7RgiAwNS4wNy4yMDEyDDjQl9Cw0LrQu9GO0YfQ
tdC90LjQtSDihJYgMTQ5LzcvMS80LzItNjAzINC+0YIgMDYuMDcuMjAxMjAuBgUq
hQNkbwQlDCPQn9CQ0JrQnCDCq9Ca0YDQuNC/0YLQvtCf0YDQviBIU03CuzBDBgNV
HSAEPDA6MAgGBiqFA2RxATAIBgYqhQNkcQIwCAYGKoUDZHEDMAgGBiqFA2RxBDAI
BgYqhQNkcQUwBgYEVR0gADAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0TAQH/BAUwAwEB
/zAdBgNVHQ4EFgQUi5g7iRhR6O+cAni46sjUILJVyV0wCAYGKoUDAgIDA0EA23Re
ec/Y27rpMi+iFbgWCazGY3skBTq5ZGsQKOUxCe4mO7UBDACiWqdA0nvqiQMXeHgq
o//fO9pxuIHtymwyMg==
-----END CERTIFICATE-----

Подготовим исходные данные для проверки сертификата «УЦ 1 ИС ГУЦ.pem»:

$ ./parse_certs_for_verify_load.tcl  "УЦ 1 ИС ГУЦ.pem" "ГУЦ Минкомсвязь.pem" > TEST_from_TCL.h
$echo "Содержимое файла TEST_from_TCL.h"
$cat TEST_from_TCL.h
/*Делаем C-код: Исходные данные для проверки подписи сертификата*/
char tbc[] = "3082065B . . . ";
char hash_type[] = "GOSTR3411_CP";
unsigned char pub_key_ca[] = "(public-key "
"(ecc "
" (curve GOST2001-CryptoPro-A)"
" (q #040FA8E8C365FBA9792AF7293FF4838AC59BBE5B573164AD91D6C8231079BFA58FFBA1377303A17E90E62EDC8462730D8BD5F93A70DA8A213FC85F915422C4755A#)"
")"
")";
unsigned char sig_cert[] = "(sig-val"
"(gost "
" (r #913931E75D87DD82EA58CACE2E321F54C26173E0D308D797598E9B84AE3F0D15#)"
" (s #A558A2452F6FB5C030F245DFC64F28ED8114195D98611FAAC7E6B223B7E58E76#)"
")"
")";
/*Сохраните вывод в файле TEST_from_TCL.h*/
/*Оттранслируйте модуль TEST_from_Tcl.c: cc -o TEST_from_Tcl TEST_from_Tcl.c -lgcrypt и выполните модуль TEST_from_Tcl*/
$

Проверка подписи сертификата выполняется модулем:

TEST_from_TCL.c

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <gcrypt.h>

#include "TEST_from_TCL.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))
#define xmalloc(a)    gcry_xmalloc ((a))

static void
show_sexp (const char *prefix, gcry_sexp_t a)
{
  char *buf;
  size_t size;

  if (prefix)
    fputs (prefix, stderr);
  size = gcry_sexp_sprint (a, GCRYSEXP_FMT_ADVANCED, NULL, 0);
  buf = gcry_xmalloc (size);

  gcry_sexp_sprint (a, GCRYSEXP_FMT_ADVANCED, buf, size);
  fprintf (stderr, "%.*s", (int)size, buf);
  gcry_free (buf);
}
/* Convert STRING consisting of hex characters into its binary
   representation and return it as an allocated buffer. The valid
   length of the buffer is returned at R_LENGTH.  The string is
   delimited by end of string.  The function returns NULL on
   error.  */
static void *
hex2buffer (const char *string, size_t *r_length)
{
  const char *s;
  unsigned char *buffer;
  size_t length;

  buffer = xmalloc (strlen(string)/2+1);
  length = 0;
  for (s=string; *s; s +=2 )
    {
      if (!hexdigitp (s) || !hexdigitp (s+1))
        return NULL;           /* Invalid hex digits. */
      ((unsigned char*)buffer)[length++] = xtoi_2 (s);
    }
  *r_length = length;
  return buffer;
}

int main(int argc, char* argv[]) {
    gpg_error_t err;
    int algo;
    gcry_md_hd_t hd;
    unsigned char *tbs_ptr;
    size_t len_tbs;
    int i;
    unsigned char *h;
    gcry_sexp_t pub_key;
    gcry_sexp_t data;
    gcry_sexp_t sig;
    gcry_mpi_t x;
    int len_xy;
    unsigned char c;
/*Для тестирования архитектуры little-endian или big-endian*/
    unsigned short arch = 1; /* 0x0001 */

    tbs_ptr = hex2buffer(tbc, &len_tbs);
    if (tbs_ptr == NULL) {
      fprintf (stderr, "Bad tbsn");
	exit(1);
    }
    algo = gcry_md_map_name (hash_type);
    if (algo == GCRY_MD_NONE)
    {
      fprintf (stderr, "Unknown algorithm '%s'n", hash_type);
      exit (1);
    }

    err = gcry_md_open(&hd, algo, 0);
    if (err)
    {
      fprintf (stderr, "LibGCrypt error %s/%sn",
          gcry_strsource (err),
          gcry_strerror (err));
        exit (1);
    }
    gcry_md_write (hd, tbs_ptr, len_tbs);
    h  = gcry_md_read(hd, 0);
//    len_xy = gcry_md_get_algo_dlen (algo);
/*Проверяем архитектуру и инвертируем хэш*/
    printf("%sn", *((unsigned char *) &arch) == 0 ? "Архитектура big-endian" : "Архитекткра little-endian");
    len_xy =  *((unsigned char *) &arch) == 0 ? 0:gcry_md_get_algo_dlen (algo);
    for (i = 0; i < (len_xy/2); i++) {
            	    c = *(h + i);
            	    *(h + i) = *(h + len_xy - i - 1);
            	    *(h + len_xy - i - 1) = c;
    }

fprintf(stderr, "Хэш длина=%dn", gcry_md_get_algo_dlen (algo));
    for (i = 0; i < gcry_md_get_algo_dlen (algo); i++)
        printf("%02X", h[i]);
//    printf("n  %sn", tbc);
    fflush(stdout);
/*Сохраняем хэш */
    x = gcry_mpi_set_opaque_copy(NULL, h, gcry_md_get_algo_dlen (algo) * 8);

/*Закрываем контекст хэширования*/
    gcry_md_reset(hd);
    gcry_md_close(hd);
/*Контекст хэша*/
    err = gcry_sexp_build (&data, NULL,
                             "(data (flags gost) (value %m))", x);
    show_sexp ("ECC GOST data cert:n", data);
fprintf (stderr, "nStep 1n");
/*Контекст публичного ключа корневого сертификата*/
    err = gcry_sexp_sscan (&pub_key, NULL, pub_key_ca,
                           strlen (pub_key_ca));
    if (err){
	fprintf(stderr, "TEST_SEXP: er gcry_sexp_sscan for pub_key_can");
	exit(1);
    }
    show_sexp ("ECC GOST public key:n", pub_key);
fprintf (stderr, "Step 2n");
/*Контекст подписи проверяемого сертификата*/
    err = gcry_sexp_sscan (&sig, NULL, sig_cert, strlen (sig_cert));
    if (err){
	fprintf(stderr, "TEST_SEXP: er gcry_sexp_sscan for sig_certn");
	exit(1);
    }
    show_sexp ("ECC GOST sig cert:n", sig);
fprintf (stderr, "Step 3n");
/*Проверка подписи сертификата*/
    err = gcry_pk_verify (sig, data, pub_key);
    if (err) {
        fprintf (stderr, "TEST_SEXP: verify cert failedn");
        exit (1);
    }
    fprintf (stderr, "TEST_SEXP: verify cert OK!!n");
}

Транслируем и выполняем утилиту TEST_from_TCL:

$cc –o TEST_from_TCL TEST_from_TCL.c –lgcrypt
$./TEST_from_TCL
Архитекткра little-endian 
Хэш длина=32 
D485903E7E8D60820118329060C558B9C733D53CA608C0C79363ECE7B4C1F799ECC GOST data cert: 
(data  
(flags gost) 
(value #D485903E7E8D60820118329060C558B9C733D53CA608C0C79363ECE7B4C1F799#) 
) 
Step 1 
ECC GOST public key: 
(public-key  
(ecc  
 (curve GOST2001-CryptoPro-A) 
 (q #040FA8E8C365FBA9792AF7293FF4838AC59BBE5B573164AD91D6C8231079BFA58FFBA1377303A17E90E
62EDC8462730D8BD5F93A70DA8A213FC85F915422C4755A#) 
 ) 
) 
Step 2 
ECC GOST sig cert: 
(sig-val  
(gost  
 (r #913931E75D87DD82EA58CACE2E321F54C26173E0D308D797598E9B84AE3F0D15#) 
 (s #A558A2452F6FB5C030F245DFC64F28ED8114195D98611FAAC7E6B223B7E58E76#) 
 ) 
) 
Step 3 
TEST_SEXP: verify cert OK!! 
$

Как видим проверка подписи сертификата «УЦ 1 ИС ГУЦ.pem» прошла успешно. Осталось проверить сам корневой сертификат «ГУЦ Минкомсвязь.pem». Все просто, достаточно выполнить команду:

$ parse_certs_for_verify_load.tcl  "ГУЦ Минкомсвязь.pem"  "ГУЦ Минкомсвязь.pem" > TEST_from_TCL.h
$ и т.д.

Если кто захочет проверить сертификаты с другими ГОСТ-овыми ключами (ГОСТ Р 34.10-2012-256 или ГОСТ Р 34.10-2012-512), то может воспользоваться УЦ CAFL63 [6] и подготовить любые сертификаты:
Инфраструктура открытых ключей: библиотека GCrypt как альтернатива OpenSSL с поддержкой российской криптографии - 4 [6]
И так, проделанные изыскания показали, что библиотека GCrypt вполне может использоваться для работы с российской криптографией. Ближайшая перспектива видится в доработке библиотеки KSBA и расширении пакета pki скриптового языка Tcl (а может и Python) поддержкой российской криптографии.
Те, кто хочет использовать библиотеку GCrypt для шифрования, рекомендую заглянуть сюда [10].

Автор: saipr

Источник [11]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/python/283194

Ссылки в тексте:

[1] «2000-й год»: https://ru.wikipedia.org/wiki/%D0%9F%D1%80%D0%BE%D0%B1%D0%BB%D0%B5%D0%BC%D0%B0_2000_%D0%B3%D0%BE%D0%B4%D0%B0

[2] исключена: https://habr.com/post/325666/

[3] GCrypt: https://habr.com/post/316736/

[4] ТК 26: https://tc26.ru/

[5] S-выражения: https://ru.wikipedia.org/wiki/S-%D0%B2%D1%8B%D1%80%D0%B0%D0%B6%D0%B5%D0%BD%D0%B8%D0%B5

[6] Image: https://habr.com/post/413493/

[7] Python: https://habr.com/post/335712/

[8] Tcl : https://habr.com/post/344526/

[9] TBS-сертификат: https://habr.com/post/194664/

[10] сюда: https://habr.com/post/120639

[11] Источник: https://habr.com/post/414249/?utm_campaign=414249