ИОК: библиотеки GCrypt и KSBA как альтернатива OpenSSL с поддержкой российской криптографии. Продолжение

    Мы продолжаем разговор об альтернативе openssl и речь пойдет о библиотеке libksba, которая входит в состав GnuPG. Библиотека libksba предоставляет высокоуровневый интерфейс для работы с такими объектами инфраструктуры открытых ключей как сертификаты, запросы на сертификаты, электронная подпись (CMS/PKCS#7). Однако, в отличии от библиотеки GCrypt, в которой реализована поддержка российских криптографических алгоритмов, то в libksba отсутствует реализация рекомендаций ТК-26 по использованию алгоритмов ГОСТ Р 34.10-2001/2012, ГОСТ Р 34.11-94/2012 в таких объектах ИОК как сертификаты, запросы на сертификаты, объекты PKCS#7/CMS (подписанные и/или шифрованные документы и т.п).

    Начнем с oid-ов, которые могут (или должны) входить в DN (Distinguished Name – отличительное имя) атрибутов issuer (издатель) и subject (владелец) сертификата. В библиотеке libksba oid-ы прописываются в структуре oid_name_tbl[] (файл dn.c ). В структуре oid_name_tbl[] отсутствуют oid-ы ИНН, ОГРН, ОГРНИП и СНИЛС, рекомендуемые ТК-26 для квалифицированного сертификата:
    18. К дополнительным атрибутам имени, необходимость использования которых устанавливается в соответствии с Федеральным законом, относятся:

    1) OGRN (ОГРН).
    Значением атрибута OGRN является строка, состоящая из 13 цифр и представляющая ОГРН владельца квалифицированного сертификата — юридического лица. Объектный идентификатор типа атрибута OGRN имеет вид 1.2.643.100.1, тип атрибута OGRN описывается следующим образом: OGRN ::= NUMERIC STRING SIZE 13;

    2) SNILS (СНИЛС).
    Значением атрибута SNILS является строка, состоящая из 11 цифр и представляющая СНИЛС владельца квалифицированного сертификата — физического лица. Объектный идентификатор типа атрибута SNILS имеет вид 1.2.643.100.3, тип атрибута SNILS описывается следующим образом: SNILS ::= NUMERIC STRING SIZE 11;

    3) INN (ИНН).
    Значением атрибута INN является строка, состоящая из 12 цифр и представляющая ИНН владельца квалифицированного сертификата. Объектный идентификатор типа атрибута INN имеет вид 1.2.643.3.131.1.1, тип атрибута INN описывается следующим образом: INN ::= NUMERIC STRING SIZE 12.

    Естественно, необходимо добавить эти oid-ы:

    static const struct {
      const char *name;
      int source; /* 0 = unknown
                     1 = rfc2253
                     2 = David Chadwick, July 2003
                     <draft-ietf-pkix-dnstrings-02.txt>
                     3 = Peter Gutmann
                     4 = tk26
                  */
      const char *description;
      size_t      oidlen;
      const unsigned char *oid;  /* DER encoded OID.  */
      const char *oidstr;        /* OID as dotted string.  */
    } oid_name_tbl[] = {
    {"CN", 1, "CommonName",            3, "\x55\x04\x03", "2.5.4.3" },
    {"SN", 2, "Surname",               3, "\x55\x04\x04", "2.5.4.4" },
    {"SERIALNUMBER", 2, "SerialNumber",3, "\x55\x04\x05", "2.5.4.5" },
    {"C",  1, "CountryName",           3, "\x55\x04\x06", "2.5.4.6" },
    {"L" , 1, "LocalityName",          3, "\x55\x04\x07", "2.5.4.7" },
    {"ST", 1, "StateOrProvince",       3, "\x55\x04\x08", "2.5.4.8" },
    {"STREET", 1, "StreetAddress",     3, "\x55\x04\x09", "2.5.4.9" },
    {"O",  1, "OrganizationName",      3, "\x55\x04\x0a", "2.5.4.10" },
    {"OU", 1, "OrganizationalUnit",    3, "\x55\x04\x0b", "2.5.4.11" },
    {"T",  2, "Title",                 3, "\x55\x04\x0c", "2.5.4.12" },
    {"D",  3, "Description",           3, "\x55\x04\x0d", "2.5.4.13" },
    {"BC", 3, "BusinessCategory",      3, "\x55\x04\x0f", "2.5.4.15" },
    {"ADDR", 2, "PostalAddress",       3, "\x55\x04\x11", "2.5.4.16" },
    {"POSTALCODE" , 0, "PostalCode",   3, "\x55\x04\x11", "2.5.4.17" },
    {"GN", 2, "GivenName",             3, "\x55\x04\x2a", "2.5.4.42" },
    {"PSEUDO", 2, "Pseudonym",         3, "\x55\x04\x41", "2.5.4.65" },
    {"DC", 1, "domainComponent",      10,
        "\x09\x92\x26\x89\x93\xF2\x2C\x64\x01\x19", "0.9.2342.19200300.100.1.25" },
    {"UID", 1, "userid",              10,
        "\x09\x92\x26\x89\x93\xF2\x2C\x64\x01\x01", "0.9.2342.19200300.100.1.1 " },
    {"E", 1, "emailAddress",       9,
        "\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01",     "1.2.840.113549.1.9.1" },
    /*TK-26*/
    {"OGRN", 4, "OGRN", 5, "\x2a\x85\x03\x64\x01", "1.2.643.100.1" },
    {"INN", 4, "INN",  8, "\x2a\x85\x03\x03\x81\x03\x01\x01", "1.2.643.3.131.1.1" },
    {"SNILS", 4, "SNILS",  5, "\x2a\x85\x03\x64\x03", "1.2.643.100.3" },
    {"OGRNIP", 4, "OGRNIP",  5, "\x2a\x85\x03\x64\x05", "1.2.643.100.5" },
    { NULL }
    };

    Что примечательного в этой структуре, так это наличие поля source, которое указывает на то, кто ввел то или иное поле, например, 1 (единица) указывает на то, что поле определено в rfc2253, а 4 (четверка), добавленная здесь, будет указывать на то, что поле рекомендовано техническим комитетом ТК-26. В соответствии с рекомендациями ТК-26 атрибуты ИНН, ОГРН, ОГРНИП и СНИЛС имеют тип NUMERIC STRING (см. выше ). Обработка oid-ов, входящих в DN в libksba предусмотрена в функции append_atv (файл dn.c), однако в нем отсутствует обработка типа TYPE_NUMERIC_STRING, и следует добавить его:

    . . .
      switch (use_hex? 0 : node->type)
        {
        case TYPE_UTF8_STRING:
          append_utf8_value (image+node->off+node->nhdr, node->len, sb);
          break;
    /*Добавляем обработку типа NUMERIC STRING*/
        case TYPE_NUMERIC_STRING:
    
        case TYPE_PRINTABLE_STRING:
    ….
    

    Патч для файла dn.c находится здесь:
    --- dn_ORIG.c   2016-08-22 11:40:58.000000000 +0300
    +++ dn.c        2018-06-26 19:23:38.068492230 +0300
    @@ -48,6 +48,7 @@
                      2 = David Chadwick, July 2003
                      <draft-ietf-pkix-dnstrings-02.txt>
                      3 = Peter Gutmann
    +                 4 = tk26
                   */
       const char *description;
       size_t      oidlen;
    @@ -74,12 +75,17 @@
         "\x09\x92\x26\x89\x93\xF2\x2C\x64\x01\x19", "0.9.2342.19200300.100.1.25" },
     {"UID", 1, "userid",              10,
         "\x09\x92\x26\x89\x93\xF2\x2C\x64\x01\x01", "0.9.2342.19200300.100.1.1 " },
    -{"EMAIL", 3, "emailAddress",       9,
    +{"E", 1, "emailAddress",       9,
         "\x2A\x86\x48\x86\xF7\x0D\x01\x09\x01",     "1.2.840.113549.1.9.1" },
    +/*oid-ы квалифицированного сертификата от TK-26*/
    +{"OGRN", 4, "OGRN",             5, "\x2a\x85\x03\x64\x01", "1.2.643.100.1" },
    +{"INN", 4, "INN",             8, "\x2a\x85\x03\x03\x81\x03\x01\x01", "1.2.643.3.131.1.1" },
    +{"SNILS", 4, "SNILS",             5, "\x2a\x85\x03\x64\x03", "1.2.643.100.3" },
    +{"OGRNIP", 4, "OGRNIP",             5, "\x2a\x85\x03\x64\x05", "1.2.643.100.5" },
    +
     { NULL }
     };
     
    -
     #define N 0x00
     #define P 0x01
     static unsigned char charclasses[128] = {
    @@ -555,8 +561,8 @@
       name = NULL;
       for (i=0; oid_name_tbl[i].name; i++)
         {
    -      if (oid_name_tbl[i].source == 1
    -          && node->len == oid_name_tbl[i].oidlen
    +/*Все oid-ы из DN переводим в текстовую форму*/
    +      if (node->len == oid_name_tbl[i].oidlen
               && !memcmp (image+node->off+node->nhdr,
                           oid_name_tbl[i].oid, node->len))
             {
    @@ -604,6 +610,9 @@
         case TYPE_UTF8_STRING:
           append_utf8_value (image+node->off+node->nhdr, node->len, sb);
           break;
    +/*Добавляем обработку NUMERIC_STRING*/
    +    case TYPE_NUMERIC_STRING:
    +
         case TYPE_PRINTABLE_STRING:
         case TYPE_IA5_STRING:
           /* we assume that wrong encodings are latin-1 */


    Сохраните его в файле diff_dn.patch.
    Библиотеку libksba можно скачать здесь. Распакуйте архив, войдите в каталог src и примените patch diff_dn.patch к файлу dn.c:

    $patch dn.c < diff_dn.patch
    $

    Помимо oid-ов атрибутов DN, необходимо также добавить oid-ы ГОСТ-овых ключей в структуру struct pk_algo_table[] (файл keyihfo.c):
    
    static const struct algo_table_s pk_algo_table[] = {
      { /* iso.member-body.us.rsadsi.pkcs.pkcs-1.1 */
        "1.2.840.113549.1.1.1", /* rsaEncryption (RSAES-PKCA1-v1.5) */
        "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01", 9,
        1, PKALGO_RSA, "rsa", "-ne", "\x30\x02\x02" },
    . . .
    /*Добавленные oid-ы ГОСТ-вых ключей*/
      { /* GOST3410-2001 */
        "1.2.643.2.2.19", /*  gostPublicKey-2001 */
        "\x2a\x85\x03\x02\x02\x13", 6,
        1, PKALGO_ECC, "ecc", "q", "\x80" },
      { /* GOST3410-2012-256 */
        "1.2.643.7.1.1.1.1", /*  gostPublicKey-2012-256 */
        "\x2a\x85\x03\x07\x01\x01\x01\x01", 8,
        1, PKALGO_ECC, "ecc", "q", "\x80" },
      { /* GOST3410-2012-512 */
        "1.2.643.7.1.1.1.2", /*  gostPublicKey-2012-512 */
        "\x2a\x85\x03\x07\x01\x01\x01\x02", 8,
        1, PKALGO_ECC, "ecc", "q", "\x80" },
    
      {NULL}
    };

    Аналогичным образом в структуру sig_algo_table[] (файл keyihfo.c) добавляем oid-ы, связанные с электронной подписью по ГОСТ:

    static const struct algo_table_s sig_algo_table[] = {
      {  /* iso.member-body.us.rsadsi.pkcs.pkcs-1.5 */
        "1.2.840.113549.1.1.5", /* sha1WithRSAEncryption */
        "\x2A\x86\x48\x86\xF7\x0D\x01\x01\x05", 9,
        1, PKALGO_RSA, "rsa", "s", "\x82", NULL, NULL, "sha1" },
    . . .
    /*oid-ы, связанные с ГОСТ-овой подписью*/
      { /* GOST3410-2001 */
        "1.2.643.2.2.19", /*  gostPublicKey-2001 */
        "\x2a\x85\x03\x02\x02\x13", 6,
        1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "gostr3411_CP" },
      { /* GOST3410-2012-256 */
        "1.2.643.7.1.1.1.1", /*  gostPublicKey-2012-256 */
        "\x2a\x85\x03\x07\x01\x01\x01\x01", 8,
        1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "stribog256"},
      { /* GOST3410-2012-512 */
        "1.2.643.7.1.1.1.2", /*  gostPublicKey-2012-512 */
        "\x2a\x85\x03\x07\x01\x01\x01\x02", 8,
        1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "stribog512"},
    
      { /* GOST3411-2012-256 */
        "1.2.643.7.1.1.3.2", /*  STRIBOG256 */
        "\x2a\x85\x03\x07\x01\x01\x03\x02", 8,
        1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "stribog256" },
      { /* GOST3411-2012-512 */
        "1.2.643.7.1.1.3.3", /*  STRIBOG512 */
        "\x2a\x85\x03\x07\x01\x01\x03\x03", 8,
        1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "stribog512" },
      { /* GOST3410-2001-Signature */
        "1.2.643.2.2.3", /*  gosrPublicKey-2001 avec signature */
        "\x2a\x85\x03\x02\x02\x03", 6,
        1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "gostr3411_CP" },
    
      {NULL}
    };

    И, наконец, дополним структуру enc_algo_table[] oid-ами ГОСТ-ключей, которые могут участвовать в асимметричном шифровании (PKCS#7):

    static const struct algo_table_s enc_algo_table[] = {
      { /* iso.member-body.us.rsadsi.pkcs.pkcs-1.1 */
        "1.2.840.113549.1.1.1", /* rsaEncryption (RSAES-PKCA1-v1.5) */
        "\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01", 9,
        1, PKALGO_RSA, "rsa", "a", "\x82" },
      {
        "1.2.643.2.2.19", /*GOST R34.10-2001 */
        "\x2A\x85\x03\x02\x02\x13", 6,
        1, PKALGO_ECC, "ecc", "a", "\x80" },
      {
        "1.2.643.7.1.1.1.1", /*GOST R34.10-2012-256 */
        "\x2A\x85\x03\x07\x01\x01\x01\x01", 8,
        1, PKALGO_ECC, "ecc", "a", "\x80" },
      {
        "1.2.643.7.1.1.1.2", /*GOST R34.10-2012-512 */
        "\x2A\x85\x03\x07\x01\x01\x01\x02", 8,
        1, PKALGO_ECC, "ecc", "a", "\x80" },
    
      {NULL}
    };

    Теперь, когда с oid-ами определились, перейдем к структуре хранения открытого ключа в сертификате. В общем случае структура SubjectPublicKeyInfo в сертификате имеет следующий вид:
    SubjectPublicKeyInfo ::= SEQUENCE
    {
    	algorithm 	OBJECT IDENTIFIER
    	GostR3410-2001/2012-PublicKeyParameters ::= SEQUENCE {
    publicKeyParamSet OBJECT IDENTIFIER,
    digestParamSet OBJECT IDENTIFIER,
    encryptionParamSet OBJECT IDENTIFIER 
    }
    	subjectKey	BIT STRING
    }
    Параметр publicKeyParamSet задает oid точки эллиптической кривой. Oid-ы допускаемых точек прописаны в структуре struct curve_aliases[] в файле ecc_curves.c библиотеки libgcrypt. В библиотеке libksba эти oid-ы прописаны в структуре struct curve_names[] в файле keyinfo.c. В этой структуре опущены oid-ы «1.2.643.2.2.36.0» (GOST2001-CryptoPro-XchA) и «1.2.643.2.2.36.1» (GOST2001-CryptoPro-XchB). Добавим эти oid-ы, но с учетом того, что oid «1.2.643.2.2.36.0» это фактически точка с oid-ом «1.2.643.2.2.35.1», а «1.2.643.2.2.36.1» соответствует oid-у «1.2.643.2.2.35.3»:

    static const struct
    {
      const char *oid;
      const char *name;
    } curve_names[] =
      {
        { "1.3.6.1.4.1.3029.1.5.1", "Curve25519" },
    . . . 
        { "1.2.643.2.2.35.3",    "GOST2001-CryptoPro-C" },
    /*Добавленные oid-ы*/
    //    "GOST2001-CryptoPro-XchA" 
        { "1.2.643.2.2.36.0", "GOST2001-CryptoPro-A" }, 
    //    "GOST2001-CryptoPro-XchB" 
        { "1.2.643.2.2.36.1", "GOST2001-CryptoPro-C" },
    . . .
      }

    Параметр encryptionParamSet, как правило, опускают. Параметр digestParamSet тоже можно опускать: тип ГОСТ-ого ключа однозначно определяет oid digestParamSet.

    Код, занимающийся извлечением из сертификата ГОСТ-ового открытого ключа, его разбором и упаковкой в S-переменную, добавлен в функцию _ksba_keyinfo_to_sexp (файл keyinfo.c), а код, ответственный за упаковку ГОСТ-овой подписи в S-переменную, добавлен в функцию crypt_val_to_sexp. Код снабжен комментариями и дополнительных пояснений не требует. Об особенностях хранения открытого ключа и подписи в сертификате мы уже говорили в предыдущей статье.

    Патч для файла keyinfo.c находится здесь :
    --- keyinfo_ORIG.c	2015-10-28 13:41:48.000000000 +0300
    +++ keyinfo.c	2018-06-29 10:22:38.312284306 +0300
    @@ -45,7 +45,6 @@
     #include "convert.h"
     #include "ber-help.h"
     
    -
     /* Constants used for the public key algorithms.  */
     typedef enum
       {
    @@ -98,6 +97,19 @@
         "1.2.840.10045.2.1", /*  ecPublicKey */
         "\x2a\x86\x48\xce\x3d\x02\x01", 7,
         1, PKALGO_ECC, "ecc", "q", "\x80" },
    +/*oid-ы ГОСТ-овых ключей*/
    +  { /* GOST3410-2001 */
    +    "1.2.643.2.2.19", /*  gostPublicKey-2001 */
    +    "\x2a\x85\x03\x02\x02\x13", 6,
    +    1, PKALGO_ECC, "ecc", "q", "\x80" },
    +  { /* GOST3410-2012-256 */
    +    "1.2.643.7.1.1.1.1", /*  gostPublicKey-2012-256 */
    +    "\x2a\x85\x03\x07\x01\x01\x01\x01", 8,
    +    1, PKALGO_ECC, "ecc", "q", "\x80" },
    +  { /* GOST3410-2012-512 */
    +    "1.2.643.7.1.1.1.2", /*  gostPublicKey-2012-512 */
    +    "\x2a\x85\x03\x07\x01\x01\x01\x02", 8,
    +    1, PKALGO_ECC, "ecc", "q", "\x80" },
     
       {NULL}
     };
    @@ -209,6 +221,32 @@
         "1.3.36.3.4.3.2.2",     /* sigS_ISO9796-2rndWithrsa_ripemd160 */
         "\x2B\x24\x03\x04\x03\x02\x02", 7,
         0, PKALGO_RSA, "rsa", "s", "\x82", NULL, NULL, "rmd160" },
    +/*oid-ы, связанные с ГОСТ-овой подписью*/
    +  { /* GOST3410-2001 */
    +    "1.2.643.2.2.19", /*  gostPublicKey-2001 */
    +    "\x2a\x85\x03\x02\x02\x13", 6,
    +    1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "gostr3411_94" },
    +  { /* GOST3410-2012-256 */
    +    "1.2.643.7.1.1.1.1", /*  gostPublicKey-2012-256 */
    +    "\x2a\x85\x03\x07\x01\x01\x01\x01", 8,
    +    1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "stribog256"},
    +  { /* GOST3410-2012-512 */
    +    "1.2.643.7.1.1.1.2", /*  gostPublicKey-2012-512 */
    +    "\x2a\x85\x03\x07\x01\x01\x01\x02", 8,
    +    1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "stribog512"},
    +
    +  { /* GOST3411-2012-256 */
    +    "1.2.643.7.1.1.3.2", /*  STRIBOG256 */
    +    "\x2a\x85\x03\x07\x01\x01\x03\x02", 8,
    +    1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "stribog256" },
    +  { /* GOST3411-2012-512 */
    +    "1.2.643.7.1.1.3.3", /*  STRIBOG512 */
    +    "\x2a\x85\x03\x07\x01\x01\x03\x03", 8,
    +    1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "stribog512" },
    +  { /* GOST3410-2001-Signature */
    +    "1.2.643.2.2.3", /*  gosrPublicKey-2001 avec signature */
    +    "\x2a\x85\x03\x02\x02\x03", 6,
    +    1, PKALGO_ECC, "gost", "s", "\x80", NULL, NULL, "gostr3411_94" },
     
       {NULL}
     };
    @@ -218,6 +256,20 @@
         "1.2.840.113549.1.1.1", /* rsaEncryption (RSAES-PKCA1-v1.5) */
         "\x2A\x86\x48\x86\xF7\x0D\x01\x01\x01", 9,
         1, PKALGO_RSA, "rsa", "a", "\x82" },
    +/*oid-ы ГОСТ-ых ключей для ассиметричного шифрования*/
    +  {
    +    "1.2.643.2.2.19", /*GOST R34.10-2001 */
    +    "\x2A\x85\x03\x02\x02\x13", 6,
    +    1, PKALGO_ECC, "ecc", "a", "\x80" },
    +  {
    +    "1.2.643.7.1.1.1.1", /*GOST R34.10-2012-256 */
    +    "\x2A\x85\x03\x07\x01\x01\x01\x01", 8,
    +    1, PKALGO_ECC, "ecc", "a", "\x80" },
    +  {
    +    "1.2.643.7.1.1.1.2", /*GOST R34.10-2012-512 */
    +    "\x2A\x85\x03\x07\x01\x01\x01\x02", 8,
    +    1, PKALGO_ECC, "ecc", "a", "\x80" },
    +
       {NULL}
     };
     
    @@ -267,6 +319,13 @@
         { "1.2.643.2.2.35.1",    "GOST2001-CryptoPro-A" },
         { "1.2.643.2.2.35.2",    "GOST2001-CryptoPro-B" },
         { "1.2.643.2.2.35.3",    "GOST2001-CryptoPro-C" },
    +/*дополнительные oid-ы точек эллиптической кривой для ГОСТ Р 34.10-2001/2012*/
    +//    "GOST2001-CryptoPro-XchA" 
    +    { "1.2.643.2.2.36.0", "GOST2001-CryptoPro-A" }, 
    +//    "GOST2001-CryptoPro-XchB" 
    +    { "1.2.643.2.2.36.1", "GOST2001-CryptoPro-C" }, 
    +
    +
         { "1.2.643.7.1.2.1.2.1", "GOST2012-tc26-A"      },
         { "1.2.643.7.1.2.1.2.2", "GOST2012-tc26-B"      },
     
    @@ -393,7 +452,8 @@
       /* get the object identifier */
       if (!derlen)
         return gpg_error (GPG_ERR_INV_KEYINFO);
    -  c = *der++; derlen--;
    +  c = *der++;
    +   derlen--;
       if ( c != 0x06 )
         return gpg_error (GPG_ERR_UNEXPECTED_TAG); /* not an OBJECT IDENTIFIER */
       TLV_LENGTH(der);
    @@ -418,6 +478,7 @@
           if (!derlen)
             return gpg_error (GPG_ERR_INV_KEYINFO);
           c = *der++; derlen--;
    +
           if ( c == 0x05 )
             {
               /*printf ("parameter: NULL \n"); the usual case */
    @@ -471,6 +532,7 @@
           else
             {
     /*            printf ("parameter: with tag %02x - ignored\n", c); */
    +
               TLV_LENGTH(der);
               seqlen -= der - startparm;
               /* skip the value */
    @@ -692,6 +754,9 @@
       const unsigned char *ctrl;
       const char *elem;
       struct stringbuf sb;
    +/*XXXXX*/
    +  int gost_key;
    +  char *parm_oid_hash = NULL;
     
       *r_string = NULL;
     
    @@ -701,6 +766,7 @@
       c = *der++; derlen--;
       if ( c != 0x30 )
         return gpg_error (GPG_ERR_UNEXPECTED_TAG); /* not a SEQUENCE */
    +
       TLV_LENGTH(der);
       /* and now the inner part */
       err = get_algorithm (1, der, derlen, &nread, &off, &len, &is_bitstr,
    @@ -715,13 +781,36 @@
                && !memcmp (der+off, pk_algo_table[algoidx].oid, len))
             break;
         }
    +
       if (!pk_algo_table[algoidx].oid)
         return gpg_error (GPG_ERR_UNKNOWN_ALGORITHM);
       if (!pk_algo_table[algoidx].supported)
         return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
    +/*Определяем тип ключа 1 - ГОСТ-овый ключ*/
    +  gost_key = !memcmp(pk_algo_table[algoidx].oidstring, "1.2.643", 7);
     
       if (parm_off && parm_len && parm_type == TYPE_OBJECT_ID)
         parm_oid = ksba_oid_to_str (der+parm_off, parm_len);
    +  else 
    +/*Извлекаем параметры ГОСТ-ового ключа*/
    +  if (parm_off && parm_len && parm_type == TYPE_SEQUENCE && gost_key && (*(der+parm_off + off - 2) == TYPE_OBJECT_ID)){
    +/*Вытаскиваем oid curve для ГОСТ-ключа*/
    +    int len_hash;
    +    int len_curve;
    +    unsigned char* addr_hash;
    +    unsigned char* addr_curve;
    +    len_curve = (int) *(der+parm_off + off -1);
    +    addr_curve = der+parm_off + off;
    +    parm_oid = ksba_oid_to_str (addr_curve, len_curve);
    +/*Вытаскиваем oid хэша для ГОСТ-ключа*/
    +    if( *(addr_curve + len_curve)== TYPE_OBJECT_ID) {
    +	len_hash = (unsigned int) *(der+parm_off + off + len_curve + 1);
    +	addr_hash = addr_curve + len_curve + 2;
    +	parm_oid_hash = ksba_oid_to_str (addr_hash, len_hash);
    +    }
    +/*Вытаскиваем oid алгоритма шифрования для ГОСТ-ключа*/
    +  }
    +
       else if (parm_off && parm_len)
         {
           parmder = der + parm_off;
    @@ -762,6 +851,13 @@
           put_stringbuf_sexp (&sb, "curve");
           put_stringbuf_sexp (&sb, parm_oid);
           put_stringbuf (&sb, ")");
    +/*Устанавливаем oid-хэша для ГОСТ-ового ключа*/
    +      if(gost_key && parm_oid_hash) {
    +        put_stringbuf (&sb, "(");
    +	put_stringbuf_sexp (&sb, "hash");
    +        put_stringbuf_sexp (&sb, parm_oid_hash);
    +        put_stringbuf (&sb, ")");
    +      }
         }
     
       /* If parameters are given and we have a description for them, parse
    @@ -851,6 +947,43 @@
               put_stringbuf (&sb, "(");
               tmp[0] = *elem; tmp[1] = 0;
               put_stringbuf_sexp (&sb, tmp);
    +/*Извлечение значения открытого ключа в соответствии с рекомендациями TK-26*/
    +          if(gost_key){
    +            unsigned char pk[129];
    +            unsigned char *x;
    +            unsigned char *y;
    +            int len_pk;
    +            int len_xy;
    +	    int i;
    +	    unsigned char c_inv;
    +	    int offset;
    +            pk[0] = 0x04;
    +            if(len == 131 || len == 66){
    +        	offset = 0;
    +    		if(der[0] == 0x04 && der[1] & 0x80)
    +    		    offset = 3;
    +    		else if(der[0] == 0x04 && der[1] & 0x40)
    +    		    offset = 2;
    +		len_pk = len - offset;
    +		memcpy(&pk[1], der + offset, len_pk);
    +		x = &pk[1];
    +		len_xy = len_pk / 2;
    +		y = x + len_xy;
    +/*REVERT-INVERTIROVANIE*/
    +        	for (i = 0; i < (len_xy/2); i++) {
    +            	    c_inv = *(x + i);
    +            	    *(x + i) = *(x + len_xy - i - 1);
    +            	    *(x + len_xy - i - 1) = c_inv;
    +        	}
    +        	for (i = 0; i < (len_xy/2); i++) {
    +            	    c_inv = y[i];
    +            	    y[i] = y[len_xy - i -1];
    +            	    y[len_xy - i - 1] = c_inv;
    +        	}
    +        	put_stringbuf_mem_sexp (&sb, pk , len_pk + 1);
    +	    }
    +          } else
    +
               put_stringbuf_mem_sexp (&sb, der, len);
               der += len;
               derlen -= len;
    @@ -1606,6 +1739,8 @@
       const unsigned char *ctrl;
       const char *elem;
       struct stringbuf sb;
    +/*XXXXX*/
    +  int gost_sign;
     
       /* FIXME: The entire function is very similar to keyinfo_to_sexp */
       *r_string = NULL;
    @@ -1615,7 +1750,6 @@
       else
         algo_table = enc_algo_table;
     
    -
       err = get_algorithm (1, der, derlen, &nread, &off, &len, &is_bitstr,
                            NULL, NULL, NULL);
       if (err)
    @@ -1628,11 +1762,16 @@
                && !memcmp (der+off, algo_table[algoidx].oid, len))
             break;
         }
    +
       if (!algo_table[algoidx].oid)
         return gpg_error (GPG_ERR_UNKNOWN_ALGORITHM);
    +
       if (!algo_table[algoidx].supported)
         return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
     
    +/*Определяем тип подписи по oid-у*/
    +  gost_sign = !memcmp(algo_table[algoidx].oidstring, "1.2.643", 7);
    +
       der += nread;
       derlen -= nread;
     
    @@ -1682,8 +1821,22 @@
     
               put_stringbuf (&sb, "(");
               tmp[0] = *elem; tmp[1] = 0;
    +/*Если ЭП по ГОСТ, то r находится справа, а s находится слева */
    +	  if(gost_sign == 1 && algo_table == sig_algo_table){
    +	    put_stringbuf_sexp (&sb, "r");
    +	    put_stringbuf_mem_sexp (&sb, der+(len/2), len/2);
    +    	    put_stringbuf (&sb, ")");
    +    	    put_stringbuf (&sb, "(");
    +	    put_stringbuf_sexp (&sb, "s");
    +	    put_stringbuf_mem_sexp (&sb, der, len/2);
    +	  }
    +	  else{
    +	  
               put_stringbuf_sexp (&sb, tmp);
               put_stringbuf_mem_sexp (&sb, der, len);
    +/*Закрывающая скобка*/
    +	  }
    +
               der += len;
               derlen -= len;
               put_stringbuf (&sb, ")");
    


    Сохраните его в файле diff_keyinfo.patch и примените к файлу keyinfo.c:

    $patch keyinfo.c < diff_keyinfo.patch
    $

    После добавления этих патчей можно собирать библиотеку. Если собранную библиотеку libksba.so.8.11.6 вы не собираетесь ставить в систему, а будете ее использовать только для тестирования, то удобно ее скопировать в каталог, в котором будут собираться тестовые примеры. Этих патчей достаточно, чтобы разобрать сертификаты с ГОСТ-выми ключами, а также подписанные документы ГОСТ-овыми ключами в формате PKCS#7, и извлечь и проверить математическую достоверность электронной подписи сертификата X509 или электронную подпись документа PKCS#7/CMS.

    Для тестирования проделанного рассмотрим два программных модуля. Первый модуль check_cert проверяет правильность подписи сертификата, а второй модуль check_cms_signed проверяет целостность подписанного документа (CMS/PKCS#7) и корректность подписи документа. Напомним, что валидность сертификата отпределяется не только корректность его подписи, но и его действительностью (неотозванностью) на проверяемый момент времени. Точно также, валидность подписи под документом определяется не только математической достоверностью подписи, но и валидностью сертификата подписанта.

    imageДля создания подписанного документа в формате PKCS#7 можно использовать утилиту guinss.exe, написанную на Python с Tkinter, а в качестве средств криптографической защиты информации (СКЗИ) использующую токены PKCS#11 с поддержкой россицйскй криптографии:



    Отметим также, что рассматриваемый формат PKCS#7 электронной подписи документа подразумевает включение в подпись сертификата подписанта. Это сделано, чтобы пример не стал необъятным.

    Исходный код утилиты проверки подписи сертификата check_cert.c приведен здесь:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <assert.h>
    #include <errno.h>
    
    #include <ctype.h>
    #include <gcrypt.h>
    #include <ksba.h>
    /* Hash function used with libksba. */
    #define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write)
    
    /*Функция распечатки 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);
    }
    
    /* Функция проверки подписи */
    static gpg_error_t
    check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert)
    {
      gpg_error_t err;
      const char *algoid;
      gcry_md_hd_t md;
      int i, algo;
      ksba_sexp_t p;
      size_t n;
      /* Исходные данные для проверки подписи:
         S_PKEY    - S-переменная с открытым ключом.
         S_SIG     - S-переменная с подписью
         S_HASH    - S-переменная с вычисленным хэшом от tbs-сертификата
       */
      gcry_sexp_t s_sig, s_hash, s_pkey;
      const char *s;
      char algo_name[16+1]; /* hash algorithm name converted to lower case. */
      int digestlen;
      unsigned char *digest;
      int gost_key;
    
      /* Hash the target certificate using the algorithm from that certificate.  */
      algoid = ksba_cert_get_digest_algo (cert);
      algo = gcry_md_map_name (algoid);
      if (!algo)
        {
          fprintf(stderr, "unknown hash algorithm `%s'\n", algoid? algoid:"?");
          return (-1);	
        }
    /*Определяем тип ключа*/
      gost_key = !memcmp(algoid, "1.2.643", 7);
    
      s = gcry_md_algo_name (algo);
      for (i=0; *s && i < sizeof algo_name - 1; s++, i++)
        algo_name[i] = tolower (*s);
      algo_name[i] = 0;   
    
      err = gcry_md_open (&md, algo, 0);
      if (err)
        {
          fprintf(stderr, "md_open failed: %s\n", algoid);
          return err;
        }
    
      err = ksba_cert_hash (cert, 1, HASH_FNC, md);
      if (err)
        {
          fprintf(stderr, "ksba_cert_hash failed: %s\n", algoid);
          gcry_md_close (md);
          return err;
        }
      gcry_md_final (md);
    
      /* Извлекаем значение подписи из сертификата*/
      p = ksba_cert_get_sig_val (cert);
    
      /* Извлекаем длину подписи*/
      n = gcry_sexp_canon_len (p, 0, NULL, NULL);
      if (!n)
        {
          gcry_md_close (md);
          ksba_free (p);
          return (-1);
        }
      /*Устанавливаем подпись в S-переменную библиотеки libgcrypt*/
      err = gcry_sexp_sscan ( &s_sig, NULL, p, n);
      ksba_free (p);
      if (err)
        {
          fprintf(stderr, "gcry_sexp_scan failed: %s\n", "Beda");
          gcry_md_close (md);
          return err;
        }
        /*Печать S-переменной подписи*/
      show_sexp ("Sig value:\n", s_sig);
    
      /* Читаем открытый ключ из сертификата */
      p = ksba_cert_get_public_key (issuer_cert);
      n = gcry_sexp_canon_len (p, 0, NULL, NULL);
      if (!n)
        {
          fprintf(stderr, "libksba did not return a proper S-Exp\n");
          gcry_md_close (md);
          ksba_free (p);
          gcry_sexp_release (s_sig);
          return (-1);
        }
      err = gcry_sexp_sscan ( &s_pkey, NULL, p, n);
      ksba_free (p);
      if (err)
        {
          fprintf(stderr, "gcry_sexp_scan failed: %s\n", "pubkey");
          gcry_md_close (md);
          gcry_sexp_release (s_sig);
          return err;
        }
        /*Печать S-переменной с открытым ключом*/
        show_sexp ("s_pkey:\n", s_pkey);
      digestlen = gcry_md_get_algo_dlen (algo);
      digest = gcry_md_read (md, algo);
    
        if (gost_key){
    	unsigned char *h;
    	unsigned char c;
    	int len_xy;
    /*Для тестирования архитектуры littlt-endian или big-endian*/
    	unsigned short arch = 1; /* 0x0001 */
    	h = digest;
    	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;
    	}
        }
    
         switch (gost_key) {
    	    case 0: 
    		if ( gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))",
                           algo_name, (int)digestlen, digest) ) {
    		    exit (1);
    		}
    		break;
    	    case 1: 
    		if ( gcry_sexp_build (&s_hash, NULL, "(data(flags gost)(value %b))",
                            (int)digestlen, digest) ) {
    		    exit (1);
    		}
    		break;
    	    default:
    		    exit (1);
        }
    
        /*Печать S-переменной с хэшом tbs-сертификата*/
        show_sexp ("s_hash:\n", s_hash);
        /*Проверка подписи*/
      err = gcry_pk_verify (s_sig, s_hash, s_pkey);
    /*Освобождаем память*/
      gcry_md_close (md);
      gcry_sexp_release (s_sig);
      gcry_sexp_release (s_hash);
      gcry_sexp_release (s_pkey);
      return err;
    }
    
    int main (int argc, unsigned char *argv[]) {
      FILE *fp;
      ksba_reader_t r;
      ksba_cert_t cert;
      FILE *fp_ca;
      ksba_reader_t r_ca;
      ksba_cert_t cert_ca;
      gpg_error_t err;
      unsigned char *sub_dn;
    
        if(argc != 3) {
    	fprintf(stderr, "Usage: check_cert <Проверяемый сертификат> <Корневой сертификат>\n");
    	exit(1);
        }
      fp = fopen (argv[1], "rb");
      if (!fp)
        {
          fprintf (stderr, "check_cert: can't open `%s'\n", argv[1]);
          exit (1);
        }
    
      err = ksba_reader_new (&r);
      if (err) {
    	fprintf(stderr, "ksba_reader_new error\n");
    	exit(1);
      }
      err = ksba_reader_set_file (r, fp);
      if (err) {
    	fprintf(stderr, "ksba_reader_set error\n");
    	exit(1);
      }
    
      err = ksba_cert_new (&cert);
      if (err){
    	fprintf(stderr, "ksba_cert_new error\n");
    	exit(1);
      }
    
      err = ksba_cert_read_der (cert, r);
      if (err){
    	fprintf(stderr, "ksba_cert_read_der error\n");
    	exit(1);
      }
      fp_ca = fopen (argv[2], "rb");
      if (!fp_ca)
        {
          fprintf (stderr, "check_cert: can't open `%s'\n", argv[2]);
          exit (1);
        }
    
      err = ksba_reader_new (&r_ca);
      if (err) {
    	fprintf(stderr, "ksba_reader_new error\n");
    	exit(1);
      }
      err = ksba_reader_set_file (r_ca, fp_ca);
      if (err) {
    	fprintf(stderr, "ksba_reader_set error\n");
    	exit(1);
      }
    
      err = ksba_cert_new (&cert_ca);
      if (err){
    	fprintf(stderr, "ksba_cert_new error\n");
    	exit(1);
      }
    
      err = ksba_cert_read_der (cert_ca, r_ca);
      if (err){
    	fprintf(stderr, "ksba_cert_read_der error\n");
    	exit(1);
      }
    
        sub_dn = ksba_cert_get_subject (cert, 0);
        fprintf(stderr, "check_cert: Verify %s\n", sub_dn);
        
        err = check_cert_sig (cert_ca, cert);
      if (err) {
        fprintf(stderr, "check_cert: verify %s error\n", argv[1]);
      } else {
        fprintf(stderr, "check_cert: verify %s Ok\n", argv[1]);
      }
    
    }


    Сохраняем код в файле check_cert.c, транслируем его и запускаем:

    bash-4.3$ln –s libksba.so.8 lgcrypt  libksba.so.8.11.6  
     bash-4.3$ cc -o check_cert check_cert.c -lgcrypt  libksba.so.8.11.6  
    bash-4.3$ ./check_cert  
    Usage: check_cert <Проверяемый сертификат> <Корневой сертификат> 
    bash-4.3$

    Итак, чтобы проверить сертификат, необходимо иметь сам сертификат и сертификат УЦ (например, можно взять из предыдущей статьи), на котором он был выпущен. Сертификаты должны быть (пока) в DER-кодировке:

    bash-4.3$ ./check_cert  CERT_CMS_KSBA.der  CAcert_NEWCA.der 
    check_cert: Verify SNILS=22222222222,INN=123456789012,O=CMS,STREET=PKCS7,L=Российский ИОК,ST=50  Московская область,C=RU,GN=KSBA,SN=GCrypt,CN=ТЕСТ GCrypt and KSBA,E=test@test.ru
    Sig value:
    (sig-val 
     (gost 
      (r #FBD2976F7E63D792A695D4EB8D41F4880F43BB98108ABC313C1661380A1E480C54F96BBE611BA3D7ECB029E4C5685792D8D565AEA2E9AFD3AB660453C04EEC20#)
      (s #ACC68AC42AA2293A945E565C621AFF8F19AA5D5039D83D11D7125469DF068B1E0C4247A325CE031E9A9C31F55191CE4FB6528A11F0D81BE5463C38C30A2AD8AC#)
      )
     (hash stribog512)
     )
    s_pkey:
    (public-key 
     (ecc 
      (curve "1.2.643.7.1.2.1.2.1")
      (hash "1.2.643.7.1.1.2.3")
      (q #04C5BFDFB8481951FB19AE8631B27CD13979FAE1ED61910EF9E8A9EAC5D757503264C327C753D4E38E402434119806088E81E2C1D5FBD36FA43366BFE374367585DC6A79954EC97F796CF63CB2F23392050ECB50E147B80749927979057DD5CFD496A2C8A4367DDD0E0E92045147AB801EF177C3EB441979A2757377E982E93314#)
      )
     )
    s_hash:
    (data 
     (flags gost)
     (value #B2507D0208DB58DE0FA9EF6E4A2EDD07E86BAD313C2A0546C786FD5CF16A515DF28A1B40149F95570A8943922D1C6CFDD781727070034FEC799C1EEB6611EBA0#)
     )
    check_cert: verify CERT_CMS_KSBA.der Ok
    bash-4.3$ 

    Для проверки самоподписанного сертификата последний указывается и как проверяемый и как корневой сертификат:

    bash-4.3$ ./check_cert  CAcert_NEWCA.der  CAcert_NEWCA.der               
    check_cert: Verify E=info@ooo.ru,CN=ООО Софт,OU=УЦ 2,O=ООО Софт,C=RU,ST=Московская область,L=г. Королев 
    . . .
    check_cert: verify CAcert_NEWCA.der Ok 
    bash-4.3$

    Итак мы имеем простую утилиты для проверки подписи у сертификатов.

    Для проверки электронной подписи документов в формате PKCS#7 разработана

    тестовая утилита check_cms_signed.c:
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <assert.h>
    #include <time.h>
    #include <errno.h>
    
    #include <ksba.h>
    
    //#include "t-common.h"
    #include <gcrypt.h>
    #include <sys/stat.h>
    #include <sys/types.h>
    #include <fcntl.h>
    #include <unistd.h>
    /* Hash function used with libksba. */
    #define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write)
    void
    dump_isotime (const ksba_isotime_t t)
    {
      if (!t || !*t)
        fprintf (stderr, "[none]");
      else
        fprintf (stderr,"%.4s-%.2s-%.2s %.2s:%.2s:%s",
                    t, t+4, t+6, t+9, t+11, t+13);
    }
    
    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);
    }
    
    void
    dummy_hash_fnc (void *arg, const void *buffer, size_t length)
    {
      (void)arg;
      (void)buffer;
      (void)length;
    }
    
    static int
    dummy_writer_cb (void *cb_value, const void *buffer, size_t count)
    {
      (void)cb_value;
      (void)buffer;
      (void)count;
      return 0;
    }
    /*Считаем хэш от файла*/
    unsigned char *dgst_file (char *oid, char* fcont, int *len_h)
    {
      gcry_md_hd_t hd;
      gcry_error_t err;
      int algo;
          FILE *fp;
          unsigned char buf[1024];
          size_t size;
          int i;
        unsigned char *h;
        unsigned char *ret_h;
    
      algo = gcry_md_map_name (oid);
      if (algo == GCRY_MD_NONE)
    	return NULL;
    	
      err = gcry_md_open(&hd, algo, 0);
      if (err)
    	return NULL;
    
          fp = fopen (fcont, "r");
    
          if (fp == NULL) 
          	    return NULL;
    
          while (!feof (fp))
            {
              size = fread (buf, 1, sizeof(buf), fp);
              gcry_md_write (hd, buf, size);
            }
    
          h  = gcry_md_read(hd, 0);
    
          *len_h = gcry_md_get_algo_dlen (algo);
        ret_h = malloc(*len_h);
        memcpy(ret_h, h, *len_h);
          gcry_md_reset(hd);
    
      gcry_md_close(hd);
      return ret_h;
    }
    
    gcry_error_t one_file (const char *fname, char *fcontent, char *fcert)
    {
      gcry_error_t err;
      FILE *fp;
      ksba_reader_t r;
      ksba_writer_t w;
      ksba_cms_t cms;
      int i;
      const char *algoid;
      ksba_stop_reason_t stopreason;
      const char *s = NULL;
      size_t n;
      ksba_sexp_t p;
      char *dn;
      int idx;
      ksba_cert_t cert;
      int ii;
      unsigned char *cert_der;
      int cert_der_len;
      int f_der;
      int rez;
      ksba_sexp_t p1;
      size_t n1;
      gcry_sexp_t s_sig, s_hash, s_pkey, s_num;
      int gost_key;
      gcry_md_hd_t md;
      int rc;
      int digestlen;
      unsigned char *digest;
      gcry_md_hd_t data_md = NULL;
      ksba_isotime_t sigtime;
      unsigned char *sub_dn; //dn - подписанта
      char *is_dn; //dn - выдавшего сертификат
      ksba_sexp_t sub_p; //номер сертификата
      
      fprintf (stderr, "\n*** checking `%s' ***\n", fname);
      fp = fopen (fname, "r");
      if (!fp) {
          fprintf (stderr, "can't open `%s'\n", fname);
          exit (1);
      }
    
      err = ksba_reader_new (&r);
      if (err) {
          fprintf (stderr, "can't  reader `%s'\n", fname);
          exit (1);
      }
      err = ksba_reader_set_file (r, fp);
      if (err) {
          fprintf (stderr, "can't  reader set `%s'\n", fname);
          exit (1);
      }
      /* Also create a writer so that cms.c won't return an error when
         writing processed content.  */
      err = ksba_writer_new (&w);
      if (err) {
          exit (1);
      }
      err = ksba_writer_set_cb (w, dummy_writer_cb, NULL);
      if (err) {
          exit (1);
      }
     
      switch (ksba_cms_identify (r))
        {
        case KSBA_CT_SIGNED_DATA:    s = "signed data"; break;
        case KSBA_CT_DATA:           s = "data";
        case KSBA_CT_ENVELOPED_DATA: if (s == NULL) s  = "enveloped data";
        case KSBA_CT_DIGESTED_DATA:  if (s == NULL) s = "digested data";
        case KSBA_CT_ENCRYPTED_DATA: if (s == NULL) s = "encrypted data";
        case KSBA_CT_AUTH_DATA:      if (s == NULL) s = "auth data"; 
        default:                     if (s == NULL) s = "unknown"; 
    	printf ("identified as: %s\n", s);
    	exit(1);
        }
    
      err = ksba_cms_new (&cms);
      if (err) {
        exit(1);
      }
    
      err = ksba_cms_set_reader_writer (cms, r, w);
      if (err) {
        exit(1);
      }
    
      rc = gcry_md_open (&data_md, 0, 0);
      if (rc)
        {
          fprintf (stderr, "md_open failed: \n");
          exit(1);;
        }
    
      err = ksba_cms_parse (cms, &stopreason);
      if (err) {
          fprintf (stderr, "ksba_cms_parse: cannot parse %s\n", fname);
        exit(1);
      }
    
      ksba_cms_set_hash_function (cms, dummy_hash_fnc, NULL);
    
      do
        {
          err = ksba_cms_parse (cms, &stopreason);
    	if (err) {
        	    fprintf (stderr, "ksba_cms_parse: cannot parse %s\n", fname);
    	    exit(1);
    	}
        }
      while (stopreason != KSBA_SR_READY);
    
    /*Читаем из PKCS7 сертификат подписанта*/
      cert_der_len = 0;
      for (ii=0; (cert=ksba_cms_get_cert (cms, ii)); ii++)
        {
          cert_der = (unsigned char*)ksba_cert_get_image(cert, (size_t *)&cert_der_len);
    	f_der = open(fcert, O_RDWR|O_TRUNC|O_CREAT, 0666);
    	if (f_der == -1) {
    	    fprintf(stderr, "Bad open file=%s for cert\n", cert_der_len, fcert);
    	    exit(1);
    	}
    /*Сохраняем в файле сертификат подписанта*/
            rez = write(f_der, cert_der, cert_der_len);
            close(f_der);
      /* Читаем публичный ключ из сертификата подписанта*/
      p1 = ksba_cert_get_public_key (cert);
      n1 = gcry_sexp_canon_len (p1, 0, NULL, NULL);
      if (!n1)
        {
          fprintf(stderr, "libksba did not return a proper S-Exp\n");
    	exit(1);
        }
      err = gcry_sexp_sscan ( &s_pkey, NULL, p1, n1);
      ksba_free (p1);
      if (err)
        {
          fprintf(stderr, "gcry_sexp_scan failed: %s\n", "pubkey");
    	exit(1);
        }
    
    /*Читаем отличительное имя DN владельца сертификатп*/
        sub_dn = ksba_cert_get_subject (cert, 0);
     
          ksba_cert_release (cert);
        }
          for (idx=0; idx < 1; idx++) {
    	  int algo;        
        	  int info_pkalgo;
    	unsigned char *s;
    	    int s_len;
    	    int len_h;
    	    unsigned char *dgst_h;
        	  
    /*Читаем из подписи/PKCS#7 DN издателя сертификата подписанта is_dn и серийный номер сертификата подписанта sub_p*/
    /*Именно по этим полям должен искать сертификат проверяющий*/
    /*Мы исходим в примере из того, что сертификат подписанта лежит в контейнере PKCS#7*/
            err = ksba_cms_get_issuer_serial (cms, idx, &is_dn, &sub_p);
    	if (err) { 
    	    fprintf (stderr, "ksba_cms_get_issuer_serial cannot %s\n", fname);
    	    exit(1);
    	}
    /*Читаем алгоритм хэша*/
            algoid = ksba_cms_get_digest_algo (cms, idx);
    
            err = ksba_cms_get_sigattr_oids (cms, 0, "1.2.840.113549.1.7.1",&dn);
    	if (err && err != -1) { 
    	    fprintf (stderr, "ksba_cms_get_sigattr_oids cannot %s err=%d\n", fname, err);
    	    exit(1);
    	}
    
    	algo = gcry_md_map_name (algoid);
    /*Имеем дело с ГОСТ-ами (1) или нет*/
    	 gost_key = !memcmp(algoid, "1.2.643", 7);
                      gcry_md_enable (data_md, algo);
    
    	  rc = gcry_md_open (&md, algo, 0);
    	if (rc) {
            fprintf(stderr, "md_open failed:\n");
        	    exit(1);
    	}
    /*Устанавливаем хэш-функцию*/
    	ksba_cms_set_hash_function (cms, HASH_FNC, data_md);
    /*Получаем хэш от подписываемых атрибутов*/
    	rc = ksba_cms_hash_signed_attrs (cms, idx);
    	if (rc) {
        	    fprintf(stderr, "hashing signed attrs failed: \n");
        	    gcry_md_close (md);
    	    continue;
    	}
        gcry_md_final (md);
        digestlen = gcry_md_get_algo_dlen (algo);
    /*Читаем значение хэша*/
        digest = gcry_md_read (data_md, algo);
        gcry_md_close (md);
        if (gost_key){
    /*Имеем дело с ГОСТ*/
    	unsigned char *h;
    	unsigned char c;
    	int len_xy;
    /*Для тестирования архитектуры littlt-endian или big-endian*/
    	unsigned short arch = 1; /* 0x0001 */
    	h = digest;
    	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;
    	}
        }
         switch (gost_key) {
    	    case 0: 
    		if ( gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))",
                           algoid, (int)digestlen, digest) ) {
    		    fprintf(stderr, "BUG\n");      
    		    exit (1);
    		}
    		break;
    	    case 1: 
    /*Это ГОСТ, создаем S-переменную со значением хэша*/
    		if ( gcry_sexp_build (&s_hash, NULL, "(data(flags gost)(value %b))",
                            (int)digestlen, digest) ) {
    		    exit (1);
    		}
    		break;
    	    default:
    		    exit (1);
        }
    
    /*Читаем время создания подписи*/
          rc = ksba_cms_get_signing_time (cms, idx, sigtime);
          if (rc) {
              fprintf(stderr, "error getting signing time\n");
    //          *sigtime = 0; /* (we can't encode an error in the time string.) */
              exit(1);
        }
    
    
    /*Читаем значения хэша для подписанного документа из PKCS#7 */
              err = ksba_cms_get_message_digest (cms, idx, &dn, &n);
    	if (err) { 
    	    fprintf (stderr, "ksba_cms_get_message_digest cannot %s\n", fname);
    	    exit(1);
    	}
    /*Считаем значение хэш от подписанного документа*/
        dgst_h = dgst_file ((char *)algoid, (char *)fcontent, &len_h);
        if (dgst_h == NULL) {
          fprintf(stderr, "\nBad digest  %s\n", fcontent);
          exit (1);
        } 
        if (memcmp(dgst_h, dn, n)) {
    /*Что произошло: подменили документ, внесли изменения со времени подписания, изменили подпись и т.п.*/
          fprintf(stderr, "\nBad content  %s\n", fcontent);
          exit (1);
        } 
    
              ksba_free (dn);
              putchar ('\n');
    
              dn = ksba_cms_get_sig_val (cms, idx);
              if (!dn) {
                printf ("signer %d - signature not found\n", idx);
                exit(1);
              }
    	n = gcry_sexp_canon_len ((ksba_sexp_t)dn, 0, NULL, NULL);
    	if (!n){
    	    fprintf(stderr, "libksba did not return a proper S-Exp\n");
        	    exit(1);
    	}
    
    	err = gcry_sexp_sscan ( &s_sig, NULL, dn, n);
    	if (err){
        	    fprintf(stderr, "gcry_sexp_scan failed: %s\n", "Beda");
        	    exit(1);
    	}
    
              ksba_free (dn);
        }
    
    
        fprintf(stderr, "\n===========================================================\n");
          if (*sigtime){
            fprintf (stderr, "Дата подписания документа:\n");
            dump_isotime (sigtime);
            fprintf (stderr, "\n");
          }
          else
            fprintf (stderr, "[date not given]\n");
        fprintf(stderr, "\nДокумент подписал: %s\n", sub_dn);
        fprintf(stderr, "\nСертификат выдал: %s\n", is_dn);
    	n = gcry_sexp_canon_len ((ksba_sexp_t)sub_p, 0, NULL, NULL);
    	if (!n){
    	    fprintf(stderr, "libksba did not return a proper S-Exp NUM\n");
        	    exit(1);
    	}
    
      err = gcry_sexp_sscan ( &s_num, NULL, sub_p, n);
    
        show_sexp ("\nНомер сертификата:\n", s_num);
    
        fprintf(stderr, "\n===========================================================\n");
    
    /*Проверка подписи*/
        err = gcry_pk_verify (s_sig, s_hash, s_pkey);
    //    if(err) {
    	show_sexp ("s_pkey:\n", s_pkey);
    	show_sexp ("s_sig:\n", s_sig);
    	show_sexp ("s_hash:\n", s_hash);
    //    }
      ksba_cms_release (cms);
      ksba_reader_release (r);
      fclose (fp);
      return (err);    
    
    }
    
    int
    main (int argc, char **argv)
    {
        gcry_error_t err;
        unsigned char *h;
        int *len_h;
    
        if(argc != 4) {
    	fprintf(stderr, "Usage: check_cms <Проверяемая подпись> <Оригинал документа> <Куда сохранить сертификат подписанта>\n");
    	exit(1);
        }
        err = one_file (argv[1], argv[2], argv[3]);
        if (err) {
    	fprintf(stderr, "check_cms: verify %s error\n", argv[1]);
        } else {
    	fprintf(stderr, "check_cms: verify %s Ok\n", argv[1]);
    	fprintf(stderr, "check_cms: Сертификат подписанта сохранен в %s\n", argv[3]);
    
        }
        exit (0);
    }
    


    В целях упрощения примера он написан из предположения, что у документа только один подписан (одна подпись) и его сертификат хранится в подписи. Сохраняем утилиту check_cms_signed.c, транслируем ее и запускаем:

    bash-4.3$ cc -o check_cms_signed check_cms_signed.c  -lgcrypt libksba.so.8.11.6  
    bash-4.3$ ./check_cms_signed  
    Usage: check_cms <Проверяемая подпись> <Оригинал документа> <Куда сохранить сертифи
    кат подписанта> 
    bash-4.3$

    А теперь проверяем как работает утилита:

    bash-4.3$ ./check_cms_signed  test_cms_ksba.txt.p7s test_cms_ksba.txt save_cert.der 
    *** checking `test_cms_ksba.txt.p7s' ***
    ===========================================================
    Дата подписания документа:
    2018-06-25 15:57:12
    
    Документ подписал: SNILS=22222222222,INN=123456789012,O=CMS,STREET=PKCS7,L=Российский ИОК,ST=50  Московская область,C=RU,GN=KSBA,SN=GCrypt,CN=ТЕСТ GCrypt and KSBA,E=test@test.ru
    
    Сертификат выдал: E=info@lissi.ru,CN=ООО Софт,OU=УЦ 2,O=ООО Софт,C=RU,ST=Московская область,L=г. Королев,STREET=ул. Ленинская д.4 пом.7,OGRN=1234567890123,INN=123456789012
    
    Номер сертификата:
    (#73102E931BF9EA1369BA#)
    
    ===========================================================
    s_pkey:
    (public-key 
     (ecc 
      (curve "1.2.643.7.1.2.1.2.2")
      (hash "1.2.643.7.1.1.2.3")
      (q #044840A283684ECB537989536B9F080F7B914F3E6C153BC23F8A9212E303BF5B13905D29D0689CA5F2D0715D13FDC0FD387650193A7B46BE20C266776FAE36483750FE52A4C4E35EFB37EA64B48CB50ED5151289F59793574BD5FA59A2048A97FD94A1E5BB8DBF616B776D70C25774C1AC11CD6B6791D15850C37F7F176F49DBB6#)
      )
     )
    s_sig:
    (sig-val 
     (gost 
      (r #430C6EE1C0126F217B58EF5FB2E25055B2DF64AF0A1D769F2E7402145322ACD77B7D537B985AD7F3E3EDE94F9521D2F1E039B6F818B88D1CD709BE7BA97FE5E7#)
      (s #6A75146760E21BF6FA4CEDB41D37D938D5988DE048F9171796E764EC0E90891A7E02CA7F855C2468E11D217DB5C28DFAB1E31FF45793029FCDD666BE589F646A#)
      )
     (hash stribog512)
     )
    s_hash:
    (data 
     (flags gost)
     (value #AD66AC68C832442C9520718AF9F67A87518CFE23098886C075F09DD19AC626DA65D7E39F6E0F81F5CF19C7DC5C3C9CC75FAD26A6C450ADD4C02FD31B49BA7CF1#)
     )
    check_cms: verify test_cms_ksba.txt.p7s Ok
    check_cms: Сертификат подписанта сохранен в save_cert.der
    bash-4.3$ 

    Если подменить/изменить подписываемый документ, то получим:

    bash-4.3$ ./check_cms_signed  test_cms_ksba.txt.p7s test_cms_ksba_Change.txt save_cert.der
    *** checking `test_cms_ksba.txt.p7s' ***
    Bad content  test_cms_ksba_Change.txt
    bash-4.3$ 

    При успешной проверке сертификат подписанта будет сохранен в <Куда сохранить сертифи
    кат подписанта> (в примере это файл sace_cert.der).

    Получилась совсем не плохая утилита, которую можно использовать и на практике и в учебных целях.

    Но пройдена только четверть пути к конечной цели. За рамками нашего разговора осталось работа с шифрованными документами (PKCS#7, VKO, KEK), подписание документов, создание запросов на сертификат. Работа продолжается.
    • +11
    • 1,5k
    • 4
    Поделиться публикацией

    Комментарии 4

      0

      Все бы хорошо, если бы эти патчи ещё в список рассылки отправлялись. А так получается, что время потрачено, патчи есть, но реально воспользоваться ими сложно, а через несколько месяцев они устареют, потому что код проектов изменится.

        0
        через несколько месяцев они устареют, потому что код проектов изменится

        Да, это так. Я сейчас работаю над проектом GnuPg под ГОСТ-криптографию в целом, включая использование токенов PKCS#11 с российской криптографией.
        Надо подумать как это учесть. Спасибо.

          0
          Было бы здорово попробовать
            0

            Скоро можно будет попробовать

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

          Самое читаемое