Rutoken, OpenSSL и локальный УЦ для подписи сообщений / Хабр

Rutoken, OpenSSL и локальный УЦ для подписи сообщений / Хабр Электронная цифровая подпись

Rutoken, openssl и локальный уц для подписи сообщений

Настраиваем «наш» openssl.cnf:

а) Добавляем в начало файла директивы для подключения движка токена:

openssl_conf = openssl_def
[ openssl_def ]
engines = engine_section

[ engine_section ]
rtengine = gost_section

[ gost_section ]
dynamic_path = /path/to/rutoken/openssl/connector/librtengine.so
MODULE_PATH = /path/to/rutoken/pkcs11/librtpkcs11ecp.so
RAND_TOKEN = pkcs11:manufacturer=Aktiv Co.;model=Rutoken ECP
default_algorithms = CIPHERS, DIGEST, PKEY, RAND


б) раскомментируйте строку

# req_extensions = v3_req # The extensions to add to a certificate request


в) в секции [ v3_req ] укажите следующие параметры:

subjectSignTool = ASN1:FORMAT:UTF8,UTF8String:Наш Рутокен ЭЦП
extendedKeyUsage=emailProtection
keyUsage=digitalSignature,nonRepudiation,keyEncipherment,dataEncipherment


г) в секции [ v3_ca ] надо убрать опцию critical из параметра basicConstraints:

basicConstraints = CA:true

Для чего? Честный ответ: не знаю. Однако все примеры корневых сертификатов, которые я скачивал в процессе попыток разобраться в теме, были без признака critical. Адресую вопрос «для чего?» более опытным в вопросе коллегам.

д) опционально устанавливаем значения по умолчанию, которые будут предлагаться при выпуске самоподписанных сертификатов и генерации запросов на выпуск клиентских сертификатов. Эти параметры находятся в секции [ req_distinguished_name ]

Параметр с постфиксом _default — то самое значение по умолчанию. Пример:

countryName			= Country Name (2 letter code)
countryName_default		= AU
countryName_min		= 2
countryName_max		= 2


Когда система попросит ввести параметр countryName, то в квадратных скобочках укажет, что по умолчанию оставит значение AU.

На этом настройка конфига OpenSSL завершена. Осталось указать OpenSSL, что использовать надо именно его. Для этого устанавливаем переменную окружения OPENSSL_CONF:

export OPENSSL_CONF=/path/to/your/openssl.cnf

Опционально форматируем наш токен, используя утилиту rtAdminТеперь все готово к развертыванию УЦ.

Алгоритм действий в общих чертах прост:

а) выпускаем корневой сертификат удостоверяющего центра, используя алгоритм ГОСТ:

б) на каждом из USB-токенов
Ниже приведена реализация этого алгоритма для одного токена:

Генерация закрытого ключа для сертификата CA (используем алгоритм ГОСТ):

openssl genpkey -algorithm gost2022_256 -pkeyopt paramset:A -outform PEM -out demoCA/private/cakey.pem


Выпускаем самоподписанный сертификат CA:

<b>openssl req -new -x509 -key demoCA/private/cakey.pem -out demoCA/certs/cacert.pem -extensions v3_ca -days  3650 -outform PEM 

Обратите внимание: мы указали в командной строке, что необходимо использовать расширения v3_ca из конфига openssl_cnf. Именно там прописано, что это наш CA. Срок действия 10 лет. Обычное дело для CA. Но можно и больше.

В процессе выпуска сертификата система попросит ввести значения параметров, которые находятся в секции [ req_distinguished_name ] нашего файла openssl.cnf

Теперь приступаем к операциям с токеном. Если токен новый, либо отформатированный со значениями по умолчанию, то PIN пользователя на нем 12345678. Я исхожу из предположения, что это именно так. Иначе необходимо указать корректный PIN пользователя и вообще стараться, чтобы в приведенных ниже примерах имена уже существующих на токене объектов не пересекались с вводимыми.

Первым делом сгенерируем ключевую пару. OpenSSL не умеет выполнять эту операцию на Рутокене, поэтому воспользуемся утилитой pkcs11-tool из пакета OpenSC:

pkcs11-tool --module /path/to/your/librtpkcs11ecp.so --login --pin 12345678 --keypairgen  --key-type GOSTR3410:A --id 303030303031 --label 'client01'


Важное замечание: мы указали id 303030303031. Каждые две цифры этого id ни что иное как ASCII-код символов «0» и «1» соответственно. При операциях с OpenSSL это будет выглядеть как «id=000001»

Генерируем запрос на сертификат:

openssl req -utf8 -new -keyform engine -key 'pkcs11:id=000001' -engine rtengine -out demoCA/newcerts/client01.csr


Если все было сделано верно, то система

Используя этот запрос, подписываем клиентский сертификат (в примере срок действия сертификата составляет 1825 дней. Важно, чтобы этот срок не превышал срок действия вашего корневого сертификата):

openssl ca -utf8 -days  1825 -keyfile demoCA/private/cakey.pem -cert demoCA/certs/cacert.pem -in demoCA/newcerts/client01.csr -outdir demoCA/newcerts -out demoCA/certs/client01.pem


Система отобразит сертификат, спросит о решении подписать его (отвечаем «y»), и о решении сохранить новый сертификат (снова отвечаем «y»).

Сохраняем полученный сертификат на токен:

pkcs11-tool --module /path/to/your/librtpkcs11ecp.so --login --pin 12345678 --id=303030303031 -w demoCA/certs/client01.pem -y cert


Все.

Тестируем созданное «чудо». Для этого подписываем и проверяем подпись фразы «Hello, world!»:

echo Hello,world! | openssl cms -nodetach -sign -signer demoCA/certs/client01.pem -keyform engine -inkey "pkcs11:id=000001" -engine rtengine -binary -noattr -outform PEM | openssl cms -verify -CAfile demoCA/certs/cacert.pem -inform PEM


Если все сделано верно, то система запросит PIN, подпишет сообщение, затем проверит подпись и в случае успеха выведет на экран исходное сообщение и результат проверки («успех»)

Замечание. Возвращаясь к титульной задаче и подписи средствами плагина, необходимо отметить, что по умолчанию результат подписания плагин отдает не в формате PEM, а в формате DER, закодировав в base64. Поэтому для проверки подписи необходимо сначала декодировать из base64, а при проверке указывать входной формат DER.

Успехов!

Библитека signature

В ней реалиован следующий API.

1. Функция инициализации:

int init(
          const char* install_path
);

2. Функция генерации ключа подписи ГОСТ Р 34-10.2001 на Рутокен ЭЦП и создания заявки на сертификат в формате PKCS#10:

