» » » Как настроить Linux для входа в домен с использованием алгоритмов ГОСТ

 

Как настроить Linux для входа в домен с использованием алгоритмов ГОСТ

Автор: admin от 20-09-2019, 16:10, посмотрело: 251

Введение



Как настроить Linux для входа в домен с использованием алгоритмов ГОСТ

Протокол Kerberos 5 сейчас активно используется для аутентификации. Особенностью данного протокола является то, что он осуществляет аутентификацию, базируясь на трех китах:




  • Симметричное шифрование

  • Хеширование

  • ЭЦП

  • Третья доверенная сторона



  • Начиная с пятой версии появилась возможность использовать еще асимметричное шифрование (для электронной подписи). Более подробно на работе протокола Kerberos останавливаться не имеет смысла, ибо описание алгоритма можно посмотреть тут.



    К сожалению, количество алгоритмов шифрования, хеширования и ЭЦП, которые использует данный протокол, не настолько велико, насколько хотелось бы, поэтому в данной статье я хочу показать, как добавить легко и просто собственные алгоритмы в реализацию данного протокола MIT'ом. Добавлять же мы будем наши отечественные алгоритмы: ГОСТ 28147-89 (aka Магма), ГОСТ Р 34.11-2012 (aka Стрибог) и ГОСТ Р 34.10-2012 (хотелось бы тоже иметь для него aka, но я не знаю:(). Готовое решение для данных алгоритмов можно его найти в моем репозитории. На стороне клиента мы будем использовать аппаратные реализации алгоритмов ГОСТ в Рутокене ЭЦП 2.0 и их программные реализации в engine GOST для openssl. Но самый безопасный вариант хранения ключей – когда они генерируются непосредственно на Рутокене и никогда не покидают его память во время криптографических операций. Для такого варианта работы потребуется rtengine.

    тут. В дальнейшем автор полагает, что вы работаете с доменом AKTIV-TEST.RU



    Добавление алгоритмов хеширования и симметричного шифрования



    Как уже было ранее озвучено, мы не будем писать алгоритмы шифрования и хеширования с нуля, а воспользуемся готовой реализацией данных алгоритмов в openssl. Напрямую openssl не поддерживает в себе реализацию отечественных алгоритмов, но обладает мобильностью в данном вопросе и позволяет добавлять новые алгоритмы, используя механизм engine GOST для работы с ключами в файловой системе и, хранящие на токене — rtengine.



    Небольшое введение в механизм engine openssl и подключение engine GOST



    Engine в openssl – это небольшая динамическая библиотека, которую openssl подгружает в runtime по требованию. Каждая такая библиотека, должна содержать в себе определенные символы (функции) для загрузки необходимых алгоритмов. В данной работе мы воспользуемся engine gost, который содержит в себе все необходимые отечественные криптографические алгоритмы.



    Установка дичайше проста, например, и происходит следующим образом:




  • Выкачиваете из репозитория реализацию данного энджина.




  • собираете библиотеку с ним (mkdir build && cd build && cmake… && make):



    mkdir build
    cd build
    cmake ..
    make



  • В директории bin (КОТОРАЯ ПОЯВИТСЯ В КОРНЕВОМ КАТАЛОГЕ ПРОЕКТА!!!) будет лежать динамическая библиотека gost.so – это и есть наш энджин. Его нужно будет перенести в директорию, где хранятся энджины openssl. Узнать месторасположение данной директории можно с помощью:



    openssl version -a | grep ENGINESDIR



  • Дело за последним – нужно сказать openssl, где находится данный энджин и как он называется. Сделать можно это с помощью изменения файла конфигурации openssl.cnf. Месторасположение которого можно узнать с помощью:



    openssl version -a | grep OPENSSLDIR


    В конец данного файла нужно будет внести следующее содержимое:



    # в начало файла написать
    openssl_conf = openssl_def
    
    ...
    
    # в конец файла
    # OpenSSL default section
    [openssl_def]
    engines = engine_section
    
    # Engine section
    [engine_section]
    gost = gost_section
    
    # Engine gost section
    [gost_section]
    engine_id = gost
    dynamic_path = /path/to/engines/dir/with/gost.so
    default_algorithms = ALL
    init = 0





  • После этого данный энджин должен появится в openssl. Проверить его работоспособность можно заставив, например, сгенерировать приватный ключ в файле по ГОСТ Р 34.10-2012:



    openssl genpkey -engine gost -algorithm gost2012_512 -pkeyopt paramset:A -out client_key.pem


    Флаг -engine как раз говорит о том, какой engine нужно подгрузить перед началом работы для того, чтобы стал виден алгоритм генерации ключей для ГОСТ Р 34.10-2012.



    Реализация алгоритма хеширования



    Начнем с самого простого – с реализации алгоритма Стрибог. Kerberos обладает жесткой связью алгоритма хеширования и шифрования, то есть вы не можете просто так взять и выбрать для шифрования один алгоритм, а для хеширования другой – вам нужно будет встроить связку алгоритма хеширования и шифрования. Причина этого мне не известна, но раз там бытуют такие правила – давайте попробуем создать связку алгоритма Стрибог и AES.




    1. Т.к. нам понадобится уверенность в подключении в разных местах нашей программы, давайте создадим сначала небольшую библиотеку gost_helper, которая будет содержать в себе функцию инициализации энджина в openssl, а так же для удобства несколько функций, возвращающих контексты некоторых алгоритмов хеширования – нам это поможет в дальнейшем. Назовем эту библиотеку gost_helper и создадим для нее соответствующий заголовочник и исходный файл в директории src/lib/crypto/openssl/:



      // gost_helper.h
      #include <openssl/evp.h>
      
      // функция инициализация энджина GOST
      void krb5int_init_gost();
      
      // вспомогательные функции, возвращающие контексты алгоритмов хеширования
      const EVP_MD * EVP_gostR3411_2012_256();
      const EVP_MD * EVP_gostR3411_2012_512();
      
      // gost_helper.c
      #include "gost_helper.h"
      #include <openssl/engine.h>
      
      // указатель на подключенный энджин
      static ENGINE *eng = NULL;
      
      void
      krb5int_init_gost()
      {
         // для ускорения работы проверяем, не подключен ли энджин уже
         if (eng) return;
         OPENSSL_add_all_algorithms_conf();
         ERR_load_crypto_strings();
      
         if (!(eng = ENGINE_by_id("gost"))) {
             printf("Engine gost doesn’t exist");
             return;
         }
      
         ENGINE_init(eng);
         ENGINE_set_default(eng, ENGINE_METHOD_ALL);
      }
      
      const EVP_MD *
      EVP_gostR3411_2012_256()
      {
         krb5int_init_gost();
         return EVP_get_digestbynid(NID_id_GostR3411_2012_256);
      }
      
      const EVP_MD *
      EVP_gostR3411_2012_512()
      {
         krb5int_init_gost();
         return EVP_get_digestbynid(NID_id_GostR3411_2012_512);
      }



    2. После добавления вспомогательной библиотеки необходимо будет объявить о ее существовании в Makefile и выписать ее зависимости от файлов. Для этого добавим следующее:



      # добавим в шаблон src/lib/crypto/openssl/Makefile.in описание зависимостей объектных файлов от исходников:
      
      STLIBOBJS=
      hmac.o  
      ...
      stubs.o 
      gost_helper.o
      
      OBJS=
      $(OUTPRE)hmac.$(OBJEXT) 
      ...
      $(OUTPRE)stubs.$(OBJEXT) 
      $(OUTPRE)gost_helper$(OBJEXT)
      
      SRCS=
      $(srcdir)/hmac.c    
      ...
      $(srcdir)/stubs.c   
      $(srcdir)/gost_helper.c
      
      # В файл зависимостей src/lib/crypto/openssl/deps добавим, от чего будут зависеть наши объектные файлы и библиотеки:
      
      gost_helper.so gost_helper.po $(OUTPRE)gost_helper.$(OBJEXT): 
       $(BUILDTOP)/include/autoconf.h 
       $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h 
       $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(top_srcdir)/include/k5-buf.h 
       $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h 
       $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h 
       $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h 
       $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h 
       $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h 
       $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h 
       gost_helper.c gost_helper.h



    3. Стоит заметить, что в дальнейшем при подключении данной библиотеки, нам надо будет добавлять зависимости некоторых файлов от заголовочника данной библиотеки. Делается это очень просто – отыскивается цель, куда надо добавить зависимость в deps файле и записывается зависимость от rel/path/to/gost_helper.h. Например, в src/lib/crypto/openssl/hash_provider/deps нужно будет добавить следующее:



      hash_evp.so hash_evp.po $(OUTPRE)hash_evp.$(OBJEXT): 
       $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h 
      ...
       $(srcdir)/../gost_helper.h hash_evp.c
      


      В целях экономии места в статье и чтобы размять ваши мозги, я больше не буду обращать на это внимание: будьте внимательны и осторожны!




    4. Добавим теперь реализации функций хеширования, все их реализации у нас лежат в src/lib/crypto/openssl/hash_provider/hash_evp.c. Туда нужно будет дописать следующее:



      // Не забываем про изменение deps файла!!! Так уж и быть на первый раз предупрежу невнимательных
      #include "crypto_int.h"
      #include "gost_helper.h"
      #include <openssl/evp.h>
      
      ...
      
      static krb5_error_code
      hash_sha384(const krb5_crypto_iov *data, size_t num_data, krb5_data *output)
      {
         return hash_evp(EVP_sha384(), data, num_data, output);
      }
      
      static krb5_error_code
      hash_stribog256(const krb5_crypto_iov *data, size_t num_data, krb5_data *output)
      {
         return hash_evp(EVP_gostR3411_2012_256(), data, num_data, output);
      }
      
      static krb5_error_code
      hash_stribog512(const krb5_crypto_iov *data, size_t num_data, krb5_data *output)
      {
         return hash_evp(EVP_gostR3411_2012_512(), data, num_data, output);
      }
      
      // структура функции хеширования
      /*
      первый атрибут -- название алгоритма
      второй -- размер хеша в байтах
      третий -- размер блока передаваемых данных в байтах
      четвертый -- указатель на функцию хеширования
      */
      const struct krb5_hash_provider krb5int_hash_sha384 = {
         "SHA-384", 48, 128, hash_sha384
      };
      
      const struct krb5_hash_provider krb5int_hash_stribog256 = {
         "GOSTR34.11-2012-256", 32, 64, hash_stribog256
      };
      
      const struct krb5_hash_provider krb5int_hash_stribog512 = {
         "GOSTR34.11-2012-512", 64, 64, hash_stribog512
      };



    5. Теперь надо объявить эти контексты во всей библиотеке. Для этого нужно создать их описание в файле src/lib/crypto/krb/crypto_int.h.



      // Параллельно заметим, что компилятор начнет ругаться, что имя функций не помещается в указанный массив, поэтому немного поправим структуру krb5_hash_provider и добавим кол-во символов в массиве имени алгоритма:
      
      ...
      
      struct krb5_hash_provider {
         char hash_name[32]; // было 8
         size_t hashsize, blocksize;
      
         krb5_error_code (*hash)(const krb5_crypto_iov *data, size_t num_data,
                                 krb5_data *output);
      };
      
      ...
      
      extern const struct krb5_hash_provider krb5int_hash_sha384;
      extern const struct krb5_hash_provider krb5int_hash_stribog256;
      extern const struct krb5_hash_provider krb5int_hash_stribog512;
      
      ...



    6. Объявим идентификатор связки Стрибог и AES, внедря макросы, которые мы назовем CKSUMTYPE_STRIBOG_256_AES256, CKSUMTYPE_STRIBOG_512_AES256, ENCTYPE_AES256_CTS_STRIBOG_256, ENCTYPE_AES256_CTS_STRIBOG_512. Их надо объявить в шаблоне заголовочного файла src/include/krb5/krb5.hin. Выглядеть это будет примерно так:



      #define ENCTYPE_ARCFOUR_HMAC_EXP            0x0018 /**< RFC 4757 */
      #define ENCTYPE_CAMELLIA128_CTS_CMAC        0x0019 /**< RFC 6803 */
      #define ENCTYPE_CAMELLIA256_CTS_CMAC        0x001a /**< RFC 6803 */
      #define ENCTYPE_AES256_CTS_STRIBOG_256      0x001b /**< NO RFC */
      #define ENCTYPE_AES256_CTS_STRIBOG_512      0x001c /**< NO RFC */
      #define ENCTYPE_UNKNOWN                     0x01ff
      
      ...
      
      #define CKSUMTYPE_CMAC_CAMELLIA256 0x0012 /**< RFC 6803 */
      #define CKSUMTYPE_MD5_HMAC_ARCFOUR -137 /* Microsoft netlogon */
      #define CKSUMTYPE_HMAC_MD5_ARCFOUR -138 /**< RFC 4757 */
      #define CKSUMTYPE_STRIBOG_256_AES256 -139 /**< NO RFC */
      #define CKSUMTYPE_STRIBOG_512_AES256 -140 /**< NO RFC */



    7. Теперь необходимо добавить две связки функции хеширования и шифрования и функции шифрования и хеширования. Зачем две, если они эквиваленты, спросите вы? Ответ: не знаю, или это исторический костыль или способ оптимизации. Тем не менее, давайте добавим в необходимые файлы новые структуры:



      // в файле src/lib/crypto/krb/cksumtypes.c добавить в конец списка структуру алгоритмов хеширования и шифрования
      /*
      первый атрибут -- идентификатор
      второй -- название связки
      в третьем -- можно добавить алиасы связок
      в четвертом -- описание
      в пятом -- указатель на функцию шифрования
      в шестом -- указатель на функцию хеширования
      в седьмом -- указатель на функцию "работы с хешом"
      в восьмом -- размер блока
      в девятом -- размер хеша
      в десятом -- различные флаги
      */
      const struct krb5_cksumtypes krb5int_cksumtypes_list[] = {
      ...
      
         { CKSUMTYPE_STRIBOG_256_AES256,
           "stribog-256-aes256", { 0 }, "STRIBOG256 AES256 key",
           &krb5int_enc_aes256, &krb5int_hash_stribog256,
           krb5int_etm_checksum, NULL,
           64, 32, 0 },
      
         { CKSUMTYPE_STRIBOG_512_AES256,
           "stribog-512-aes256", { 0 }, "STRIBOG512 AES256 key",
           &krb5int_enc_aes256, &krb5int_hash_stribog512,
           krb5int_etm_checksum, NULL,
           64, 64, 0 },
      
      };
      
      // в файле src/lib/crypto/krb/etypes.c добавить в конец списка структуру алгоритмов шифрования и хеширования:
      
      /*
      первый атрибут -- идентификатор
      второй -- название
      третий -- алиас
      четвертый -- описание
      пятый -- указатель на функцию шифрования
      шестой -- указатель на функцию хеширования
      седьмой -- размер возвращаемых данных функцией получения случайных чисел из данных
      восьмой -- указатель на функцию, возвращающую характеристики алгоритмов
      девятый -- указатель на функцию шифрования, использующую алгоритмы хеширования и шифрования
         десятый -- указатель на функцию расшифрования, использующую алгоритмы хеширования и шифрования
         одиннадцатый -- указатель на функцию преобразования строки в ключ
         двенадцатый -- указатель на функцию выработки случайного ключа
         тринадцатый -- различные флаги
         четырнадцатый -- размер ключа
      */
      const struct krb5_keytypes krb5int_enctypes_list[] = { 
      ...
      
         { ENCTYPE_AES256_CTS_STRIBOG_256,
           "aes256-cts-stribog-256", { "aes256-stribog256" },
           "AES-256 CTS mode with 256-bit stribog",
           &krb5int_enc_aes256, &krb5int_hash_stribog256,
           16,
           krb5int_aes2_crypto_length, krb5int_etm_encrypt, krb5int_etm_decrypt,
           krb5int_aes2_string_to_key, k5_rand2key_direct,
           krb5int_aes2_prf,
           CKSUMTYPE_STRIBOG_256_AES256,
           0 /*flags*/, 256 },
      
         { ENCTYPE_AES256_CTS_STRIBOG_512,
           "aes256-cts-stribog-512", { "aes256-stribog512" },
           "AES-256 CTS mode with 512-bit stribog",
           &krb5int_enc_aes256, &krb5int_hash_stribog512,
           16,
           krb5int_aes2_crypto_length, krb5int_etm_encrypt, krb5int_etm_decrypt,
           krb5int_aes2_string_to_key, k5_rand2key_direct,
           krb5int_aes2_prf,
           CKSUMTYPE_STRIBOG_512_AES256,
           0 /*flags*/, 256 },
      
      };



    8. Казалось бы уже все, а вот и нет! Далее наступают проблемы, которые будут видны только в процессе отладки, некоторых из них можно было бы избежать, указав, например, другие указатели на функции в вышеуказанных структурах, но мы пойдем более сложным путем, чтобы показать, что еще возможно придется исправить по дороге. Обо всех этих проблемах я узнал только пользуясь отладчиком:



      // В файле src/lib/crypto/openssl/hmac.c будет задействоваться функция map_digest -- которая возвращает контекст хеш-функции по переданной структуре. Сейчас эта функция ничего не знает о наших ГОСТах. Исправим это:
      
      #include "crypto_int.h"
      #include "gost_helper.h"
      #include <openssl/hmac.h>
      #include <openssl/evp.h>
      
      static const EVP_MD *
      map_digest(const struct krb5_hash_provider *hash)
      {
         if (!strncmp(hashhash_name, "SHA1",4))
             return EVP_sha1();
      
      ...
      
         else if (!strncmp(hashhash_name, "GOSTR34.11-2012-256", 19))
             return EVP_gostR3411_2012_256();
         else if (!strncmp(hashhash_name, "GOSTR34.11-2012-512", 19))
             return EVP_gostR3411_2012_512();
         else
             return NULL;
      }
      
      // В файле src/lib/crypto/openssl/pbkdf2.c вызывается krb5int_pbkdf2_hmac, которая сейчас ничего не знает о новых хешах:
      
      krb5_error_code
      krb5int_pbkdf2_hmac(const struct krb5_hash_provider *hash,
                         const krb5_data *out, unsigned long count,
                         const krb5_data *pass, const krb5_data *salt)
      {
         const EVP_MD *md = NULL;
         /* Get the message digest handle corresponding to the hash. */
         if (hash == &krb5int_hash_sha1)
             md = EVP_sha1();
      
      ...
      
         else if (hash == &krb5int_hash_stribog256)
             md = EVP_gostR3411_2012_256();
         else if (hash == &krb5int_hash_stribog512)
             md = EVP_gostR3411_2012_512();
      
      ...
      
         return 0;
      }
      
      // в файле src/lib/krb5/krb/init_ctx.c нужно добавить в список связок алгоритмов, идентификатор новых алгоритмов:
      
      static krb5_enctype default_enctype_list[] = {
      
      ...
      
         ENCTYPE_AES256_CTS_STRIBOG_256, ENCTYPE_AES256_CTS_STRIBOG_512,
         0
      };





    После всех этих изменений можно проверить работу алгоритма. Соберем все, что мы наделали.



    autoconf
    ./configure --with-crypto-impl=openssl # использовать реализацию алгоритмов из openssl
    make
    sudo make install


    Теперь приступим к проверке. Для этого давайте выставим принудительное использование внедренных нами алгоритмов в файлы конфигурации Kerberos. Выполните следующее:




    1. Остановите krb5kdc:



      service krb5-kdc stop



    2. Подправим файл конфигурации kdc.conf (у меня это /usr/local/var/krb5kdc/kdc.conf). Выставим принудительное хеширование с помощью нововведенного алгоритма:



      [realms]
      AKTIV-TEST.RU = {
      master_key_type = aes256-stribog512
      supported_enctypes = aes256-stribog512:normal
      default_tgs_enctypes = aes256-stribog512
      default_tkt_enctypes = aes256-stribog512
      permitted_enctypes = aes256-stribog512
      }
      # Так же можно использовать хеш длиной 256 бит



    3. Аналогичные изменения в файле конфигурации всего протокола krb5.conf (у меня он находится по адресу /etc/krb5.conf):



      [libdefaults]
      supported_enctypes = aes256-stribog512:normal
      default_tgs_enctypes = aes256-stribog512
      default_tkt_enctypes = aes256-stribog512
      permitted_enctypes = aes256-stribog512
      # Также можно использовать хеш длиной 256 бит



    4. Далее запустим krb5kdc т.к. изменился master_key, то возможно придется создать базу данных principals заново с помощью krb5_newrealm.




    5. После этого создаем всех необходимых принципалов и можно начать работу. Попробуйте аутентифицироваться с помощью kinit.




    6. Проверим, что хеширование происходит по указанному алгоритму можно с

      помощью klist -e.






    Если сервис не запускается, то его можно запустить из-под рута с помощью src/kdc/krb5kdc. Если все запустилось, прошло гладко – поздравляю вас! Иначе – увы, панацею от всех проблем я не предлагаю, а лишь предлагаю "небольшую" инструкцию, содержащую основные шаги, которые вам придется совершить, дабы внедрить новый алгоритм в Kerberos. И если у вас ничего не вышло – берите в руки gdb и смотрите, где что выходит не так. Вам лишь могу дать несколько советов:




    1. собирать проект в режиме отладки можно, если передать в ./configure CFLAGS=" -g -O0";

    2. запустить krb5kdc не в фоновом режиме можно с помощью флага -n;

    3. надеюсь до этого не дойдет (хотя при реализации асимметричных алгоритмов у меня все-таки дошло) – возможно вам потребуются отладочные символы openssl – установите их или взяв из репозитория, или установив openssl из исходников с отладочными символами;

    4. то же самое касается gost engine.



    По идее этого набора советов вам должно хватить для того чтобы избавить себя от лишней траты времени в поисках "неизведанного".



    Реализация алгоритма шифрования



    В этом разделе я покажу, как можно добавить свой алгоритм шифрования данных в Kerberos, и мы попробуем создать связку Магмы и Стрибога, добавленного в прошлом разделе. Тут я уже предполагаю, что небольшая библиотека gost_helper уже была добавлена в прошлом разделе. Ну что же, разминаем пальцы и приступаем:




    1. Сначала опишем алгоритм в нашей библиотеке libk5crypto описав их в заголовочном файле src/lib/crypto/krb/crypto_int.h.



      ...
      
      extern const struct krb5_enc_provider krb5int_enc_camellia256;
      extern const struct krb5_enc_provider krb5int_enc_gost89;
      
      ...



    2. В директории src/lib/crypto/openssl/enc_provider добавим исходник gost.c, содержащий в себе реализацию всех необходимых алгоритмов (за основу я взял исходник алгоритма des). Важно заметить, что мы реализуем только режим шифрования cbc, так что для самопроверки вы можете взять любой другой режим шифрования и добавить его:



      #include "crypto_int.h"
      #include "gost_helper.h"
      #include <openssl/evp.h>
      
      #define BLOCK_SIZE 8
      
      static krb5_error_code
      krb5int_gost_encrypt(krb5_key key, const krb5_data *ivec, krb5_crypto_iov *data,
                     size_t num_data)
      {
         int ret, olen = BLOCK_SIZE;
         unsigned char iblock[BLOCK_SIZE], oblock[BLOCK_SIZE];
         struct iov_cursor cursor;
         EVP_CIPHER_CTX *ctx;
      
         // я бы перенес вызов этой функции в функцию инициализации состояния, но функция krb5int_gost_encrypt, иногда вызывается без нее:
         krb5int_init_gost();
         ctx = EVP_CIPHER_CTX_new();
         if (ctx == NULL)
             return ENOMEM;
      
         ret = EVP_EncryptInit_ex(ctx, EVP_get_cipherbynid(NID_gost89_cbc), NULL,
                                  keykeyblock.contents,
                                  (ivec) ? (unsigned char*)ivecdata : NULL);
         if (!ret) {
             EVP_CIPHER_CTX_free(ctx);
             return KRB5_CRYPTO_INTERNAL;
         }
      
         EVP_CIPHER_CTX_set_padding(ctx,0);
      
         k5_iov_cursor_init(&cursor, data, num_data, BLOCK_SIZE, FALSE);
         while (k5_iov_cursor_get(&cursor, iblock)) {
             ret = EVP_EncryptUpdate(ctx, oblock, &olen, iblock, BLOCK_SIZE);
             if (!ret)
                 break;
             k5_iov_cursor_put(&cursor, oblock);
         }
      
         if (ivec != NULL)
             memcpy(ivecdata, oblock, BLOCK_SIZE);
      
         EVP_CIPHER_CTX_free(ctx);
      
         zap(iblock, sizeof(iblock));
         zap(oblock, sizeof(oblock));
      
         if (ret != 1)
             return KRB5_CRYPTO_INTERNAL;
         return 0;
      }
      
      static krb5_error_code
      krb5int_gost_decrypt(krb5_key key, const krb5_data *ivec, krb5_crypto_iov *data,
                     size_t num_data)
      {
         int ret, olen = BLOCK_SIZE;
         unsigned char iblock[BLOCK_SIZE], oblock[BLOCK_SIZE];
         struct iov_cursor cursor;
         EVP_CIPHER_CTX *ctx;
      
         krb5int_init_gost();
         ctx = EVP_CIPHER_CTX_new();
         if (ctx == NULL)
             return ENOMEM;
      
         ret = EVP_DecryptInit_ex(ctx, EVP_get_cipherbynid(NID_gost89_cbc), NULL,
                                  keykeyblock.contents,
                                  (ivec) ? (unsigned char*)ivecdata : NULL);
         if (!ret) {
             EVP_CIPHER_CTX_free(ctx);
             return KRB5_CRYPTO_INTERNAL;
         }
      
         EVP_CIPHER_CTX_set_padding(ctx,0);
      
         k5_iov_cursor_init(&cursor, data, num_data, BLOCK_SIZE, FALSE);
         while (k5_iov_cursor_get(&cursor, iblock)) {
             ret = EVP_DecryptUpdate(ctx, oblock, &olen,
                                     (unsigned char *)iblock, BLOCK_SIZE);
             if (!ret)
                 break;
             k5_iov_cursor_put(&cursor, oblock);
         }
      
         if (ivec != NULL)
             memcpy(ivecdata, iblock, BLOCK_SIZE);
      
         EVP_CIPHER_CTX_free(ctx);
      
         zap(iblock, sizeof(iblock));
         zap(oblock, sizeof(oblock));
      
         if (ret != 1)
             return KRB5_CRYPTO_INTERNAL;
         return 0;
      }
      
      static krb5_error_code
      krb5int_gost_init_state (const krb5_keyblock *key, krb5_keyusage usage,
                             krb5_data *state)
      {
         statelength = 8;
         statedata = (void *) malloc(8);
         if (statedata == NULL)
             return ENOMEM;
         memset(statedata, 0, statelength);
         return 0;
      }
      
      static void
      krb5int_gost_free_state(krb5_data *state)
      {
         free(statedata);
         *state = empty_data();
      }
      
      /*
      первый атрибут -- как неожиданно, размер блока
      второй -- кол-во байтов для хранения первичного ключа
      третий -- размер байтов для хранения развернутого ключа
      четвертый -- указатель на функцию шифрования
      пятый -- указатель на функцию расшифровки
      шестой -- указатель на функцию cbc-mac checksum, не вдавался в подробности что это, т.к. наш алгоритм этого не имеет
      седьмой -- указатель на функцию инициализации шифрования
      восьмой -- указатель на функцию освобождения памяти под первичное состояние 
      */
      const struct krb5_enc_provider krb5int_enc_gost89 = {
         BLOCK_SIZE,
         32, 32,
         krb5int_gost_encrypt,
         krb5int_gost_decrypt,
         NULL,
         krb5int_gost_init_state,
         krb5int_gost_free_state
      };
      



    3. В шаблоне src/lib/crypto/openssl/enc_provider/Makefile.in укажем, что появился новый исходник:



      STLIBOBJS= 
      des.o   
      ...
      gost.o
      
      OBJS= 
      $(OUTPRE)des.$(OBJEXT)  
      ...
      $(OUTPRE)gost$(OBJEXT)
      
      SRCS= 
      $(srcdir)/des.c     
      ...
      $(srcdir)/gost.c



    4. Не забываем про указание зависимостей в src/lib/crypto/openssl/enc_provider/deps:



      gost.so gost.po $(OUTPRE)gost.$(OBJEXT): $(BUILDTOP)/include/autoconf.h 
       $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h 
       $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(srcdir)/../../krb/crypto_int.h 
       $(srcdir)/../crypto_mod.h $(top_srcdir)/include/k5-buf.h 
       $(top_srcdir)/include/k5-err.h $(top_srcdir)/include/k5-gmt_mktime.h 
       $(top_srcdir)/include/k5-int-pkinit.h $(top_srcdir)/include/k5-int.h 
       $(top_srcdir)/include/k5-platform.h $(top_srcdir)/include/k5-plugin.h 
       $(top_srcdir)/include/k5-thread.h $(top_srcdir)/include/k5-trace.h 
       $(top_srcdir)/include/krb5.h $(top_srcdir)/include/krb5/authdata_plugin.h 
       $(top_srcdir)/include/krb5/plugin.h $(top_srcdir)/include/port-sockets.h 
       $(top_srcdir)/include/socket-utils.h $(srcdir)/../gost_helper.h gost.c



    5. Добавим идентификаторы связок к src/include/krb5/krb5.hin:



      ...
      
      #define ENCTYPE_AES256_CTS_STRIBOG_256      0x001b /**< NO RFC */
      #define ENCTYPE_AES256_CTS_STRIBOG_512      0x001c /**< NO RFC */
      #define ENCTYPE_GOST89_CBC_STRIBOG_256      0x001d /**< SOME GOST */
      #define ENCTYPE_GOST89_CBC_STRIBOG_512      0x001e /**< SOME GOST */
      #define ENCTYPE_UNKNOWN                     0x01ff
      
      ...
      
      #define CKSUMTYPE_STRIBOG_256_AES256 -139 /**< NO RFC */
      #define CKSUMTYPE_STRIBOG_512_AES256 -140 /**< NO RFC */
      #define CKSUMTYPE_STRIBOG_256_GOST89 -141 /**< SOME GOST */
      #define CKSUMTYPE_STRIBOG_512_GOST89 -142 /**< SOME GOST */



    6. Опишем структуру связок функции хеширования и шифрования, как в прошлом разделе:



      // src/lib/crypto/krb/cksumtypes.c 
      
      const struct krb5_cksumtypes krb5int_cksumtypes_list[] = {
      
      ...
      
         { CKSUMTYPE_STRIBOG_256_GOST89,
           "stribog-256-gost89", { 0 }, "STRIBOG256 GOST89 key",
           &krb5int_enc_gost89, &krb5int_hash_stribog256,
           krb5int_dk_checksum, NULL,
           64, 32, 0 },
      
         { CKSUMTYPE_STRIBOG_512_GOST89,
           "stribog-512-gost89", { 0 }, "STRIBOG512 GOST89 key",
           &krb5int_enc_gost89, &krb5int_hash_stribog512,
           krb5int_dk_checksum, NULL,
           64, 64, 0 },
      
      };
      
      // src/lib/crypto/krb/etypes.c
      
      // тут я использовал только функции по умолчанию, но вы можете попробовать поэкспериментировать с различными другими функциями, например, с такими же как в aes
      const struct krb5_keytypes krb5int_enctypes_list[] = {
      
      ...
      
          { ENCTYPE_GOST89_CBC_STRIBOG_256,
            "gost89-cbc-stribog-256", { "gost89-stribog256" },
            "GOST 28147-89 CBC mode with 256-bit stribog",
            &krb5int_enc_gost89, &krb5int_hash_stribog256,
            16,
            krb5int_dk_crypto_length, krb5int_dk_encrypt, krb5int_dk_decrypt,
            krb5int_dk_string_to_key, k5_rand2key_direct,
            krb5int_dk_prf,
            CKSUMTYPE_STRIBOG_256_GOST89,
            0 /*flags*/, 256 },
      
         { ENCTYPE_GOST89_CBC_STRIBOG_512,
           "gost89-cbc-stribog-512", { "gost89-stribog512" },
           "GOST 28147-89 CBC mode with 512-bit stribog",
           &krb5int_enc_gost89, &krb5int_hash_stribog512,
           16,
           krb5int_dk_crypto_length, krb5int_dk_encrypt, krb5int_dk_decrypt,
           krb5int_dk_string_to_key, k5_rand2key_direct,
           krb5int_dk_prf,
           CKSUMTYPE_STRIBOG_512_GOST89,
           0 /*flags*/, 256 },
      
      };



    7. Добавим в список режимов шифрования по умолчанию в файле src/lib/krb5/krb/init_ctx.c:



      static krb5_enctype default_enctype_list[] = {
      
      ...
      
         ENCTYPE_AES256_CTS_STRIBOG_256, ENCTYPE_AES256_CTS_STRIBOG_512,
         ENCTYPE_GOST89_CBC_STRIBOG_256, ENCTYPE_GOST89_CBC_STRIBOG_512,
         0
      };





    После этих манипуляций можно попробовать протестировать нововведенный алгоритм. Тестирование происходит аналогичным образом, что и в прошлом разделе. Имя связки можно взять в виде алиаса (например, gost89-stribog512) или с помощью имени самого алгоритма (например, gost89-cbc-stribog-512). Надеюсь, что все заработает, иначе не забывайте о том, что я сказал ранее.



    Добавление алгоритма цифровой подписи



    Ура! Мы приступаем к финальному разделу данной статьи и попробуем добавить собственный алгоритм электронной подписи. Не стоит пугаться, его добавить проще чем что-либо еще, так что давайте поскорее приступим… Хотя нет, подождите, для начала небольшая ремарка.



    Асимметричное шифрование, достаточно, тяжеловесная штука. Одна из его главных особенностей – возможность аутентификации: все это построено на удостоверяющих центрах, сертификатах и всякой-всякой сложной лабуде. Как говорится...



    Ребята, не стоит вскрывать эту тему. Вы молодые, шутливые, вам все легко. Это не то. Это не Чикатило и даже не архивы спецслужб. Сюда лучше не лезть. Серьезно, любой из вас будет жалеть. Лучше закройте тему и забудьте, что тут писалось. Я вполне понимаю, что данным сообщением вызову дополнительный интерес, но хочу сразу предостеречь пытливых — стоп. Остальные просто не найдут.

    Как-то так. Ну что же, если у вас хватило смелости приступить к этому, то давайте начнем. В данной статье я покажу вам два способа получения и хранения приватных ключей и сертификатов: в файловой системе и на токене. В сущности, для обоих применима Аксиома Эскобара, но для второго способа потребуется дополнительный энджин, так что если вы решитесь пойти путем шиноби, то милости просим в данный раздел, остальные могут его пропустить.



    Настройка openssl для работы с токенами (на примере Рутокен)



    Для работы с Рутокенами, аппаратно поддерживающими ГОСТ-криптографию, для openssl существует rtengine. Его установка достаточно проста и не сильно отличается от GOST, нужно только знать, что и где брать.




    1. Скачиваете отсюда SDK rutoken разработчика и достаете из папки sdk/openssl/rtengine/bin/ собранную под вашу платформу библиотеку с энжином и помещаете в папку с engine.




    2. Скачать и установить модуль librtpkcs11ecp.so. отсюда




    3. Собирать утилиту из ветки master OpenSC взяв коммит 8cf1e6f




    4. После того как мы все это сделали, осталось настроить конфигурационный файл openssl.cnf:



      # в начала файла написать
      openssl_conf = openssl_def
      
      ...
      
      # в конец файла
      # OpenSSL default section
      [openssl_def]
      engines = engine_section
      
      # Engine section
      [engine_section]
      gost = gost_section
      rtengine = rtengine_section
      
      # Engine gost section
      [gost_section]
      engine_id = gost
      dynamic_path = /usr/lib/x86_64-linux-gnu/engines-1.1/gost.so
      default_algorithms = ALL
      
      # Engine rtengine section
      [rtengine_section]
      engine_id = rtengine
      dynamic_path = /path/to/engine/librtengine.so
      MODULE_PATH = /path/to/module/librtpkcs11ecp.so
      RAND_TOKEN = pkcs11:manufacturer=Aktiv Co.;model=Rutoken ECP
      default_algorithms = CIPHERS, DIGEST, PKEY, RAND





    Работоспособность engine мы проверим на деле.



    Создание приватного ключа и сертификата для принципала, KDC и УЦ



    За основу данной инструкции была взята инструкция по настройке kerberos. Я лишь адаптировал ее для работы с русскими алгоритмами, так что частично заглядывайте туда.




    1. Создадим приватный ключ и сертификат удостоверяющего центра, а также приватный ключ, запрос на сертификат и сам сертификат, подписанный УЦ для KDC:



      openssl genpkey -engine gost -algorithm gost2012_256 -pkeyopt paramset:B -out CA_key.pem # приватный ключ УЦ
      openssl req -engine gost -key CA_key.pem -new -x509 -out CA_cert.pem # самоподписанный сертификат УЦ
      
      openssl genpkey -engine gost -algorithm gost2012_256 -pkeyopt paramset:B -out KDC_key.pem # приватный ключ KDC
      openssl req -engine gost -new -out KDC.req -key ./KDC_key.pem # заявка на подписание сертификата для KDC
      
      # !!! ТУТ ВАЖНО СОЗДАТЬ РАСШИРЕНИЕ pkinit_extensions ИЗ ИНСТРУКЦИИ ВЫШЕ
      REALM=AKTIV-TEST.RU; export REALM # устанавливаем домен KDC
      CLIENT=127.0.0.1; export CLIENT # устанавливаем имя клиента, для которого подписываем сертификат (в нашем случае имя KDC). Я все воспроизвожу локально, поэтому ставлю localhost
      openssl x509 -engine gost -req -in ./KDC.req -CAkey ./CA_key.pem -CA ./CA_cert.pem -out ./KDC.pem -extfile ./pkinit_extensions -extensions kdc_cert -CAcreateserial # подписываем сертификат для KDC.
      
      sudo cp ./KDC.pem ./KDC_key.pem ./CA_cert.pem /var/lib/krb5kdc # отправляем все ключи и сертификаты у директорию kdc.



    2. Изменим конфигурационный файл kdc, чтобы он знал, откуда брать ключи и сертификаты:



      
      [kdcdefaults]
      
      ...
      
         pkinit_identity = FILE:/var/lib/krb5kdc/KDC.pem,/var/lib/krb5kdc/KDC_key.pem
         pkinit_anchors = FILE:/var/lib/krb5kdc/CA_cert.pem





    [libdefaults]

    spake_preauth_groups = edwards25519



    3. Зададим принудительную предварительную аутентификацию для принципала:
    
       ```bash
       sudo kadmin.local
       kadmin.local$: modprinc +requires_preauth user



    1. Создадим приватный ключ и заявку на сертификат для нашего принципала. Тут будет развилка, и надо выполнить разные действия в зависимости от того, где мы будем хранить приватный ключ: на токене или в ФС:




      1. Для создания ключа и заявки в ФС все делаем почти аналогично, как для KDC:



        openssl genpkey -engine gost -algorithm gost2012_256 -pkeyopt paramset:B -out client_key.pem # приватный ключ клиента
        openssl req -engine gost -new -out client.req -key ./client_key.pem # заявка на подписание сертификата для клиента



      2. Для создания ключа на токене и подписания сертификата для него нужно выполнить следующее:



        pkcs11-tool --module /path/to/module/librtpkcs11ecp.so --keypairgen --key-type GOSTR3410-2012-256:B -l --id 45 # создаем приватный и публичный ключ и помещаем их на токен и присваеваем  id=45
        openssl req -engine rtengine -new -key="pkcs11:id=E" -keyform engine -out client.req # создаем заявку на сертификат для ключа хранящегося на токене. E -- ascii запись 45






    2. Теперь подпишем нашу заявку:



      REALM=AKTIV-TEST.RU; export REALM # имя домена
      CLIENT=user; export CLIENT # имя пользователя, для которого подписываем сертификат
      openssl x509 -engine gost -CAkey ./CA_key.pem -CA ./CA_cert.pem -req -in ./client.req -extensions client_cert -extfile ./pkinit_extensions -out client.pem
      openssl x509 -engine gost -in client.pem -out client.crt -outform DER # Конвертируем сертификат из формата PEM в формат CRT



    3. Теперь дело за малым: в первом случае загружаем сертификат в специальную директорию, во втором – на токен:



      sudo cp ./client_key.pem client.pem /etc/krb5 # первый случай
      
      pkcs11-tool --module /usr/lib/librtpkcs11ecp.so -l -y cert -w ./client.crt --id 45 # второй случай (сертификат должен иметь тот же id, что и ключ)



    4. Настраиваем файл конфигурации клиента (у меня это /etc/krb5.conf):



      [libdefaults]
      
      ...
         pkinit_anchors = FILE:/var/lib/krb5kdc/CA_cert.pem
         # для аутентификации через ФС
         pkinit_identities = FILE:/etc/krb5/client.pem,/etc/krb5/client_key.pem 
         # для аутентификации через токен
         #pkinit_identities = PKCS11:/usr/lib/librtpkcs11ecp.so
      





    Надеюсь, что на данном этапе проблем не возникнет. Мы совсем близко! Добавим реализацию новых алгоритмов.



    Добавление нового алгоритма цифровой подписи



    Алгоритмы ЭЦП добавить куда проще чем те, что рассматривались ранее – придется заменить всего-то 2 файли! src/plugins/preauth/pkinit/pkcs11.h и src/plugins/preauth/pkinit/pkinit_crypto_openssl.c




    1. Начнем с добавления идентификаторов новых механизмов и ключей в заголовочник pkcs11.h. Идентификатор механизма – это название алгоритма, который подается токену для того, чтобы он его совершил. Все эти идентификаторы стандартизированы и их можно найти в интернете (хоть и с большим трудом). Наши я нашел здесь в sdk/pkcs11/include/rtpkcs11t.h. Добавим в заголовочник следующие идентификаторы ключей и механизмов:



      ...
      
      #define CKK_TWOFISH      (0x21)
      #define CKK_GOSTR3410        (0x30)
      #define CKK_GOSTR3411        (0x31)
      #define CKK_GOST28147        (0x32)
      #define CKK_VENDOR_DEFINED   (1UL  31)
      
      // A mask for new GOST algorithms.
      // For details visit https://tc26.ru/standarts/perevody/guidelines-the-pkcs-11-extensions-for-implementing-the-gost-r-34-10-2012-and-gost-r-34-11-2012-russian-standards-.html
      #define NSSCK_VENDOR_PKCS11_RU_TEAM     (CKK_VENDOR_DEFINED | 0x54321000)
      #define CK_VENDOR_PKCS11_RU_TEAM_TK26   NSSCK_VENDOR_PKCS11_RU_TEAM
      
      #define CKK_GOSTR3410_512    (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x003)
      
      ...
      
      #define CKM_AES_MAC_GENERAL      (0x1084)
      #define CKM_AES_CBC_PAD          (0x1085)
      #define CKM_GOSTR3410_KEY_PAIR_GEN   (0x1200UL)
      #define CKM_GOSTR3410            (0x1201UL)
      #define CKM_GOSTR3410_WITH_GOSTR3411 (0x1202UL)
      #define CKM_GOSTR3410_KEY_WRAP  (0x1203UL)
      #define CKM_GOSTR3410_DERIVE    (0x1204UL)
      #define CKM_GOSTR3410_512_KEY_PAIR_GEN   (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x005)
      #define CKM_GOSTR3410_512    (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x006)
      #define CKM_GOSTR3410_12_DERIVE  (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x007)
      #define CKM_GOSTR3410_WITH_GOSTR3411_12_256  (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x008)
      #define CKM_GOSTR3410_WITH_GOSTR3411_12_512  (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x009)
      #define CKM_GOSTR3411            (0x1210UL)
      #define CKM_GOSTR3411_HMAC      (0x1211UL)
      #define CKM_GOSTR3411_12_256 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x012)
      #define CKM_GOSTR3411_12_512 (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x013)
      #define CKM_GOSTR3411_12_256_HMAC    (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x014)
      #define CKM_GOSTR3411_12_512_HMAC    (CK_VENDOR_PKCS11_RU_TEAM_TK26 | 0x015)
      #define CKM_GOST28147_KEY_GEN   (0x1220UL)
      #define CKM_GOST28147_ECB       (0x1221UL)
      #define CKM_GOST28147           (0x1222UL)
      #define CKM_GOST28147_MAC       (0x1223UL)
      #define CKM_GOST28147_KEY_WRAP  (0x1224UL)
      


      Всеми ими мы не воспользуемся, но на будущее можно добавить.




    2. В файле pkinit_crypto_openssl.c, в первую очередь, нужно добавить подгрузку энджинов перед началом работы. Вызов этой функции также нужно вставить перед get_key, т.к. эта функция почему-то вызывается перед подгрузкой энджинов:



      #include <dirent.h>
      #include <arpa/inet.h>
      #include <openssl/engine.h>
      
      static ENGINE *eng = NULL;
      
      krb5int_init_engines()
      {
         if (eng) return;
         OPENSSL_add_all_algorithms_conf();
         ERR_load_crypto_strings();
      
       if (!(eng = ENGINE_by_id("rtengine"))) {
             printf("Engine rtengine doesn’t exist");
             return;
         }
      
         ENGINE_init(eng);
         ENGINE_set_default(eng, ENGINE_METHOD_ALL);
      
         if (!(eng = ENGINE_by_id("gost"))) {
             printf("Engine gost doesn’t exist");
             return;
         }
      
         ENGINE_init(eng);
         ENGINE_set_default(eng, ENGINE_METHOD_ALL);
      }
      
      ...
      
      get_key(krb5_context context, pkinit_identity_crypto_context id_cryptoctx,
             char *filename, const char *fsname, EVP_PKEY **retkey,
             const char *password)
      {
      ...
      
         krb5_error_code retval;
      
         krb5int_init_engines();
      
      ...
      }
      
      ...
      
      int
      pkinit_openssl_init()
      {
         /* Initialize OpenSSL. */
         ERR_load_crypto_strings();
         OpenSSL_add_all_algorithms();
         krb5int_init_engines();
         return 0;
      }



    3. После инициализации энджинов можно приступить к замене механизмов и электронной подписи. В данный момент жестко зашит только один алгоритм – RSA с хешом, полученным из sha1. Мы же встроим выбор между возможными режимами в зависимости от типа ключа, хранящегося на токене или в ФС. Для этого введем несколько функций, которые будут на вход получать контексты шифрования, а на выходе выдавать необходимые идентификаторы алгоритмов и механизмов. Также будет необходимо немного подправить функцию получения хендла приватного ключа с токена, т.к. она возвращает только RSA ключи:



      // закомментируем фильтр на тип приватного ключа
      krb5_error_code
      pkinit_find_private_key(pkinit_identity_crypto_context id_cryptoctx,
                             CK_ATTRIBUTE_TYPE usage,
                             CK_OBJECT_HANDLE *objp)
      {
      
      ...
      
         true_false = TRUE;
         attrs[nattrs].type = usage;
         attrs[nattrs].pValue = &true_false;
         attrs[nattrs].ulValueLen = sizeof true_false;
         nattrs++;
      #endif
      
      //    keytype = CKK_RSA;
      //    attrs[nattrs].type = CKA_KEY_TYPE;
      //    attrs[nattrs].pValue = &keytype;
      //    attrs[nattrs].ulValueLen = sizeof keytype;
      //    nattrs++;
      
      ...
      
      }
      
      // функция получения типа идентификатора алгоритма цифровой подписи в зависимости от полученного типа ключа:
      static int
      ckk_key_to_nid(CK_KEY_TYPE type)
      {
         switch(type){
         case CKK_GOSTR3410:
             return NID_id_GostR3410_2012_256;
         case CKK_GOSTR3410_512:
             return NID_id_GostR3410_2012_512;
         default:
             return NID_rsa;
         }
      }
      
      // функция, возвращающая идентификатор алгоритма цифровой подписи, если он хранится на токене:
      static int
      pkinit_get_pkey_type(krb5_context context,
                             pkinit_identity_crypto_context id_cryptoctx)
      {
         CK_OBJECT_HANDLE obj;
         CK_ATTRIBUTE attrs[1];
         CK_KEY_TYPE key_type;
         int r;
      
         // открываем сессию:
         if (pkinit_open_session(context, id_cryptoctx)) {
             pkiDebug("can't open pkcs11 sessionn");
             return NID_rsa;
         }
      
         // находим приватный ключ:
         if (pkinit_find_private_key(id_cryptoctx, CKA_SIGN, &obj)) {
             return NID_rsa;
         }
      
         // вытаскиваем тип ключа:
         attrs[0].type = CKA_KEY_TYPE;
         attrs[0].pValue = &key_type;
         attrs[0].ulValueLen = sizeof (key_type);
      
         if ((r = id_cryptoctxp11C_GetAttributeValue(id_cryptoctxsession,
                                                         obj, attrs, 1)) != CKR_OK) {
             pkiDebug("C_GetAttributeValue: %sn Used RSAn", pkinit_pkcs11_code_to_text(r));
             return NID_rsa;
         }
      
         // возвращаем идентификатор алгоритма:
         return ckk_key_to_nid(key_type);
      
      }
      
      // функция, возвращающая идентификатор алгоритма хеширования в зависимости от того, какой алгоритм цифровой подписи используется:
      static int
      pkey_to_digest_nid(const EVP_PKEY* const pkey)
      {
         switch (EVP_PKEY_id(pkey)) {
         case NID_id_GostR3410_2012_256:
             return NID_id_GostR3411_2012_256;
         case NID_id_GostR3410_2012_512:
             return NID_id_GostR3411_2012_512;
         case NID_id_GostR3410_2001:
             return NID_id_GostR3411_2012_256;
         default:
             return NID_sha1;
         }
      }
      
      // функция, возвращающая идентификатор алгоритма хеширования для данного контекста:
      static int
      get_digest_nid(krb5_context context, const pkinit_identity_crypto_context id_cryptctx)
      {
         int nid;
         // если ключ указан (он находится в ФС), значит достаем NID оттуда, иначе из токена
         if (id_cryptctxmy_key) {
             nid = EVP_PKEY_id(id_cryptctxmy_key);
         } else {
             nid = pkinit_get_pkey_type(context, id_cryptctx);
         }
      
         switch (nid) {
         case NID_id_GostR3410_2012_256:
             return NID_id_GostR3411_2012_256;
         case NID_id_GostR3410_2012_512:
             return NID_id_GostR3411_2012_512;
         case NID_id_GostR3410_2001:
             return NID_id_GostR3411_2012_256;
         default:
             return NID_sha1;
         }
      }
      
      // функция, возвращающая идентификатор алгоритма цифровой подписи:
      static int
      get_alg_nid(krb5_context context, const pkinit_identity_crypto_context id_cryptctx)
      {
         int nid;
         if (id_cryptctxmy_key) {
             nid = EVP_PKEY_id(id_cryptctxmy_key);
         } else {
             nid = pkinit_get_pkey_type(context, id_cryptctx);
         }
      
         switch (nid) {
         case NID_id_GostR3410_2012_256:
             return NID_id_tc26_signwithdigest_gost3410_2012_256;
         case NID_id_GostR3410_2012_512:
             return NID_id_tc26_signwithdigest_gost3410_2012_512;
         case NID_id_GostR3410_2001:
             return NID_id_tc26_signwithdigest_gost3410_2012_256;
         default:
             return NID_sha1WithRSAEncryption;
         }
      }
      
      // функция возвращающая идентификатор механизма:
      static CK_MECHANISM_TYPE
      get_mech_type(krb5_context context, const pkinit_identity_crypto_context id_cryptctx)
      {
         int nid;
         if (id_cryptctxmy_key) {
             nid = EVP_PKEY_id(id_cryptctxmy_key);
         } else {
             nid = pkinit_get_pkey_type(context, id_cryptctx);
         }
      
         switch (nid) {
         case NID_id_GostR3410_2012_256:
             return CKM_GOSTR3410_WITH_GOSTR3411_12_256;
         case NID_id_GostR3410_2012_512:
             return CKM_GOSTR3410_WITH_GOSTR3411_12_512;
         case NID_id_GostR3410_2001:
             return CKM_GOSTR3410_WITH_GOSTR3411_12_256;
         default:
             return CKM_RSA_PKCS;
         }
      }



    4. Теперь лишь осталось в функциях создания цифровой подписи cms_signeddata_create и create_signature заменить жесткую установку алгоритма на выбор алгоритма в зависимости от контекста:



      
      krb5_error_code
      cms_signeddata_create(krb5_context context,
                           pkinit_plg_crypto_context plg_cryptoctx,
                           pkinit_req_crypto_context req_cryptoctx,
                           pkinit_identity_crypto_context id_cryptoctx,
                           int cms_msg_type,
                           int include_certchain,
                           unsigned char *data,
                           unsigned int data_len,
                           unsigned char **signed_data,
                           unsigned int *signed_data_len)
      {
      
      ...
      
             /* Set digest algs */
             p7sidigest_algalgorithm = OBJ_nid2obj(
                         get_digest_nid(context, id_cryptoctx));
      
      ...
      
             p7sidigest_enc_algalgorithm = OBJ_nid2obj(get_alg_nid(context, id_cryptoctx));
      
      ...
      
                 EVP_DigestInit_ex(ctx, EVP_get_digestbynid(get_digest_nid(context, id_cryptoctx)), NULL);
      
      ...
      
                 alen = (unsigned int )ASN1_item_i2d((ASN1_VALUE *) sk, &abuf,
                                      ASN1_ITEM_rptr(PKCS7_ATTR_SIGN));
      
      ...
              // заменяем проверку механизма, на проверку идентификатора алгоритма хеширования (сделать это в двух местах):
                 if (id_cryptoctxpkcs11_method == 1 &&
                 get_digest_nid(context, id_cryptoctx) == NID_sha1) {
      
      }
      
      static krb5_error_code
      create_signature(unsigned char **sig, unsigned int *sig_len,
                      unsigned char *data, unsigned int data_len, EVP_PKEY *pkey)
      {
      
      ...
      
         EVP_SignInit(ctx, EVP_get_digestbynid(pkey_to_digest_nid(pkey)));
      
      ...
      
      }
      
      // узнаем механизм теперь на этапе создания подписи:
      static krb5_error_code
      pkinit_sign_data_pkcs11(krb5_context context,
                             pkinit_identity_crypto_context id_cryptoctx,
                             unsigned char *data,
                             unsigned int data_len,
                             unsigned char **sig,
                             unsigned int *sig_len)
      {
      
      ...
      
         mech.mechanism = get_mech_type(context, id_cryptoctx);
         mech.pParameter = NULL;
         mech.ulParameterLen = 0;
      
      ...
      
      }





    После всех этих манипуляций вы можете попробовать зайти, используя токен или сертификат в файловой системе (во втором случае, вероятно, потребуются права рута):



    sudo kinit user


    Если после запроса пароля токена не потребовалось вводить пароль user, значит, все отработало правильно.



    Все замечания и вопросы вы можете писать в комментариях, а я постараюсь на них оперативно ответить.



    Источник: Хабр / Интересные публикации

    Категория: Game Development

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

    Добавление комментария

    Имя:*
    E-Mail:
    Комментарий:
    Полужирный Наклонный текст Подчеркнутый текст Зачеркнутый текст | Выравнивание по левому краю По центру Выравнивание по правому краю | Вставка смайликов Выбор цвета | Скрытый текст Вставка цитаты Преобразовать выбранный текст из транслитерации в кириллицу Вставка спойлера
    Введите два слова, показанных на изображении: *