Цифровая подпись с использованием сертификата и ключа от USB-токена

Я хочу подписать файл с помощью ключа пользователя и сертификата с USB-токена (ключа).

Я некоторое время искал по этому вопросу на stackoverflow и других сайтах, но не получил ничего полезного, кроме некоторых хороших возможностей в .NET Framework (которые я не использую).

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

Также я прочитал, что как только токен подключен к компьютеру, его сертификат загружается в системное хранилище. Можно ли использовать сертификат из магазина? Как такой сертификат может быть идентифицирован и доступен среди других в магазине? а как насчет закрытого ключа?

Я использовал OpenSSL для цифровой подписи, когда сертификат может быть извлечен из файла .p12 или .pfx.

Поправь меня, если я где-то не прав, я новичок в этой теме.

3

Решение

Существует 2 варианта:

  1. PKCS # 11. Почти каждый производитель USB-крипто-токенов и смарт-карт предоставляет DLL-драйвер драйвера для PKCS # 11, который вы можете вызвать. Я должен отметить, что спецификация интерфейса PKCS # 11 является довольно свободной, и это приводит к причудам и несовместимости между различными поставщиками. То есть вам может понадобиться использовать один набор атрибутов сертификата на одном устройстве и другой набор атрибутов на другом устройстве.
  2. CryptoAPI. Большинство поставщиков предоставляют модуль CryptoAPI (CSP), который «отображает» сертификат в хранилище сертификатов Windows, и вы используете его для подписи так же, как вы используете любой сертификат в хранилище сертификатов Windows. Это означает использование различных функций Crypt *, Cert * и аналогичных функций в Windows API.

Я не думаю, что OpenSSL может быть использован для вашей задачи — вам нужно использовать либо CryptoAPI, либо PKCS # 11.

наш SecureBlackbox Продукт предоставляет единый высокоуровневый интерфейс для подписи данных в соответствии с различными криптографическими стандартами и с использованием PKCS # 11 и / или CryptoAPI. Тем не менее, в случае PKCS # 11 вам (или оператору системы, в которой выполняется подпись) необходимо знать путь к DLL-библиотеке драйвера PKCS # 11. SecureBlackbox можно использовать из C ++, используя его версию библиотеки.

6

Другие решения

Я не знаю, что охарактеризовал бы какой-либо аспект движков OpenSSL как «довольно простой». Версия для командной строки хаотична, и я мог понять, что делала командная строка только после того, как сделал это в коде сам. Порядок операций и время жизни не очень хорошо определены (и я до сих пор не до конца знаю, что это такое, но у меня работает система и больше не происходит утечка памяти, так что, да)

Я выложил работающие версии на github: https://github.com/tkil/openssl-pkcs11-samples

Вот экскурсия по соответствующим частям tok-sign.c:

Сначала несколько помощников:

#define FAIL( msg, dest )                      \
do {                                       \
fprintf( stderr, "error: " msg "\n" ); \
goto dest;                             \
} while ( 0 )

/* mandatory is "not optional"... */
const int CMD_MANDATORY = 0;

Наверное, самая странная вещь о dynamic двигатель в том, что это действительно мета-движок: вы кормите его различными параметрами, и когда вы кормите его LOAD, он загружает динамическую библиотеку и делает доступным новый движок. Это просто в коде, если вы знаете правильный порядок операций. Здесь мы получаем доступ к динамическому движку, настраиваем его, а затем просим ввести pkcs11 двигатель:

ENGINE_load_dynamic();
ENGINE * dyn = ENGINE_by_id( "dynamic" );
if ( ! dyn )
FAIL( "retrieving 'dynamic' engine", free_out_sig_file );

// this is the bridge between OpenSSL and any generic PCKS11 provider:
char * engine_pkcs11_so = "/opt/crypto/lib/engines/engine_pkcs11.so";

if ( 1 != ENGINE_ctrl_cmd_string( dyn, "SO_PATH", engine_pkcs11_so, CMD_MANDATORY ) )
FAIL( "dyn: setting so_path <= 'engine_pkcs11.so'", free_dyn );