char* create_key_request(
	const char* pin,			/* PIN-код токена */
	const char* slot_key_id,		/* СЛОТ:ID ключа */		
	const char* paramset,		/* параметры ключа */
	const char* request_file,		/* файл, в который будет сохранена заявка */
	const char* common_name,	/* понятное имя субъекта */
	const char* org,			/* организация */
	const char* org_unit,		/* подразделение организации */
	const char* city,			/* город */
	const char* region,			/* регион */
	const char* country,		/* страна */
	const char* email,			/* email */
	const char* keyUsages,		/* способы использования ключа, через , */
	const char* extendedKeyUsages	/* расширенные способы использования ключа, через , */	
);

3. Функция записи сертификата на Рутокен ЭЦП

int save_pem_cert(	
       const char* cert,	               /* сертификат в PEM */		
       const char* cert_file,               /*файл с сертификатом в PEM */		
       const char* slot_cert_id,         /* SLOT : ID сертификата */			
       const char* label                     /* label */	 			
);

4. Функция подписи файла в формате PKCS#7 по ГОСТ Р 34.10-2001:

char* sign_file(
	const char* pin,			/* PIN-код токена */
	const char* slot_key_id,		/* СЛОТ:ID ключа */
	const char* slot_cert_id,		/* СЛОТ:ID сертификата */
	const char* file_path, 		/* путь к файлу, который будет подписан */
	int detached			/* тип подписи: 1-отсоединенная, 0-присоединенная */
);

Функция инициализации имеет один параметр — путь к папке, в которую апплет распаковал бинарники. Этого достаточно, для того чтобы наша библиотека Signature могла загрузить OpenSSL, плагин к нему и библиотеку PKCS#11 для работы с Рутокен ЭЦП.

Читайте также:  0x80090304 Интеграция DMDK с КриптоПро и Stunnel

Для взаимодействия с Java-апплетом в библиотеке реализован так же JNI-интерфейс.

Листинг 3. Реализация JNI-интерфейса библиотеки Signature

#include <windows.h>
#include "signature.h"

#include <jni.h>

#ifdef __cplusplus
extern "C" {
#endif

JNIEXPORT jint JNICALL 
Java_Rutoken_OpenSSL_Init
(
	JNIEnv*		env, 
	jclass		cl, 
	jstring		install_path
)
{			
	if(!install_path) 
		return 0;

	return (jint)init((*env).GetStringUTFChars(install_path, false));	
}

JNIEXPORT jint JNICALL 
Java_Rutoken_OpenSSL_SaveCertToToken
(
	JNIEnv*		env, 
	jclass		cl, 
	jstring		cert,			// сертификат в PEM 
	jstring		cert_file,			// файл с сертификатом в PEM 
	jstring		slot_cert_id,		// SLOT : ID сертификата 
	jstring		label			
)
{
	char* pCert		=	NULL;
	char* pCertFile		=	NULL;
	char* pId			=	NULL;
	char* pLabel		=	NULL;

	if( (!cert && !cert_file) || !slot_cert_id)
		return 0;

	if(cert)
		pCert=(char*)(*env).GetStringUTFChars(cert, false);
	if(cert_file)
		pCertFile=(char*)(*env).GetStringUTFChars(cert_file, false);	
	
	pId=(char*)(*env).GetStringUTFChars(slot_cert_id, false);
	
	if(label)
		pLabel=(char*)(*env).GetStringUTFChars(label, false);

	return (jint)save_pem_cert(
		pCert, pCertFile, pId, pLabel);			
}

JNIEXPORT jstring JNICALL 
Java_Rutoken_OpenSSL_CreateKeyRequest
(
	JNIEnv*		env, 
	jclass		cl, 
	jstring		pin,			                // PIN-код токена 
	jstring		slot_key_id,		        // СЛОТ:ID ключа 		
	jstring		paramset,		        // параметры ключа 
	jstring		request_file,		        // файл, в который будет сохранена заявка 
	jstring		common_name,               // понятное имя субъекта 	
	jstring		org,			                // организация 
	jstring		org_unit,			        // подразделение организации 
	jstring		city,			                // город 		
	jstring		region,			        // регион 
	jstring		country,			        // страна 
	jstring		email,			        // почтовый адрес 
	jstring		keyUsages,		        // способы использования ключа, через , 
	jstring		extendedKeyUsages	// расширенные способы использования ключа, через , 				
)
{
	char* pPin				=	NULL;
	char* pSlotKeyId			=	NULL;
	char* pParamset		        =	NULL;
	char* pCommonName		=	NULL;
	char* pOrg				=	NULL;
	char* pOrgUnit				=	NULL;
	char* pCity				=	NULL;
	char* pRegion				=	NULL;
	char* pCountry			=	NULL;
	char* pEmail				=	NULL;
	char* pKeyUsages			=	NULL;
	char* pExtendedKeyUsages	=	NULL;

	char* request				=	NULL;	

	if(!pin || !slot_key_id || !paramset ||
    	    !common_name || !email || !keyUsages || 
	    !extendedKeyUsages) 
	        return NULL;	

	pPin=(char*)(*env).GetStringUTFChars(pin, false);
	pSlotKeyId=(char*)(*env).GetStringUTFChars(slot_key_id, false);
	pParamset=(char*)(*env).GetStringUTFChars(paramset, false);
	pCommonName=(char*)(*env).GetStringUTFChars(common_name, false);
	pOrg=(char*)(*env).GetStringUTFChars(org, false);
	pEmail=(char*)(*env).GetStringUTFChars(email, false);
	pKeyUsages=(char*)(*env).GetStringUTFChars(keyUsages, false);
	pExtendedKeyUsages=(char*)(*env).GetStringUTFChars(extendedKeyUsages, false);

	if(org) 
	                pOrg=(char*)(*env).GetStringUTFChars(org, false);
	if(org_unit)
		pOrgUnit=(char*)(*env).GetStringUTFChars(org_unit, false);
	if(city)
		pCity=(char*)(*env).GetStringUTFChars(city, false);
	if(region)
		pRegion=(char*)(*env).GetStringUTFChars(region, false);
	if(country)
		pCountry=(char*)(*env).GetStringUTFChars(country, false);	

	request=(char*)create_key_request(
		pPin, pSlotKeyId, pParamset, NULL,
		pCommonName, pOrg, pOrgUnit, pCity,
		pRegion, pCountry, pEmail, pKeyUsages,
		pExtendedKeyUsages);

	if(request)
		return (*env).NewStringUTF((const char*)request);			
	else
		return NULL;
}

JNIEXPORT jstring JNICALL 
Java_Rutoken_OpenSSL_SignFile
(
	JNIEnv*		env, 
	jclass		cl, 
	jstring		pin,			// PIN-код токена 
	jstring		slot_key_id,	// СЛОТ:ID ключа 
	jstring		slot_cert_id,	// СЛОТ:ID сертификата 
	jstring		file_path, 		// путь к файлу, который будет подписан 
	jint		detached				
)
{
	char* pPin		=	NULL;
	char* pKeyID	=	NULL;
	char* pCertID	=	NULL;
	char* pFilePath	=	NULL;

	char* signature	=	NULL;

	if(!pin || !slot_key_id || !slot_cert_id || !file_path)
		return NULL;

	pPin=(char*)(*env).GetStringUTFChars(pin, false);
	pKeyID=(char*)(*env).GetStringUTFChars(slot_key_id, false);
	pCertID=(char*)(*env).GetStringUTFChars(slot_cert_id, false);
	pFilePath=(char*)(*env).GetStringUTFChars(file_path, false);

	signature=sign_file(
		pPin,
		pKeyID,		
		pCertID,		
		pFilePath,
		(int)detached);

	if(signature)
		return (*env).NewStringUTF((const char*)signature);			
	else
		return NULL;
}

#ifdef __cplusplus
}
#endif

