- PVSM.RU - https://www.pvsm.ru -
Протокол Kerberos 5 сейчас активно используется для аутентификации. Особенностью данного протокола является то, что он осуществляет аутентификацию, базируясь на четырех китах:
Начиная с пятой версии появилась возможность использовать еще асимметричное шифрование (для электронной подписи). Более подробно на работе протокола Kerberos останавливаться не имеет смысла, ибо описание алгоритма можно посмотреть тут [1].
К сожалению, количество алгоритмов шифрования, хеширования и ЭЦП, которые использует данный протокол, не настолько велико, насколько хотелось бы, поэтому в данной статье я хочу показать, как добавить легко и просто собственные алгоритмы в реализацию данного протокола MIT'ом [2]. Добавлять же мы будем наши отечественные алгоритмы: ГОСТ 28147-89 (aka Магма), ГОСТ Р 34.11-2012 (aka Стрибог) и ГОСТ Р 34.10-2012 (хотелось бы тоже иметь для него aka, но я не знаю:(). Готовое решение для данных алгоритмов можно его найти в моем репозитории [3]. На стороне клиента мы будем использовать аппаратные реализации алгоритмов ГОСТ в Рутокене ЭЦП 2.0 и их программные реализации в engine GOST для openssl. Но самый безопасный вариант хранения ключей – когда они генерируются непосредственно на Рутокене и никогда не покидают его память во время криптографических операций. Для такого варианта работы потребуется rtengine.
Перед тем, как приступить к внедрению алгоритмов, опишем основные места, где будут производится изменения. Внутри директории src/lib/crypto/ находятся реализации всех алгоритмов, отвечающих за симметричную криптографию и хеширование. В ней имеются 2 реализации этих криптографических алгоритмов: встроенная(builtin) и openssl. Дабы сэкономить время и место, мы, естественно, будем добавлять реализации алгоритмов с помощью openssl, в котором они уже есть (ну или почти есть, но об этом чуть позже). Для добавления же асимметричных алгоритмов, нужно будет подправить плагин src/plugins/preauth/pkinit
Если у вас еще не настроен Kerberos, то инструкцию по его первичной настройки и эксплуатации можно взять тут [4]. В дальнейшем автор полагает, что вы работаете с доменом AKTIV-TEST.RU
Как уже было ранее озвучено, мы не будем писать алгоритмы шифрования и хеширования с нуля, а воспользуемся готовой реализацией данных алгоритмов в openssl. Напрямую openssl не поддерживает в себе реализацию отечественных алгоритмов, но обладает мобильностью в данном вопросе и позволяет добавлять новые алгоритмы, используя механизм engine GOST для работы с ключами в файловой системе и, хранящие на токене — rtengine.
Engine в openssl – это небольшая динамическая библиотека, которую openssl подгружает в runtime по требованию. Каждая такая библиотека, должна содержать в себе определенные символы (функции) для загрузки необходимых алгоритмов. В данной работе мы воспользуемся engine gost, который содержит в себе все необходимые отечественные криптографические алгоритмы.
Установка дичайше проста, например, и происходит следующим образом:
Выкачиваете из репозитория [5] реализацию данного энджина.
собираете библиотеку с ним (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.
Т.к. нам понадобится уверенность в подключении в разных местах нашей программы, давайте создадим сначала небольшую библиотеку 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);
}
После добавления вспомогательной библиотеки необходимо будет объявить о ее существовании в 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
Стоит заметить, что в дальнейшем при подключении данной библиотеки, нам надо будет добавлять зависимости некоторых файлов от заголовочника данной библиотеки. Делается это очень просто – отыскивается цель, куда надо добавить зависимость в 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
В целях экономии места в статье и чтобы размять ваши
Добавим теперь реализации функций хеширования, все их реализации у нас лежат в 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
};
Теперь надо объявить эти контексты во всей библиотеке. Для этого нужно создать их описание в файле 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;
...
Объявим идентификатор связки Стрибог и 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 */
Теперь необходимо добавить две связки функции хеширования и шифрования и функции шифрования и хеширования. Зачем две, если они эквиваленты, спросите вы? Ответ: не знаю, или это исторический костыль или способ оптимизации. Тем не менее, давайте добавим в необходимые файлы новые структуры:
// в файле 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 },
};
Казалось бы уже все, а вот и нет! Далее наступают проблемы, которые будут видны только в процессе отладки, некоторых из них можно было бы избежать, указав, например, другие указатели на функции в вышеуказанных структурах, но мы пойдем более сложным путем, чтобы показать, что еще возможно придется исправить по дороге. Обо всех этих проблемах я узнал только пользуясь отладчиком:
// В файле 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(hash->hash_name, "SHA1",4))
return EVP_sha1();
...
else if (!strncmp(hash->hash_name, "GOSTR34.11-2012-256", 19))
return EVP_gostR3411_2012_256();
else if (!strncmp(hash->hash_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. Выполните следующее:
Остановите krb5kdc:
service krb5-kdc stop
Подправим файл конфигурации 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 бит
Аналогичные изменения в файле конфигурации всего протокола 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 бит
Далее запустим krb5kdc т.к. изменился master_key, то возможно придется создать базу данных principals заново с помощью krb5_newrealm.
После этого создаем всех необходимых принципалов и можно начать работу. Попробуйте аутентифицироваться с помощью kinit.
Проверим, что хеширование происходит по указанному алгоритму можно с
помощью klist -e.
Если сервис не запускается, то его можно запустить из-под рута с помощью src/kdc/krb5kdc. Если все запустилось, прошло гладко – поздравляю вас! Иначе – увы, панацею от всех проблем я не предлагаю, а лишь предлагаю "небольшую" инструкцию, содержащую основные шаги, которые вам придется совершить, дабы внедрить новый алгоритм в Kerberos. И если у вас ничего не вышло – берите в руки gdb и смотрите, где что выходит не так. Вам лишь могу дать несколько советов:
По идее этого набора советов вам должно хватить для того чтобы избавить себя от лишней траты времени в поисках "неизведанного".
В этом разделе я покажу, как можно добавить свой алгоритм шифрования данных в Kerberos, и мы попробуем создать связку Магмы и Стрибога, добавленного в прошлом разделе. Тут я уже предполагаю, что небольшая библиотека gost_helper уже была добавлена в прошлом разделе. Ну что же, разминаем пальцы и приступаем:
Сначала опишем алгоритм в нашей библиотеке 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;
...
В директории 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,
key->keyblock.contents,
(ivec) ? (unsigned char*)ivec->data : 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(ivec->data, 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,
key->keyblock.contents,
(ivec) ? (unsigned char*)ivec->data : 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(ivec->data, 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)
{
state->length = 8;
state->data = (void *) malloc(8);
if (state->data == NULL)
return ENOMEM;
memset(state->data, 0, state->length);
return 0;
}
static void
krb5int_gost_free_state(krb5_data *state)
{
free(state->data);
*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
};
В шаблоне 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
Не забываем про указание зависимостей в 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
Добавим идентификаторы связок к 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 */
Опишем структуру связок функции хеширования и шифрования, как в прошлом разделе:
// 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 },
};
Добавим в список режимов шифрования по умолчанию в файле 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
};
После этих манипуляций можно попробовать протестировать нововведенный алгоритм. Тестирование происходит аналогичным образом, что и в прошлом разделе [8]. Имя связки можно взять в виде алиаса (например, gost89-stribog512) или с помощью имени самого алгоритма (например, gost89-cbc-stribog-512). Надеюсь, что все заработает, иначе не забывайте о том, что я сказал ранее [9].
Ура! Мы приступаем к финальному разделу данной статьи и попробуем добавить собственный алгоритм электронной подписи. Не стоит пугаться, его добавить проще чем что-либо еще, так что давайте поскорее приступим… Хотя нет, подождите, для начала небольшая ремарка.
Асимметричное шифрование, достаточно, тяжеловесная штука. Одна из его главных особенностей – возможность аутентификации: все это построено на удостоверяющих центрах, сертификатах и всякой-всякой сложной лабуде. Как говорится...
Ребята, не стоит вскрывать эту тему. Вы молодые, шутливые, вам все легко. Это не то. Это не Чикатило и даже не архивы спецслужб. Сюда лучше не лезть. Серьезно, любой из вас будет жалеть. Лучше закройте тему и забудьте, что тут писалось. Я вполне понимаю, что данным сообщением вызову дополнительный интерес, но хочу сразу предостеречь пытливых — стоп. Остальные просто не найдут.
Как-то так. Ну что же, если у вас хватило смелости приступить к этому, то давайте начнем. В данной статье я покажу вам два способа получения и хранения приватных ключей и сертификатов: в файловой системе и на токене. В сущности, для обоих применима Аксиома Эскобара [10], но для второго способа потребуется дополнительный энджин, так что если вы решитесь пойти путем шиноби, то милости просим в данный раздел, остальные могут его пропустить.
Для работы с Рутокенами, аппаратно поддерживающими ГОСТ-криптографию, для openssl существует rtengine. Его установка достаточно проста и не сильно отличается от GOST, нужно только знать, что и где брать.
Скачиваете отсюда [11] SDK rutoken разработчика и достаете из папки sdk/openssl/rtengine/bin/ собранную под вашу платформу библиотеку с энжином и помещаете в папку с engine.
Скачать и установить модуль librtpkcs11ecp.so. отсюда [12]
Собирать утилиту из ветки master OpenSC взяв коммит 8cf1e6f [13]
После того как мы все это сделали, осталось настроить конфигурационный файл 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%20Co.;model=Rutoken%20ECP
default_algorithms = CIPHERS, DIGEST, PKEY, RAND
Работоспособность engine мы проверим на деле.
За основу данной инструкции была взята инструкция по настройке kerberos [4]. Я лишь адаптировал ее для работы с русскими алгоритмами, так что частично заглядывайте туда.
Создадим приватный ключ и сертификат удостоверяющего центра, а также приватный ключ, запрос на сертификат и сам сертификат, подписанный УЦ для 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.
Изменим конфигурационный файл 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
Создадим приватный ключ и заявку на сертификат для нашего принципала. Тут будет развилка, и надо выполнить разные действия в зависимости от того, где мы будем хранить приватный ключ: на токене или в ФС:
Для создания ключа и заявки в ФС все делаем почти аналогично, как для 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 # заявка на подписание сертификата для клиента
Для создания ключа на токене и подписания сертификата для него нужно выполнить следующее:
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
Теперь подпишем нашу заявку:
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
Теперь дело за малым: в первом случае загружаем сертификат в специальную директорию, во втором – на токен:
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, что и ключ)
Настраиваем файл конфигурации клиента (у меня это /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
Начнем с добавления идентификаторов новых механизмов и ключей в заголовочник pkcs11.h. Идентификатор механизма – это название алгоритма, который подается токену для того, чтобы он его совершил. Все эти идентификаторы стандартизированы и их можно найти в интернете (хоть и с большим трудом). Наши я нашел здесь [11] в 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)
Всеми ими мы не воспользуемся, но на будущее можно добавить.
В файле 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;
}
После инициализации энджинов можно приступить к замене механизмов и электронной подписи. В данный момент жестко зашит только один алгоритм – 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_cryptoctx->p11->C_GetAttributeValue(id_cryptoctx->session,
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_cryptctx->my_key) {
nid = EVP_PKEY_id(id_cryptctx->my_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_cryptctx->my_key) {
nid = EVP_PKEY_id(id_cryptctx->my_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_cryptctx->my_key) {
nid = EVP_PKEY_id(id_cryptctx->my_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;
}
}
Теперь лишь осталось в функциях создания цифровой подписи 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 */
p7si->digest_alg->algorithm = OBJ_nid2obj(
get_digest_nid(context, id_cryptoctx));
...
p7si->digest_enc_alg->algorithm = 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_cryptoctx->pkcs11_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, значит, все отработало правильно.
Все замечания и вопросы вы можете писать в комментариях, а я постараюсь на них оперативно ответить.
Автор: lo1ol
Источник [14]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/rutoken-e-tsp/330723
Ссылки в тексте:
[1] тут: https://en.wikipedia.org/wiki/Kerberos_(protocol)
[2] реализацию данного протокола MIT'ом: https://github.com/krb5/krb5
[3] моем репозитории: https://github.com/lo1ol/krb5/tree/gost_impl
[4] тут: https://dev.rutoken.ru/pages/viewpage.action?pageId=21332045
[5] репозитория: https://github.com/gost-engine/engine
[6] мозги: http://www.braintools.ru
[7] исходников: https://github.com/openssl/openssl
[8] прошлом разделе: #check_algor
[9] сказал ранее: #my_remark
[10] Аксиома Эскобара: https://memepedia.ru/aksioma-eskobara/
[11] отсюда: https://www.rutoken.ru/developers/sdk/
[12] отсюда: https://www.rutoken.ru/support/download/pkcs/
[13] 8cf1e6f: https://github.com/OpenSC/OpenSC/tree/8cf1e6f769b36bdcadc3958574305b90e88c816d
[14] Источник: https://habr.com/ru/post/467707/?utm_source=habrahabr&utm_medium=rss&utm_campaign=467707
Нажмите здесь для печати.