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

в 4:18, , рубрики: C, c++, certificate, cms разработка, gcc, libgcrypt, open source, openssl, PKI, python, tcl, TK26, x509, ГОСТ 34.10-2012, ГОСТ 34.11-2012, криптография, Программирование, Разработка под Linux

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

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

Уже сегодня не имеет смысла получать сертификаты с подписью по ГОСТ Р 34.10-2001.
В тоже время очень много сервисов или приложений разработано на базе OpenSSL, который поддерживал работу с ГОСТ Р 34.10-2001.
Но сегодня в стандартной версии openssl отсутствует поддержка как ГОСТ Р 34.11-2012, так и ГОСТ Р 34.10-2012. Более того в версии 1.1 поддержка криптографии ГОСТ исключена из стандартной поставки («The GOST engine was out of date and therefore it has been removed.»).
Все это заставляет искать альтернативные пути для работы с сертификатами, с электронной подписью («сообщениями формата CMS») и другими объектами ИОК на базе новой российской криптографии.
Одним из таких возможных путей является использование библиотеки GCrypt. В этой библиотеки реализована поддержка новых алгоритмов ГОСТ Р 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) рекомендованы для использования две базовые точки для ключей ГОСТ Р 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-выражения (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Одними из главных объектов инфраструктуры открытых ключей (ИОК) являются сертификаты X509. Рекомендации ТК-26 по составу и структуре сертификатов изложены в документе «ТЕХНИЧЕСКАЯ СПЕЦИФИКАЦИЯ ИСПОЛЬЗОВАНИЯ АЛГОРИТМОВ ГОСТ Р 34.10, ГОСТ Р 34.11 В ПРОФИЛЕ СЕРТИФИКАТА И СПИСКЕ ОТЗЫВА СЕРТИФИКАТОВ (CRL) ИНФРАСТРУКТУРЫ ОТКРЫТЫХ КЛЮЧЕЙ X.509 (Утверждена решением заседания технического комитета по стандартизации «Криптографическая защита информации» (Протокол N13 от 24.04.2014 г.) ». Именно в соответствии с этими рекомендациями выпускают сертификаты все аккредитованные в Минкомсвязи России УЦ.
Для проверки подписи в сертификате необходимо (см. выше) получить хэш проверяемого сертификата, его подпись и открытый ключ корневого сертификата. Сразу отметим, что для самоподписанного сертификата все эти данные хранятся в одном сертификате.
Для работы с объектами ИОК/PKI (сертификаты, CMS, запросы Инфраструктура открытых ключей: библиотека GCrypt как альтернатива OpenSSL с поддержкой российской криптографии - 3
и т.п.), как правило, используется библиотека KSBA, которая на данный момент не поддерживает рекомендации ТК-26, хотя опыт такой поддержки есть. В принципе, ничто не мешает добавить поддержку рекомендаций ТК-26 в проект ksba.
На данном этапе, этапе тестирования GCrypt, для работы с объектами ИОК/PKI (сертификаты и т.п.) с россицйской криптографией удобно использовать скриптовые языки типа Python, Tcl и т.д. Был выбран скриптовый язык Tcl . На нем легко и просто написать прототип программы, который затем перенести на язык 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-сертификат, подпись сертификата, тип подписи, открытый ключ. Если мы рассматриваем самоподписанный сертификат, то этого достаточно для проверки подписи. Если же мы проверяем подпись сертификата, подписанного (выпущенного) другим сертификатом (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 и подготовить любые сертификаты:
Инфраструктура открытых ключей: библиотека GCrypt как альтернатива OpenSSL с поддержкой российской криптографии - 4
И так, проделанные изыскания показали, что библиотека GCrypt вполне может использоваться для работы с российской криптографией. Ближайшая перспектива видится в доработке библиотеки KSBA и расширении пакета pki скриптового языка Tcl (а может и Python) поддержкой российской криптографии.
Те, кто хочет использовать библиотеку GCrypt для шифрования, рекомендую заглянуть сюда.

Автор: saipr

Источник

Поделиться

* - обязательные к заполнению поля