Листинг 4. Реализация библиотеки Signature

#include <windows.h>

#include <openssl/lhash.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <openssl/crypto.h> /* for CRYPTO_* and SSLeay_version */
#include <openssl/rand.h>
#include <openssl/md4.h>
#include <openssl/des.h>
#include <openssl/engine.h>
#include <openssl/pkcs12.h>
#include <openssl/x509.h>
#include <openssl/x509v3.h>
#include <openssl/cms.h>

#define    CMD_LOAD_CERT_CTRL		(ENGINE_CMD_BASE 5)
#define	CMD_SAVE_CERT_CTRL		(ENGINE_CMD_BASE 6)
#define	CMD_LOGOUT			        (ENGINE_CMD_BASE 8)

#define ENGINE_PKCS11_PIN_MESSAGE		"PKCS#11 token PIN"

char modules_path[MAX_PATH];

/* структура для взаимодействия с engine_pkcs11 */
typedef struct _CERT_PKCS11_INFO {
	const char*	s_slot_cert_id;
	X509*		cert;
	const char*	label;
} CERT_PKCS11_INFO;

/* callback передачи PIN-кода */
int pin_cb(UI *ui, UI_STRING *uis) 
{
	char* pPin=NULL;
	char* pString=NULL;

	pString=(char*)UI_get0_output_string(uis);
	if(!pString) 
		return 0;

	if(!strncmp(pString, ENGINE_PKCS11_PIN_MESSAGE, 
		strlen(ENGINE_PKCS11_PIN_MESSAGE))) {	
		pPin=(char*)UI_get_app_data(ui);
		if(!pPin) 		
			return 0;	

		UI_set_result(ui, uis, pPin);
		return 1;
	} else 
		return 0;
}

/* создает объект расширения сертификата */ 
X509_EXTENSION* 
create_X509_extension
(
		char* name, 
		char *value
)
{
	X509_EXTENSION *ex;
	ex = X509V3_EXT_conf(NULL, NULL, name, value);
	if (!ex)
		return NULL;
	return ex;	
 }

ENGINE* engine_gost=NULL;

ENGINE* LoadEngine(const char* pin) 
{
	ENGINE* engine_pkcs11=NULL;	
	char	enginePkcs11[MAX_PATH];
	char	engineGost[MAX_PATH];
	char	rtPkcs11ECP[MAX_PATH];			
	
	/* динамическая загрузка engine GOST */
	strcpy(engineGost, modules_path);
	strcat(engineGost, "\gost.dll");
	
	engine_gost=ENGINE_by_id("dynamic"); 
	if(!engine_gost) 
		return NULL;	

	if(!ENGINE_ctrl_cmd_string(engine_gost,	"SO_PATH",		engineGost, 0) ||
	    !ENGINE_ctrl_cmd_string(engine_gost,	"ID",			"gost", 0) ||
	    !ENGINE_ctrl_cmd_string(engine_gost,	"LOAD",			NULL, 0))
		return NULL;	

	if(!ENGINE_init(engine_gost)) {
		ENGINE_free(engine_gost);		
		return NULL;
	}

	/* динамическая загрузка engine PKCS11 */
	strcpy(enginePkcs11, modules_path);
	strcat(enginePkcs11, "\pkcs11_gost.dll");

	strcpy(rtPkcs11ECP, modules_path);
	strcat(rtPkcs11ECP, "\rtPKCS11ECP.dll");

	/* WARNING: крайне нужный вызов */
	ENGINE_add(engine_gost);

	engine_pkcs11=ENGINE_by_id("dynamic"); 
	if(!engine_pkcs11) 
		return NULL;				

	if(!ENGINE_ctrl_cmd_string(engine_pkcs11,	"SO_PATH",		enginePkcs11, 0) ||
			!ENGINE_ctrl_cmd_string(engine_pkcs11,	"ID",			"pkcs11_gost", 0) ||
			!ENGINE_ctrl_cmd_string(engine_pkcs11,	"LOAD",			NULL, 0) ||
			!ENGINE_ctrl_cmd_string(engine_pkcs11,	"MODULE_PATH",	rtPkcs11ECP, 0))			
		return NULL;		

	if(pin) {
		if(!ENGINE_ctrl_cmd_string(engine_pkcs11,	"PIN",	pin, 0))
			return NULL;
	}
	
	if(!ENGINE_init(engine_pkcs11)) {
		ENGINE_free(engine_pkcs11);		
		return NULL;
	}

	if(!ENGINE_set_default(engine_pkcs11, ENGINE_METHOD_ALL)) {
		ENGINE_free(engine_pkcs11);	
		return NULL;
	}

	return engine_pkcs11;
}