if ( 1 != ENGINE_ctrl_cmd_string( dyn, "ID", "pkcs11", CMD_MANDATORY ) )
FAIL( "dyn: setting id <= 'pkcs11'", free_dyn );

if ( 1 != ENGINE_ctrl_cmd( dyn, "LIST_ADD", 1, NULL, NULL, CMD_MANDATORY ) )
FAIL( "dyn: setting list_add <= 1", free_dyn );

if ( 1 != ENGINE_ctrl_cmd( dyn, "LOAD", 1, NULL, NULL, CMD_MANDATORY ) )
FAIL( "dyn: setting load <= 1", free_dyn );

На этом этапе, если все эти вызовы выполнены успешно, ваш экземпляр OpenSSL теперь имеет доступ к новому движку под названием «pkcs11». Теперь нам нужно получить доступ к этому новому движку, правильно его настроить и позволить ему инициализироваться:

ENGINE * pkcs11 = ENGINE_by_id( "pkcs11" );
if ( ! pkcs11 )
FAIL( "pkcs11: unable to get engine", free_dyn );

// this is the actual pkcs11 provider we're using.  in this case, it's
// from the OpenSC package.
char * opensc_pkcs11_so = "/opt/crypto/lib/opensc-pkcs11.so";

if ( 1 != ENGINE_ctrl_cmd_string( pkcs11, "MODULE_PATH", opensc_pkcs11_so, CMD_MANDATORY ) )
FAIL( "setting module_path <= 'opensc-pkcs11.so'", free_pkcs11 );

if ( 1 != ENGINE_ctrl_cmd_string( pkcs11, "PIN", key_pin, CMD_MANDATORY ) )
FAIL( "setting pin", free_pkcs11 );

if ( 1 != ENGINE_init( pkcs11 ) )
FAIL( "pkcs11: unable to initialize engine", free_pkcs11 );

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

EVP_PKEY * key = ENGINE_load_private_key( pkcs11, key_id, NULL, NULL );
if ( ! key )
FAIL( "reading private key", free_pkcs11 );

Однако я не мог понять, как извлечь соответствующий сертификат через интерфейс ENGINE, поэтому я перешел непосредственно к LibP11:

PKCS11_CTX * p11_ctx = PKCS11_CTX_new();
if ( ! p11_ctx )
FAIL( "opening pkcs11 context", free_key );

if ( 0 != PKCS11_CTX_load( p11_ctx, opensc_pkcs11_so ) )
FAIL( "unable to load module", free_p11_ctx );

PKCS11_SLOT * p11_slots;
unsigned int num_p11_slots;
if ( 0 != PKCS11_enumerate_slots( p11_ctx, &p11_slots, &num_p11_slots ) )
FAIL( "enumerating slots", free_p11_ctx_module );

PKCS11_SLOT * p11_used_slot =
PKCS11_find_token( p11_ctx, p11_slots, num_p11_slots );
if ( ! p11_used_slot )
FAIL( "finding token", free_p11_slots );

PKCS11_CERT * p11_certs;
unsigned int num_p11_certs;
if ( 0 != PKCS11_enumerate_certs( p11_used_slot->token, &p11_certs, &num_p11_certs ) )
FAIL( "enumerating certs", free_p11_slots );

Наконец, мы начинаем сбор данных для запроса CMS_Sign. Мы смотрим на все сертификаты на токене, выбираем тот, который соответствует закрытому ключу, а затем сохраняем остальные в OpenSSL STACK_OF(X509):

STACK_OF(X509) * extra_certs = sk_X509_new_null();
if ( ! extra_certs )
FAIL( "allocating extra certs", free_p11_slots );

X509 * key_cert = NULL;
for ( unsigned int i = 0; i < num_p11_certs; ++i )
{
PKCS11_CERT * p11_cert = p11_certs + i;

if ( ! p11_cert->label )
continue;

// fprintf( stderr, "p11: got cert label='%s', x509=%p\n",
//         p11_cert->label, p11_cert->x509 );

if ( ! p11_cert->x509 )
{
fprintf( stderr, "p11: ... no x509, ignoring\n" );
continue;
}

const char * label = p11_cert->label;
const unsigned int label_len = strlen( label );

if ( strcmp( label, key_label ) == 0 )
{
// fprintf( stderr, "p11: ... saving as signing cert\n" );
key_cert = p11_cert->x509;
}
else if ( strncmp( label, "encrypt", 7 ) == 0 &&
label_len == 8 &&
'0' <= label[7] && label[7] <= '3' )
{
// fprintf( stderr, "p11: ... ignoring as encrypting cert\n" );
}
else
{
// fprintf( stderr, "p11: ... saving as extra cert\n" );
if ( ! sk_X509_push( extra_certs, p11_cert->x509 ) )
FAIL( "pushing extra cert", free_extra_certs );
}
}