X509_REQ* 
create_request
(
		EVP_PKEY*	pKey,
		const char* common_name,		/* понятное имя субъекта */
		const char* org,				/* организация */
		const char* org_unit,			/* подразделение организации */
		const char* city,				/* город */
		const char* region,				/* регион */
		const char* country,			/* страна */
		const char* email,				/* почтовый адрес */
		const char* keyUsages,			/* способы использования ключа, через , */
		const char* extendedKeyUsages	/* расширенные способы использования ключа, через ; */
)
{
	X509_REQ* req;
	X509_NAME* subject;	
	BOOL bGoodEmail=TRUE;

	subject	= X509_NAME_new();	
	
	if(common_name && strlen(common_name)>0) {	
		if(!X509_NAME_add_entry_by_NID(
				subject, NID_commonName, 
				MBSTRING_UTF8, (unsigned char*)common_name, 
				-1, -1, 0)) {
			X509_NAME_free(subject);
			return NULL;	
		}
	}

	if(org && strlen(org)>0) 
		if(!X509_NAME_add_entry_by_NID(
				subject, NID_organizationName, 
				MBSTRING_UTF8, (unsigned char*)org, 
				-1, -1, 0)) {
			X509_NAME_free(subject);
			return NULL;	
		}

	if(org_unit && strlen(org_unit)>0) 
		if(!X509_NAME_add_entry_by_NID(
				subject, NID_organizationalUnitName, 
				MBSTRING_UTF8, (unsigned char*)org_unit, 
				-1, -1, 0)) {
			X509_NAME_free(subject);
			return NULL;
		}

	if(city && strlen(city)>0) 
		if(!X509_NAME_add_entry_by_NID(
				subject, NID_localityName, 
				MBSTRING_UTF8, (unsigned char*)city, 
				-1, -1, 0)) {
			X509_NAME_free(subject);
			return NULL;
		}

	if(region && strlen(region)>0) 
		if(!X509_NAME_add_entry_by_NID(
				subject, NID_stateOrProvinceName, 
				MBSTRING_UTF8, (unsigned char*)region, 
				-1, -1, 0)) {
			X509_NAME_free(subject);
			return NULL;
		}

	if(country && strlen(country)>0) 
		if(!X509_NAME_add_entry_by_NID(
				subject, NID_countryName, 
				MBSTRING_UTF8, (unsigned char*)country, 
				-1, -1, 0)) {
			X509_NAME_free(subject);
			return NULL;
		}

	if(email && strlen(email)>0) {
		for (int i=0; i<strlen(email); i  ) 
		if (email[i]&0x80) {			
			bGoodEmail=FALSE;
			break;		
		}	
	
		if(bGoodEmail) {
			if(!X509_NAME_add_entry_by_NID(
					subject, NID_pkcs9_emailAddress, 
					MBSTRING_UTF8, (unsigned char*)email, 
					-1, -1, 0)) {
				X509_NAME_free(subject);
				return NULL;
			}
		}
	}
			
	req=X509_REQ_new();
	if(!req) {
		X509_NAME_free(subject);
		return NULL;
	}
	
	/* установка версии */
	if(!X509_REQ_set_version(req, 0)) {
		X509_REQ_free(req);
		X509_NAME_free(subject);
		return NULL;
	}

	/* установка subject */	
	if(!X509_REQ_set_subject_name(req, subject)) {		
		X509_REQ_free(req);		
		X509_NAME_free(subject);				
		return NULL;
	}

	/* установка открытого ключа */
	if(!X509_REQ_set_pubkey(req, pKey)) {
		X509_REQ_free(req);
		X509_NAME_free(subject);
		return NULL;	
	}
	
	/* "digitalSignature,keyEncipherment" */
	X509_EXTENSION* keyUsageExt = 
		create_X509_extension("keyUsage", (char*)keyUsages); 
	if(!keyUsageExt) {
		X509_REQ_free(req);
		X509_NAME_free(subject);
		return NULL;	
	}
		
	/* "clientAuth,emailProtection" */ 												  
	X509_EXTENSION* extendedKeyUsageExt = 
		create_X509_extension("extendedKeyUsage", (char*)extendedKeyUsages); 
	if(!extendedKeyUsageExt) {
		X509_EXTENSION_free(keyUsageExt);
		X509_REQ_free(req);
		X509_NAME_free(subject);
		return NULL;
	}

	STACK_OF(X509_EXTENSION)* extension_stack = 
		sk_X509_EXTENSION_new_null();
	if(!extension_stack) {
		X509_EXTENSION_free(extendedKeyUsageExt);
		X509_EXTENSION_free(keyUsageExt);
		X509_REQ_free(req);
		X509_NAME_free(subject);
		return NULL;
	}

	sk_X509_EXTENSION_push(extension_stack, keyUsageExt);
	sk_X509_EXTENSION_push(extension_stack, extendedKeyUsageExt);

	if(!X509_REQ_add_extensions(req, extension_stack)) {
		X509_EXTENSION_free(extendedKeyUsageExt);
		X509_EXTENSION_free(keyUsageExt);
		X509_REQ_free(req);
		X509_NAME_free(subject);
		return NULL;
	}

	if(!X509_REQ_sign(req, pKey, EVP_get_digestbyname("md_gost94"))) {
		X509_EXTENSION_free(extendedKeyUsageExt);
		X509_EXTENSION_free(keyUsageExt);
		X509_REQ_free(req);
		X509_NAME_free(subject);
		return NULL;
	}

	sk_X509_EXTENSION_pop_free
		(extension_stack,X509_EXTENSION_free);	
	X509_NAME_free(subject);
	
	return req;	
	
}

ENGINE* engine_pkcs11	=	NULL;

extern "C" __declspec( dllexport )
int init(const char* install_path)
{	
	HMODULE hLibp11	= NULL;
	HMODULE hLibTdl	= NULL;
	char libp11[MAX_PATH];
	char libtdl[MAX_PATH];

	strcpy(modules_path, install_path);
		
	strcpy(libtdl, install_path);
	strcat(libtdl, "\libltdl3.dll"); 	
	hLibTdl=LoadLibraryA(libtdl);
	if(!hLibTdl) 
		return 0;	

	strcpy(libp11, install_path);
	strcat(libp11, "\libp11.dll"); 		
	hLibp11=LoadLibraryA(libp11);
	if(!hLibp11) 
		return 0;	
	
	/* инициализируем OpenSSL */
	ENGINE_load_builtin_engines();					
	OPENSSL_add_all_algorithms_noconf();	

	engine_pkcs11=LoadEngine(NULL); 
	if(!engine_pkcs11) 
		return 0;	

	return 1;
}

/* записать сертификат на токен */
extern "C" __declspec( dllexport ) 
int save_pem_cert
(	
	const char* cert,				/* сертификат в PEM */
	const char* cert_file,			/* файл с сертификатом в PEM */
	const char* slot_cert_id,		        /* SLOT : ID сертификата */
	const char* label				/* label */
)
{
	int				len			  =	0;
	X509*				x509		  =	NULL;
	BIO*				bio_cert	  =	NULL;	
	BIO*				bio_der		  =	NULL;	
	CERT_PKCS11_INFO	cert_info;		

	/* загружаем сертификат */
	if(cert) {
		bio_cert=BIO_new(BIO_s_mem());
		if(!bio_cert) 			
			return 0;			

		if(!BIO_puts(bio_cert, cert)) 
			return 0;				

		x509=PEM_read_bio_X509(bio_cert, NULL, NULL, NULL);
		if(!x509) {
			BIO_free(bio_cert);						
			return 0;
		}		
	} else if(cert_file) {

		bio_cert=BIO_new_file(cert_file, "rb");
		if(!bio_cert)  						
			return 0;			

		x509=PEM_read_bio_X509(bio_cert, NULL, NULL, NULL);
		if(!x509) {
			BIO_free(bio_cert);						
			return 0;
		}		
	} 	

	cert_info.s_slot_cert_id=slot_cert_id;
	cert_info.cert=x509;
	cert_info.label=label;

	if(!ENGINE_ctrl(engine_pkcs11, CMD_SAVE_CERT_CTRL, 0, 
			(void*)&cert_info, NULL)) {		
		return 0;
	}				

	return 1;
}

/* генерирует ключ подписи ГОСТ Р 34-10.2001 на Рутокен ЭЦП и создает заявку в формате PKCS#10 */
extern "C" __declspec( dllexport )
char* create_key_request(
	const char* pin,				/* PIN-код токена */
	const char* slot_key_id,		        /* СЛОТ:ID ключа */		
	const char* paramset,			/* параметры ключа */
	const char* request_file,		        /* файл, в который будет сохранена заявка */
	const char* common_name,		/* понятное имя субъекта */
	const char* org,				/* организация */
	const char* org_unit,			/* подразделение организации */
	const char* city,				/* город */
	const char* region,				/* регион */
	const char* country,			/* страна */
	const char* email,				/* почтовый адрес */
	const char* keyUsages,			/* способы использования ключа, через , */
	const char* extendedKeyUsages	/* расширенные способы использования ключа, через , */	
)			
{
	BIO*			bio_req=NULL;
	BIO*			bio_key=NULL;
	X509_REQ*		pRequest=NULL;	
	BUF_MEM*		pbmReq;
	char*			cRequest=NULL;	
	UI_METHOD*		uim=NULL;
	int				reason=0;	
	EVP_PKEY*		key1=NULL;	
	EVP_PKEY*		newkey=NULL;
	EVP_PKEY_CTX*	ctx=NULL;		

	key1=EVP_PKEY_new();
	if(!key1) {		
		return NULL;
	}
		
	if(!EVP_PKEY_set_type(key1, 811)) {
		EVP_PKEY_free(key1);		
		return NULL;
	}

	ctx=EVP_PKEY_CTX_new(key1, NULL);	
	if(!ctx) {
		EVP_PKEY_free(key1);		
		return NULL;
	}

	if(!EVP_PKEY_keygen_init(ctx)) {		
		EVP_PKEY_CTX_free(ctx);		
		return NULL;
	}

	if(!EVP_PKEY_CTX_ctrl_str(ctx, "paramset", paramset)) {
		EVP_PKEY_CTX_free(ctx);		
		return NULL;
	}

	if(!EVP_PKEY_CTX_ctrl_str(ctx, "slot_key_id", slot_key_id)) {		
		EVP_PKEY_CTX_free(ctx);		
		return NULL;
	}

	if(!EVP_PKEY_CTX_ctrl_str(ctx, "pin", pin)) {		
		EVP_PKEY_CTX_free(ctx);		
		return NULL;
	}		

	if(!EVP_PKEY_keygen(ctx,&newkey)) {
		EVP_PKEY_CTX_free(ctx);	
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;
	}	

	pRequest=create_request(
		newkey, common_name, org,
		org_unit, city, region, country, 
		email, keyUsages, extendedKeyUsages);
	
	if(!pRequest) {		
		EVP_PKEY_free(newkey);
		EVP_PKEY_CTX_free(ctx);		
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;
	}

	bio_req=BIO_new(BIO_s_mem());
	if(!bio_req) {		
		X509_REQ_free(pRequest);
		EVP_PKEY_free(newkey);
		EVP_PKEY_CTX_free(ctx);	
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;
	}

	if(!PEM_write_bio_X509_REQ(bio_req, pRequest)) {		
		BIO_free(bio_req);
		X509_REQ_free(pRequest);		
		EVP_PKEY_free(newkey);
		EVP_PKEY_CTX_free(ctx);	
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;
	}
	
	BIO_get_mem_ptr(bio_req, &pbmReq);	
	if(!pbmReq) {		
		BIO_free(bio_req);
		X509_REQ_free(pRequest);		
		EVP_PKEY_free(newkey);
		EVP_PKEY_CTX_free(ctx);	
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;
	}

	cRequest=(char*)OPENSSL_malloc(pbmReq->length 1);
	if(!cRequest) {			
		BIO_free(bio_req);
		X509_REQ_free(pRequest);		
		EVP_PKEY_free(newkey);
		EVP_PKEY_CTX_free(ctx);	
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;
	}

	memset(cRequest, 0, pbmReq->length 1);

	memcpy(cRequest, pbmReq->data, pbmReq->length); 
	
	BIO_free(bio_req);
	bio_req=NULL;

	if(request_file) {
		bio_req=BIO_new_file(request_file, "wb");
		if(!bio_req) {			
			X509_REQ_free(pRequest);		
			EVP_PKEY_free(newkey);
			EVP_PKEY_CTX_free(ctx);		
			/* logout */ 
			ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
			return NULL;
		}
		if(!PEM_write_bio_X509_REQ(bio_req, pRequest)) {			
			BIO_free(bio_req);
			EVP_PKEY_free(newkey);
			EVP_PKEY_CTX_free(ctx);	
			/* logout */ 
			ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
			return NULL;
		}
		BIO_free(bio_req);
	}
		
	X509_REQ_free(pRequest);	
	EVP_PKEY_free(newkey);
	EVP_PKEY_CTX_free(ctx);	

	/* logout */ 
	ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);

	return cRequest;
}