if ( ! key_cert )
FAIL( "finding signing cert", free_extra_certs );

Наконец, мы можем подписать данные, а затем вывести подпись в формате файла DER:

CMS_ContentInfo * ci = CMS_sign( key_cert, key, extra_certs, in_data_file,
CMS_DETACHED | CMS_BINARY );
if ( ! ci )
FAIL( "could not create signing structure", free_extra_certs );

if ( 1 != i2d_CMS_bio( out_sig_file, ci ) )
FAIL( "could not write signature in DER", free_ci );

После этого это просто очистка:

free_ci:
CMS_ContentInfo_free( ci );

free_extra_certs:
/* these certs are actually "owned" by the libp11 code, and are
* presumably freed with the slot or context. */
sk_X509_free( extra_certs );

free_p11_slots:
PKCS11_release_all_slots( p11_ctx, p11_slots, num_p11_slots );

free_p11_ctx_module:
PKCS11_CTX_unload( p11_ctx );

free_p11_ctx:
PKCS11_CTX_free( p11_ctx );

free_key:
EVP_PKEY_free( key );

free_pkcs11:
ENGINE_free( pkcs11 );

free_dyn:
ENGINE_free( dyn );
10

Вы можете сделать это, используя движки OpenSSL и предоставив им механизм PKCS # 11.

Это можно сделать из командной строки (флаг -engine) или установив движок. У apps.c в / apps / дистрибутива openssl есть хорошие примеры.

Типичная форма вызова командной строки выглядит

usr/bin/openssl << EOM
engine dynamic -pre SO_PATH:/Library/OpenSC/lib/engines/engine_pkcs11.so  -pre ID:pkcs11 -pre LIST_ADD:1 -pre LOAD -pre MODULE_PATH:opensc-pkcs11.so
req -engine pkcs11 -batch -subj "/CN=moi" -new -key slot_$SLOT-id_$KID -keyform engine -x509 -out cert.pem -text
EOM

который создает&подписывает запрос на устройство.

Чтобы подписать что-то:

${OPENSSL} << EOM || exit 1
engine -vvvv dynamic \
-pre SO_PATH:/Library/OpenSC/lib/engines/engine_pkcs11.so  \
-pre ID:pkcs11 \
-pre LIST_ADD:1 \
-pre LOAD \
-pre MODULE_PATH:$PKCS \
-pre PIN:$PIN  \
-pre VERBOSE

x509 -engine pkcs11 -req -in req.csr -out signed.pem \
-CAfile $CA \
-keyform engine -key $SLOT:$CAKID \
-cert $CAKID.pem

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

и использовать ключ на карте, чтобы соединиться с клиентской аутентификацией к серверу:

${OPENSSL} << EOM || exit 1
engine -vvvv dynamic \
-pre SO_PATH:/Library/OpenSC/lib/engines/engine_pkcs11.so  \
-pre ID:pkcs11 \
-pre LIST_ADD:1 \
-pre LOAD \
-pre MODULE_PATH:$PKCS \
-pre PIN:$PIN  \
-pre VERBOSE

s_client -engine pkcs11 -connect localhost:1443 \
-CAfile $CA \
-keyform engine -key $SLOT:$KID \
-cert $KID.pem

EOM

это довольно просто перевести на собственный код — просто найдите движок в apps.c в файле openssl apps.c — чтобы увидеть, как это сделать. В большинстве случаев это некоторые

pkey = ENGINE_load_private_key(e, file,

в отличие от старого

BIO_read_filename(key,file)
pkey=d2i_PrivateKey_bio(key, NULL);
6
По вопросам рекламы [email protected]