/* возвращает подпись PKCS#7 по ГОСТ Р 34-10.2001 в формате PEM */
extern "C" __declspec( dllexport )
char* sign_file(
	const char* pin,			/* PIN-код токена */
	const char* slot_key_id,	/* СЛОТ:ID ключа */
	const char* slot_cert_id,	/* СЛОТ:ID сертификата */
	const char* file_path, 		/* путь к файлу, который будет подписан */
	int detached				/* тип подписи: 1-отсоединенная, 0-присоединенная */
) 
{
	BIO*				bio_cert		=	NULL;
	BIO*				in				=	NULL;
	BIO*				out				=	NULL;	
	BUF_MEM*			pbmOut			=	NULL;
	EVP_PKEY*			pKey			=	NULL;
	UI_METHOD*			uim				=	NULL;	
	PKCS7*				p7				=	NULL;
	char*				pSignature		=	NULL;
	int					flags			=	0;
	int					reason			=	0;	
	CERT_PKCS11_INFO	cert_info;
	
	uim=UI_create_method("RutokenECP");
	if(!uim) 		
		return NULL;	
	
	UI_method_set_reader(uim, pin_cb);			

	/* считываем закрытый ключ */
	pKey=ENGINE_load_private_key(engine_pkcs11, slot_key_id, uim, (void*)pin);
	if(!pKey) {			
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;		
	}
	
	memset(&cert_info, 0, sizeof(cert_info));

	cert_info.s_slot_cert_id=slot_cert_id;
	
	/*  считывем сертификат с токена */
	if(!ENGINE_ctrl(engine_pkcs11, CMD_LOAD_CERT_CTRL, 0, &cert_info, NULL)) {
		EVP_PKEY_free(pKey);
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;	
	}
	
	BIO_free(bio_cert);
	
	in=BIO_new_file(file_path, "rb");	
	if(!in) {
		EVP_PKEY_free(pKey);	
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;	
	}

	out=BIO_new(BIO_s_mem()); 
	if(!out) {	
		BIO_free(in);
		EVP_PKEY_free(pKey);	
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;	
	}

	if(detached)
		flags=PKCS7_DETACHED|PKCS7_BINARY;
	else
		flags=PKCS7_BINARY;	

	/* подпись pkcs#7 */	
	p7=PKCS7_sign(cert_info.cert, pKey, NULL, in, flags);	
	if(!p7) {							
		BIO_free(out);	
		BIO_free(in);	
		EVP_PKEY_free(pKey);
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;
	}	

	if(!PEM_write_bio_PKCS7(out, p7)) {					
		PKCS7_free(p7);
		BIO_free(out);		
		BIO_free(in);
		EVP_PKEY_free(pKey);	
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;		
	}
	
	BIO_get_mem_ptr(out, &pbmOut);	
	if(!pbmOut) {
		PKCS7_free(p7);
		BIO_free(out);		
		BIO_free(in);
		EVP_PKEY_free(pKey);
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;		
	}

	pSignature=(char*)OPENSSL_malloc(pbmOut->length 1);
	if(!pSignature) {	
		PKCS7_free(p7);
		BIO_free(out);		
		BIO_free(in);
		EVP_PKEY_free(pKey);
		/* logout */ 
		ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
		return NULL;
	}

	memset(pSignature, 0, pbmOut->length 1);

	memcpy(pSignature, 
		pbmOut->data, pbmOut->length);	
		
	CRYPTO_cleanup_all_ex_data();

	PKCS7_free(p7);
	BIO_free(out);		
	BIO_free(in);
	EVP_PKEY_free(pKey);	

	/* logout */ 
	ENGINE_ctrl(engine_pkcs11, CMD_LOGOUT, 0, (void*)slot_key_id, NULL);
	

	return pSignature;	
}

Желающие могут сделать эту библиотеку кроссплатформенной (заменив LoadLibrary на соответствующий вызов), расширить эту библиотеку нужными им функциями.

Читайте также:  Опыт применения технологии Рутокен для регистрации и авторизации пользователей в системе (часть 2) | ProHoster

Подготовительный этап

Мы реализовали его внутри функцииinit_pkcs11:

#include "utils.h"

CK_FUNCTION_LIST_PTR functionList;                 // Указатель на список функций PKCS#11, хранящийся в структуре CK_FUNCTION_LIST
CK_FUNCTION_LIST_EXTENDED_PTR functionListEx;      // Указатель на список функций расширения PKCS#11, хранящийся в структуре CK_FUNCTION_LIST_EXTENDED
static HMODULE module;

int init_pkcs11()
{
    CK_C_GetFunctionList getFunctionList;              // Указатель на функцию C_GetFunctionList
    CK_C_EX_GetFunctionListExtended getFunctionListEx; // Указатель на функцию C_EX_GetFunctionListExtended

    /* Параметры для инициализации библиотеки: разрешаем использовать объекты синхронизации операционной системы */
    CK_C_INITIALIZE_ARGS initArgs = { NULL_PTR, NULL_PTR, NULL_PTR, NULL_PTR, CKF_OS_LOCKING_OK, NULL_PTR };

    CK_RV rv;                      // Код возврата PKCS#11 функций
    int errorCode = 1;                                 // Флаг ошибки

    /*************************************************************************
    * Выполнить действия для начала работы с библиотекой PKCS#11             *
    *************************************************************************/
    printf("Initialization...n");

    /*************************************************************************
    * Загрузить библиотеку                                                   *
    *************************************************************************/
    module = LoadLibrary(PKCS11_LIBRARY_DIR "/" PKCS11ECP_LIBRARY_NAME);
    CHECK(" LoadLibrary", module != NULL, exit);

    /*************************************************************************
    * Получить адрес функции запроса структуры с указателями на функции      *
    *************************************************************************/
    getFunctionList = (CK_C_GetFunctionList)GetProcAddress(module, "C_GetFunctionList");
    CHECK(" GetProcAddress (C_GetFunctionList)", getFunctionList != NULL, unload_pkcs11);

    /*************************************************************************
    * Получить адрес функции запроса структуры с указателями на функции      *
    * расширения стандарта PKCS#11                                           *
    *************************************************************************/
    getFunctionListEx = (CK_C_EX_GetFunctionListExtended)GetProcAddress(module, "C_EX_GetFunctionListExtended");
    CHECK(" GetProcAddress (C_EX_GetFunctionListExtended)", getFunctionList != NULL, unload_pkcs11);

    /*************************************************************************
    * Получить структуру с указателями на функции                            *
    *************************************************************************/
    rv = getFunctionList(&functionList);
    CHECK_AND_LOG(" Get function list", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

    /*************************************************************************
    * Получить структуру с указателями на функции расширения стандарта       *
    *************************************************************************/
    rv = getFunctionListEx(&functionListEx);
    CHECK_AND_LOG(" Get function list extended", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

    /*************************************************************************
    * Инициализировать библиотеку                                            *
    *************************************************************************/
    rv = functionList->C_Initialize(&initArgs);
    CHECK_AND_LOG(" C_Initialize", rv == CKR_OK, rvToStr(rv), unload_pkcs11);

    errorCode = 0;

    /*************************************************************************
    * Выгрузить библиотеку из памяти                                         *
    *************************************************************************/
unload_pkcs11:
    if (errorCode)
        CHECK_RELEASE(" FreeLibrary", FreeLibrary(module), errorCode);
exit:
    return errorCode;
}

Здесь происходит следующее:

  1. В память процесса подгружается PKCS#11-библиотека, хранящаяся по пути PKCS11ECP_LIBRARY_NAME, с помощью функции LoadLibrary (стандартная функция для Windows-систем, для Linux-систем определена обертка).

  2. Далее из библиотеки вытаскиваются указатели на функции C_GetFunctionList и C_EX_GetFunctionListExtended. Первая функция определена в стандарте PKCS#11 и позволяет получить структуру указателей на функции библиотеки. Вторая — является специфичной для библиотеки rtpkcs11ecp и позволяет получить схожую структуру указателей на функции расширения библиотеки. О функциях расширения мы поговорим позже.

  3. Потом мы вызываем полученную функцию C_GetFunctionList и получаем уже саму структуру указателей на функции.

  4. С помощью функции C_Initialize инициализируется загруженная библиотека. Функция C_Initialize в качестве аргумента принимает параметры инициализации библиотеки. Подробнее о них можно почитать здесь, для нас же важен флаг CKF_OS_LOCKING_OK. Его необходимо использовать, если мы хотим использовать библиотеку в нескольких потоках. В нашем примере мы могли бы опустить этот флаг.

Поиск объектов и создание сырой подписи

В прошлом разделе мы сгенерировали ключевую пару. На этот раз будем считать, что у нас нет хендлов на сгенерированные ключи, но мы знаем их идентификатор – CKA_ID. Попробуем найти объект закрытого ключа на токене:

int findObjects(CK_SESSION_HANDLE session,         // Хэндл открытой сессии
                CK_ATTRIBUTE_PTR attributes,       // Массив с шаблоном для поиска
                CK_ULONG attrCount,                // Количество атрибутов в массиве поиска
                CK_OBJECT_HANDLE objects[],        // Массив для записи найденных объектов
                CK_ULONG* objectsCount             // Количество найденных объектов
                       )
{
    CK_RV rv;                                           // Код возврата. Могут быть возвращены только ошибки, определенные в PKCS#11
    int errorCode = 1;                                  // Флаг ошибки

    /*************************************************************************
    * Инициализировать операцию поиска                                       *
    *************************************************************************/
    rv = functionList->C_FindObjectsInit(session, attributes, attrCount);
    CHECK_AND_LOG("  C_FindObjectsInit", rv == CKR_OK, rvToStr(rv), exit);

    /*************************************************************************
    * Найти все объекты, соответствующие критериям поиска                    *
    *************************************************************************/

    rv = functionList->C_FindObjects(session, objects, *objectsCount, objectsCount);
    CHECK_AND_LOG("  C_FindObjects", rv == CKR_OK, rvToStr(rv), find_final);

    errorCode = 0;

    /*************************************************************************
    * Деинициализировать операцию поиска                                     *
    *************************************************************************/
find_final:
    rv = functionList->C_FindObjectsFinal(session);
    CHECK_RELEASE_AND_LOG("  C_FindObjectsFinal", rv == CKR_OK, rvToStr(rv), errorCode);

exit:
    return errorCode;
}

int find_private_key(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE_PTR privateKey)
{
        CK_BYTE keyPairIdGost2022_256[] = { "GOST R 34.10-2022 (256 bits) sample key pair ID (Aktiv Co.)" };
        CK_OBJECT_CLASS privateKeyObject = CKO_PRIVATE_KEY;

        CK_ATTRIBUTE privateKeyTemplate[] =
        {
                { CKA_CLASS, &privateKeyObject, sizeof(privateKeyObject)},              // Класс - закрытый ключ
                { CKA_ID, &keyPairIdGost2022_256, sizeof(keyPairIdGost2022_256) - 1},   // Идентификатор ключевой пары (должен совпадать у открытого и закрытого ключей)
        };

        CK_ULONG cnt = 1;

        CK_RV rv;
        int errorCode = 1;

        rv = findObjects(session, privateKeyTemplate,
        arraysize(privateKeyTemplate), privateKey, &cnt);

        CHECK(" findObjects", rv == 0, exit);
        CHECK_AND_LOG(" Checking number of keys found", cnt == 1, "No objects foundn", exit);

        errorCode = 0;
exit:
        return errorCode;
}

Данный пример иллюстрирует работу с функцией поиска объекта по заданным атрибутам. Как можно заметить, операция поиска объекта на токене является составной и работа с ней сводится как минимум к вызову трёх функций: C_FindObjectsInit, C_FindObjects, C_FindObjectsFinal.

Функция C_FindObjects может вызываться по несколько раз, и каждый раз она будет возвращать следующие объекты поиска. Предпоследний аргумент функции C_FindObjects задаёт размер выходного массива объектов. А последний — количество полученных объектов после очередного поиска.

Поиск приватного ключа производился по атрибуту его класса и идентификатору. Мы рассчитывали, что найдётся хотя бы один объект по заданному шаблону и брали любой из них. Используем найденный ключ для вычисления сырой подписи:

int sign(CK_SESSION_HANDLE session, CK_OBJECT_HANDLE privateKey)
{    
    /* OID алгоритма хеширования ГОСТ Р 34.11-2022(256)                     */
    CK_BYTE parametersGostR3411_256[] = {0x06, 0x08, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x02};

    /* Механизм подписи/проверки подписи по алгоритму ГОСТ Р 34.10-2022(256) и хешированием по алгоритму ГОСТ Р 34.11-2022(256) */
    CK_MECHANISM gost3410SignWith3411Mech = { CKM_GOSTR3410_WITH_GOSTR3411_12_256, ¶metersGostR3411_256, sizeof(parametersGostR3411_256)};

    CK_BYTE data[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
               0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
               0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
               0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07 };

    CK_BYTE_PTR signature;                            // Указатель на буфер, содержащий цифровую подпись для данных
    CK_ULONG signatureSize;                           // Размер буфера, содержащего цифровую подпись для данных, в байтах

    CK_RV rv;
    int errorCode = 1;

    /*************************************************************************
    * Вычислить подпись от данных                                            *
    *************************************************************************/
    printf(" Signing data...n");

    /*************************************************************************
    * Инициализировать операцию подписи данных                               *
    *************************************************************************/
    rv = functionList->C_SignInit(session, &gost3410SignWith3411Mech, privateKey);
    CHECK_AND_LOG("  >C_SignInit", rv == CKR_OK, rvToStr(rv), exit);

    /*************************************************************************
    * Определить размер данных подписи                                       *
    *************************************************************************/
    rv = functionList->C_Sign(session, data, sizeof(data), NULL_PTR, &signatureSize);
    CHECK_AND_LOG("  C_Sign(get size)", rv == CKR_OK, rvToStr(rv), exit);

    /*************************************************************************
    * Подписать данные                                                       *
    *************************************************************************/

    signature = (CK_BYTE*)malloc(signatureSize * sizeof(CK_BYTE));
    CHECK("  Memory allocation for signature", signature != NULL, exit);

    rv = functionList->C_Sign(session, data, sizeof(data), signature, &signatureSize);
    CHECK_AND_LOG("  C_Sign (signing)", rv == CKR_OK, rvToStr(rv), free_signature);


    /*************************************************************************
    * Распечатать буфер, содержащий подпись                                  *
    *************************************************************************/
    printf("  Signature buffer is: n");
    printHex(signature, signatureSize);
    printf("Data has been signed successfully.n");

    errorCode = 0;

free_signature:
    free(signature);
exit:
    return errorCode;
}

В этом примере подпись и хеш можно считать одновременно. Такой вариант рекомендован для безопасности: цепочку “хеширование-подпись” лучше не «разрывать». Чтобы показать, какой алгоритм хеширования использовать, мы передали его OID.

Читайте также:  КриптоПро ЭЦП Browser plug in - Скачать плагин для браузера

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

Создание объектов — на примере генерации ключевых пар

В первую очередь, напишем функцию, которая будет генерировать ключевую пару ГОСТ Р 34.10-2022 256 бит на указанном слоте:

int gen_gost_key_pair(CK_SESSION_HANDLE session)
{
    CK_KEY_TYPE keyTypeGostR3410_2022_256 = CKK_GOSTR3410;
    CK_BYTE keyPairIdGost2022_256[] = { "GOST R 34.10-2022 (256 bits) sample key pair ID (Aktiv Co.)" };
    CK_BYTE parametersGostR3410_2022_256[] = { 0x06, 0x07, 0x2a, 0x85, 0x03, 0x02, 0x02, 0x23, 0x01 };
    CK_BYTE parametersGostR3411_2022_256[] = { 0x06, 0x08, 0x2a, 0x85, 0x03, 0x07, 0x01, 0x01, 0x02, 0x02 };
    CK_BBOOL attributeTrue = CK_TRUE;
    CK_BBOOL attributeFalse = CK_FALSE;

    CK_OBJECT_CLASS publicKeyObject = CKO_PUBLIC_KEY;

    CK_ATTRIBUTE publicKeyTemplate[] =
    {
        { CKA_CLASS, &publicKeyObject, sizeof(publicKeyObject)},                                        // Класс - открытый ключ
        { CKA_ID, &keyPairIdGost2022_256, sizeof(keyPairIdGost2022_256) - 1 },                          // Идентификатор ключевой пары (должен совпадать у открытого и закрытого ключей)
        { CKA_KEY_TYPE, &keyTypeGostR3410_2022_256, sizeof(keyTypeGostR3410_2022_256) },                // Тип ключа - ГОСТ Р 34.10-2022(256)
        { CKA_TOKEN, &attributeTrue, sizeof(attributeTrue)},                                            // Ключ является объектом токена
        { CKA_PRIVATE, &attributeFalse, sizeof(attributeFalse)},                                        // Ключ доступен без аутентификации на токене
        { CKA_GOSTR3410_PARAMS, parametersGostR3410_2022_256, sizeof(parametersGostR3410_2022_256) },   // Параметры алгоритма ГОСТ Р 34.10-2022(256)
        { CKA_GOSTR3411_PARAMS, parametersGostR3411_2022_256, sizeof(parametersGostR3411_2022_256) }    // Параметры алгоритма ГОСТ Р 34.11-2022(256)
    };

    CK_OBJECT_CLASS privateKeyObject = CKO_PRIVATE_KEY;

    CK_ATTRIBUTE privateKeyTemplate[] =
    {
        { CKA_CLASS, &privateKeyObject, sizeof(privateKeyObject)},                                      // Класс - закрытый ключ
        { CKA_ID, &keyPairIdGost2022_256, sizeof(keyPairIdGost2022_256) - 1 },                          // Идентификатор ключевой пары (должен совпадать у открытого и закрытого ключей)
        { CKA_KEY_TYPE, &keyTypeGostR3410_2022_256, sizeof(keyTypeGostR3410_2022_256) },                // Тип ключа - ГОСТ Р 34.10-2022(256)
        { CKA_TOKEN, &attributeTrue, sizeof(attributeTrue)},                                            // Ключ является объектом токена
        { CKA_PRIVATE, &attributeTrue, sizeof(attributeTrue)},                                          // Ключ доступен только после аутентификации на токене
        { CKA_GOSTR3410_PARAMS, parametersGostR3410_2022_256, sizeof(parametersGostR3410_2022_256) },   // Параметры алгоритма ГОСТ Р 34.10-2022(256)
        { CKA_GOSTR3411_PARAMS, parametersGostR3411_2022_256, sizeof(parametersGostR3411_2022_256) }    // Параметры алгоритма ГОСТ Р 34.11-2022(256)
    };

    CK_OBJECT_HANDLE privateKey;                      // Хэндл закрытого ключа ГОСТ (ключевая пара для подписи и шифрования)    
    CK_OBJECT_HANDLE publicKey;                       // Хэндл открытого ключа ГОСТ (ключевая пара для подписи и шифрования)    

    CK_MECHANISM gostR3410_2022_256KeyPairGenMech = { CKM_GOSTR3410_KEY_PAIR_GEN, NULL_PTR, 0 };

    CK_RV rv;   
    int errorCode = 1;

    /*************************************************************************
    * Генерация ключевой пары на токене                                      *
    *************************************************************************/
    rv = functionList->C_GenerateKeyPair(session, &gostR3410_2022_256KeyPairGenMech, 
    publicKeyTemplate, arraysize(publicKeyTemplate),
    privateKeyTemplate, arraysize(privateKeyTemplate),
    &publicKey, &privateKey);
    CHECK_AND_LOG(" C_GenerateKeyPair", rv == CKR_OK, rvToStr(rv), exit);

    errorCode = 0;
    printf("Gost key pair generated successfullyn");

exit:
    return errorCode;
}

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

Теперь перейдём к объектам. Внутри функции gen_gost_key_pair происходит создание двух объектов на токене: открытого и закрытого ключей. Вот, что стандарт PKCS#11 говорит про объекты:

Cryptoki recognizes a number of classes of objects, as defined in the CK_OBJECT_CLASS data type. An object consists of a set of attributes, each of which has a given value. Each attribute that an object possesses has precisely one value.

То есть стандарт не даёт явное определение объекта, но из того, что там написано, мы знаем:

Также в стандарте представлена классификация объектов:

Иерархия PKCS#11 объектов
Иерархия PKCS#11 объектов

Заголовок диаграммы определяет класс объекта, а то что ниже — некоторые из его атрибутов. Видно, что объектом может являться некоторый механизм (о механизмах мы поговорим позже), встроенные функции токена (Hardware feature), некоторые данные на токене (Storage). В нашем случае мы выполнили действие с данными.

Название всех атрибутов начинается с префикса “CKA_”. Одним из самых важных атрибутов является CKA_ID. Он задаёт идентификатор объекта и используется для связи ключевых пар и сертификатов. Атрибут CKA_TOKEN является булевым и показывает, является ли объект — объектом токена.

Атрибут CKA_PRIVATE тоже является булевым и определяет нужна ли предварительная аутентификация для получения доступа к объекту. Атрибут CKA_ID — задаёт шестнадцатеричный идентификатор объекта. Также есть булевые атрибуты CKA_MODIFIABLE, CKA_COPYABLE, CKA_DESTROYABLE для более тонкой настройки доступа к объекту.

Объекты данных могут быть самыми разнообразными: асимметричные ключи, симметричные ключи, сертификаты, просто какая-либо информация на токене. В нашем примере мы создали два объекта, но сделали это неявно с помощью механизма генерации ключей. C_GenerateKeyPair приняла на вход механизм генерации ключевой пары, шаблоны открытого и закрытого ключа и с помощью механизма сгенерировала объекты ключевой пары (publicKey и privateKey).

Оцените статью
ЭЦП Эксперт
Добавить комментарий