32,50
рейтинг
26 декабря 2011 в 18:34

Разработка → Электронная подпись в браузере с помощью OpenSSL и СКЗИ Рутокен ЭЦП

UPDATE. Готовое решение для электронной подписи в браузере — Рутокен Плагин

Потребность в решениях, помогающих реализовать электронную подпись в «браузере», возрастает. Главные требования к таким решениям — поддержка российких криптоалгоритмов, обеспечение безопасности ключа и нормальное usability. В данном топике мы напишем браузерный криптографический java-апплет, в который интегрирован OpenSSL ГОСТ c модулем поддержки Рутокен ЭЦП. Этот апплет не требует установки какого-либо клиентского софта (кроме java-машины, конечно) и позволяет подписывать файлы через браузер в формате PKCS#7 с ипользованием аппаратной реализации российских криптографических стандартов на «борту» USB-токена Рутокен ЭЦП. Для демонстрации в топике будет дан пример HTML-страницы, использующей данный апплет. На странице можно сгенерить ключ внутри токена, создать заявку PKCS#10 на сертификат для этого ключа, получить тестовый сертификат, записать его на токен, подписать файл.

Архитектура решения представлена на рисунке:

image

Рутокен ЭЦП — это компактное USB-устройство, внутри которого находятся защищенная память и микроконтроллер, реализующий российские криптографические стандарты. Так как операции проводятся на «борту» токена, то ключ пользователя невозможно украсть.

OpenSSL — это кроссплатформенный пакет с открытым программным кодом, в котором поддерживаются большинство современных криптографических алгоритмов, форматы PKCS, CMS, S/MIME, протоколы SSL/TLS и т.п. Начиная с версии 1.0.0 OpenSSL обеспечивает полнофункциональную поддержку российских криптоалгоритмов.

Про плагин, обеспечивающий поддержку Рутокен ЭЦП в OpenSSL, можно почитать тут же на хабре
habrahabr.ru/blogs/infosecurity/134725.

Библиотека Signature — это динамическая библиотека, «надстройка» над OpenSSL, которая инкапсулирует в себе вызовы OpenSSL. Предоставляет JNI-интерфейс для использования в Java. Эту библиотеку мы напишем.

Криптографический java-апплет



Делаем проект, как это делается в Eclipse. Добавляем в проект package Rutoken, а в него добавляем класс OpenSSL, отвечающий за взаимодействие с native-библиотекой Signature, и класс OpenSSL_Wrapper, который наследует от applet.

ВНИМАНИЕ! Имена лучше оставить без изменений, так как иначе придется менять имена у функций интерфейса JNI библиотеки Signature.

Все необходимые библиотеки Java-апплет будет хранить внутри JAR-архива как ресурсы, а при загрузке на web-странице будет распаковывать их в папку %TEMP% и уже оттуда использовать, поэтому добавляем в package необходимые бинарники как ресурсы.

Полный список бинарников:

libeay32.dll — библиотека OpenSSL
gost.dll — модуль поддержки алгоритмов ГОСТ в OpenSSL
pkcs11_gost.dll — модуль поддержки Рутокен ЭЦП в OpenSSL
signature.dll — «надстройка» над OpenSSL, предоставляющая JNI-интерфейс
rtPKCS11ECP.dll — библиотека PKCS#11 для Рутокен ЭЦП (распространяется вендором)
libp11.dll — «надстройка» над библиотекой PKCS#11
libltdl3.dll — дополнительная библиотека

Собранные библиотеки для платформы win32 можно скачать по ссылке www.rutoken.ru/download/software/forum/openssl-rutoken-win32.zip

Листинг 1. Класс OpenSSL

package Rutoken;

import javax.swing.*;


public class OpenSSL {
	static {		
		try 
		{			
			System.load(OpenSSL_Wrapper.temp + "/libeay32.dll");			
			System.load(OpenSSL_Wrapper.temp + "/signature.dll");
		} 
		catch(Exception ex) 
		{
			JOptionPane.showMessageDialog(null, ex.getMessage());
		}        
        }
	

	public native static int Init
	(						
		String	install_path	// путь к папке установки		
	);

	// записывает сертификат на токен 	
	public native static int SaveCertToToken
	(
		String cert,		// сертификат в PEM 
		String cert_file,		// файл с сертификатом в PEM 
		String slot_cert_id,		// SLOT : ID сертификата 
		String label		// label 
	);

	// генерирует ключ подписи ГОСТ Р 34-10.2001 на Рутокен ЭЦП и создает заявку в формате PKCS#10 	
	public native static String CreateKeyRequest
	(
		String pin,		// PIN-код токена 
		String slot_key_id,		// СЛОТ:ID ключа 		
		String paramset,		// параметры ключа 
		String request_file,		// файл, в который будет сохранена заявка 
		String common_name,	// понятное имя субъекта 
		String org,		// организация 
		String org_unit,		// подразделение организации 
		String city,		// город 		
		String region,		// регион 
		String country,		// страна 
		String email,		// почтовый адрес 
		String keyUsages,		// способы использования ключа, через , 
		String extendedKeyUsages	// расширенные способы использования ключа, через , 	
	);

	// возвращает подпись PKCS#7 по ГОСТ Р 34.10-2001 в формате PEM 		
	public native static String SignFile
	(
	        String pin,		        // PIN-код токена 
		String slot_key_id,	        // СЛОТ:ID ключа 
		String slot_cert_id,	        // СЛОТ:ID сертификата 
		String file_path, 		// путь к файлу, который будет подписан 
		int detached		        // тип подписи: 1-отсоединенная, 0-присоединенная 
	);
}



Листинг 2. Класс OpenSSL_Wrapper


package Rutoken;

import java.applet.Applet;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import javax.swing.JOptionPane;



public class OpenSSL_Wrapper extends Applet {
	
	public static String temp=System.getProperty("java.io.tmpdir")+"{rutoken-ecp-1983-8564}";
	
	OpenSSL nativeObj;
	
	public void CopyResToFile(String resource, String file)
	{		
		try
		{
			File f=new File(file);
			InputStream inputStream= getClass().getResourceAsStream(resource);
		
			OutputStream out=new FileOutputStream(f);
			byte buf[]=new byte[1024];
			int len;
			while((len=inputStream.read(buf))>0)
				out.write(buf,0,len);
			out.close();
			inputStream.close();			
		}
		catch (IOException e)
		{}
	}
	
	public void init()
	{		
                File f = new File(temp);
                f.mkdir();        		
        
                // openssl
                CopyResToFile("libeay32.dll", temp+"/libeay32.dll");
                // openssl plugin
                CopyResToFile("gost.dll", temp+"/gost.dll");
                // openssl plugin для Рутокен ЭЦП
	        CopyResToFile("pkcs11_gost.dll", temp+"/pkcs11_gost.dll");
	        // библиотека PKCS#11 для Рутокен ЭЦП
	        CopyResToFile("rtPKCS11ECP.dll", temp+"/rtPKCS11ECP.dll");
	        // libtool
	        CopyResToFile("libltdl3.dll", temp+"/libltdl3.dll");
	        // openssl wrapper
	        CopyResToFile("signature.dll", temp+"/signature.dll");		
	        // PKCS#11 wrapper
	        CopyResToFile("libp11.dll", temp+"/libp11.dll");
		
	        if(1!=OpenSSL.Init(temp)) {
	                    JOptionPane.showMessageDialog(null, "Ошибка при инициализации OpenSSL!");
	        }
	}
			

	// записывает сертификат на токен 	
	public int SaveCertToToken
	(
		String cert,		// сертификат в PEM 
		String cert_file,		// файл с сертификатом в PEM 
		String slot_cert_id,		// SLOT : ID сертификата 
		String label		// label 
	)
	{
		return OpenSSL.SaveCertToToken(cert, cert_file, slot_cert_id, label);
	}

	// генерирует ключ подписи ГОСТ Р 34-10.2001 на Рутокен ЭЦП и создает заявку в формате PKCS#10 	
	public String CreateKeyRequest
	(
		String pin,		// PIN-код токена 
		String slot_key_id,		// СЛОТ:ID ключа 		
		String paramset,		// параметры ключа 
		String request_file,		// файл, в который будет сохранена заявка 
		String common_name,	// понятное имя субъекта 
		String org,	                // организация 
		String org_unit,		// подразделение организации 
		String city,		// город 		
		String region,		// регион 
		String country,		// страна 
		String email,		// почтовый адрес 
		String keyUsages,		// способы использования ключа, через , 
		String extendedKeyUsages	// расширенные способы использования ключа, через , 	
	)
	{					
		return OpenSSL.CreateKeyRequest(
				pin, slot_key_id, paramset, request_file, 
				common_name, org, org_unit, city, region, 
				country, email, keyUsages, extendedKeyUsages);								
	}

	// возвращает подпись PKCS#7 по ГОСТ Р 34.10-2001 в формате PEM 		
	public String SignFile(
		String pin,		// PIN-код токена 
		String slot_key_id,		// СЛОТ:ID ключа 
		String slot_cert_id,		// СЛОТ:ID сертификата 
		String file_path, 		// путь к файлу, который будет подписан 
		int detached		// тип подписи: 1-отсоединенная, 0-присоединенная 
	)
	{			
                        return OpenSSL.SignFile(pin, slot_key_id, slot_cert_id, file_path, detached);
	}		   	
}


Package Rutoken следует экспортировать в JAR-архив, а затем этот архив подписать с помощью утилиты jarsigner.

Библитека 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 для работы с Рутокен ЭЦП.

Для взаимодействия с 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 на соответствующий вызов), расширить эту библиотеку нужными им функциями.

Демонстрационная HTML-cтраница



Листинг 5. Пример страницы HTML, на которой JavaScript использует наш апплет

<html><head>
<title>Демо-страница элетронной подписи</title>

<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="keywords" content="">

</head>

<body bgcolor="#ffffff">
<applet code="Rutoken.OpenSSL_Wrapper.class" archive="Rutoken.jar" height="0" width="0">
<table>
  <tbody><tr width="100%" bgcolor="#FFF8DC" valign="top">
    <td align="right" height="40" valign="middle" width="10000"><span class="warning">
    Для работы страницы требуется plugin JAVA. Для установки JAVA перейдите по 
        <a href="http://www.java.com/ru/download/windows_xpi.jsp?locale=ru">ссылке...</a></span>
    </td>
 </tr>
</tbody></table>
</applet>

<center>


<script type="text/javascript">
var loaded=0;

function createKeyRequest()
{
    var request;    
    var pin;  

    if(!document.getElementById("common_name").value) {        
        alert("Ошибка!\nВведите понятное имя!.")
        return;
    }
    
    if(!document.getElementById("email").value) {        
        alert("Ошибка!\nВведите email!.")
        return;
    }
        
    if(!document.getElementById("pin").value) {
        alert("Ошибка!\nВведите PIN!.")
        return;
    }       
                                                              
     pin=document.getElementById("pin").value;     
    
     if(loaded==0) {
        var applet = "<applet id='RutokenApplet' style='visibility: hidden' name='RutokenApplet' archive='Rutoken.jar' code='Rutoken.OpenSSL_Wrapper.class' width='0' height='0'></applet>"; 
        var body = document.getElementsByTagName("body")[0]; 
        var div = document.createElement("div"); 
        div.innerHTML = applet; 
        body.appendChild(div); 
        loaded=1;
    }

    request = document.RutokenApplet.CreateKeyRequest(
           pin,
            document.getElementById("keyId").value,
            "A",
            null,
            document.getElementById("common_name").value, 
            document.getElementById("org").value, 
            document.getElementById("org_unit").value, 
            document.getElementById("city").value, 
            document.getElementById("region").value, 
            document.getElementById("country").value, 
            document.getElementById("email").value, 
            "digitalSignature,keyEncipherment",
            "clientAuth,emailProtection");

    if(!request) {
        alert("Не удалось создать заявку на сертификат!");
        return;
    }    
        
    document.getElementById('request').value=request;                               
}


function saveCertToToken() 
{
    if (loaded == 0) {
        var applet = "<applet id='RutokenApplet' style='visibility: hidden' name='RutokenApplet' archive='Rutoken.jar' code='Rutoken.OpenSSL_Wrapper.class' width='0' height='0'></applet>";
        var body = document.getElementsByTagName("body")[0];
        var div = document.createElement("div");
        div.innerHTML = applet;
        body.appendChild(div);
        loaded = 1;
    }
  
    if(!document.getElementById('certarea').value)
    {
        alert("Не установлен сертификат для записи!");
        return;
    }

    if (!document.RutokenApplet.SaveCertToToken(document.getElementById('certarea').value,
            null, document.getElementById("keyId").value, null)) {
        alert("Не удалось записать сертификат на токен!");
        return;
    } else {
        alert("Успех!");
    }

}

function signFile() {
    var signature;

    if (!document.getElementById("pin").value) {
        alert("Ошибка!\nВведите PIN!.")
        return;
    }

    if (!document.getElementById('fileForSign').value) {
        alert("Выберите файл для подписи!");
        return;
    }   
 
    if (loaded == 0) {
        var applet = "<applet id='RutokenApplet' style='visibility: hidden' name='RutokenApplet' archive='Rutoken.jar' code='Rutoken.OpenSSL_Wrapper.class' width='0' height='0'></applet>";
        var body = document.getElementsByTagName("body")[0];
        var div = document.createElement("div");
        div.innerHTML = applet;
        body.appendChild(div);
        loaded = 1;
    }    

    signature=document.RutokenApplet.SignFile(
            document.getElementById("pin").value,
            document.getElementById("keyId").value,
            document.getElementById("keyId").value,
            document.getElementById('fileForSign').value, 
            1);
            
    if(!signature) {
        alert("Не удалось подписать файл!");
        return;
    } else {
        alert("Успех!");
        document.getElementById('signature').value = signature;
    }    

}

</script>

<table align="center" border="0" cellpadding="0" cellspacing="0" width="760">
<tbody><tr valign="top">
<td width="8""> </td>
<td class="m9" align="center" width="125">
</td>
<td><table align="center" width="600"><tbody><tr valign="top"><td width="20"> </td>
<td>

<!--BEGIN CONTENT-->

<table border="0" cellpadding="5" cellspacing="0">
	<tbody><tr valign="top">
<td><table bgcolor="" border="0" cellpadding="1" cellspacing="0"><tbody><tr><td>

<table border="0" cellpadding="8" cellspacing="0" width="400">
<tbody>
<tr>
<td valign="top" width="400">
<table border="0" cellpadding="0" cellspacing="0">
  <tbody>
  <tr>
    <td colspan="2" align="center" height="6"><h3>Получение сертификата</h3>
    </td>
  </tr>
  <tr>
    <td colspan="2" height="10">
    </td>
  </tr>          
  <tr>
    <td colspan="2" align="center" height="6"><h4>Укажите информацию о себе:</h4>
    </td>
  </tr>          
  <tr>
    <td id="locNameAlign" align="left"><span id="spnNameLabel"><locid id="locNameLabel"><font size="-1">ФИО*:</font></locid></span>
    </td>
    <td><input id="common_name" maxlength="64" size="38" name="tbCommonName">
    </td>
  </tr>
  <tr>
    <td colspan="2" height="18">
    </td>
  </tr>
  <tr>
    <td id="locEmailAlign" align="left"><span id="spnEmailLabel"><locid id="locEmailLabel"><font size="-1">Email*:</font></locid></span>
    </td>
    <td><input id="email" maxlength="128" size="38" name="tbEmail">
    </td>
  </tr>
  <tr>
    <td colspan="2" height="18">
    </td>
  </tr>  
    <tr><td id="locCompanyAlign" align="left"><span id="spnCompanyLabel"><locid id="locOrgLabel"><font size="-1">Организация:</font></locid></span></td>
    <td><input id="org" maxlength="64" size="38" name="tbOrg"></td>
  </tr>
  <tr>
    <td colspan="2" height="18">
    </td>
  </tr>
  <tr>
    <td id="locDepartmentAlign" align="left"><span id="spnDepartmentLabel"><locid id="locOrgUnitLabel"><font size="-1">Подразделение:</font></locid></span></td>
    <td><input id="org_unit" maxlength="64" size="38" name="tbOrgUnit"></td></tr>
  <tr>
    <td colspan="2" height="18">
    </td>
  </tr>
  <tr>
  </tr><tr>
    <td id="locCityAlign" align="left"><span id="spnCityLabel"><locid id="locLocalityLabel"><font size="-1">Город:</font></locid></span></td>
    <td><input id="city" maxlength="128" size="38" name="tbLocality"></td></tr>
  <tr>
    <td colspan="2" height="18">
    </td>
  </tr>
  <tr>
  </tr><tr>
    <td id="locStateAlign" align="left"><span id="spnStateLabel"><locid id="locStateLabel"><font size="-1">Область, штат:</font></locid></span></td>
    <td><input id="region" maxlength="128" size="38" name="tbState"></td></tr>
  <tr>
    <td colspan="2" height="18">
    </td>
  </tr>
  <tr>
  </tr><tr>
    <td id="locCountryAlign" align="left"><span id="spnCountryLabel"><locid id="locCountryLabel"><font size="-1">Страна/регион:</font></locid></span></td>
    <td><input id="country" maxlength="2" size="38" value="RU" name="tbCountry">
    </td>
  </tr>
  <tr>
    <td colspan="2" height="18">
    </td>
  </tr>
  <tr>
    <td colspan="2" height="8">
    </td>
  </tr>   
  <tr>
    <td id="locKeyIDAlign" align="left"><span id="spnKeyId"><locid id="locKeyIDLabel"><font size="-1">Введите ID:</font></locid></span></td>
    <td><input id="keyId" maxlength="20" size="38" name="tbKeyID"></td>
  </tr>      
  <tr>
    <td id="tdPin" align="left"><span id="spnPin"><locid id="locPin"><font size="-1">Введите PIN:</font></locid></span></td>
    <td><input id="pin" maxlength="20" size="38" type="password"></td>
  </tr>   
  <tr>
    <td colspan="2" height="8">
    </td>
  </tr>
  <tr>   
    <td height="12">
    </td>   
    <td align="right"><input disable="true" value="Сгенерить ключ и создать заявку" onclick="createKeyRequest();" type="button">    
    </td>
  </tr>
  <tr>
    <td colspan="2" height="20">
    </td>
  </tr>    
  </tbody>
  </table> 
  <table>
  <tbody>
    <tr>         
        <td>Ниже представлена заявка в формате PKCS#10. Для получения сертификата используйте <a href="http://ca.cryptocom.ru">тестовый УЦ</a></td>
    </tr> 
     <tr>
        <td height="3">
        </td>
    </tr>     
    <tr>
        <td align="right"><textarea id="request" rows="6" cols="44" name="tbRequest"></textarea>
        </td>
    </tr> 
    <tr>
        <td height="6">
        </td>
    </tr>     
    <tr>         
        <td>Для записи на токен вставьте сертификат в формате PEM в окно:</a></td>
    </tr> 
    <tr>
        <td height="3">
        </td>
    </tr>     
    <tr>
        <td align="right"><textarea id="certarea" rows="6" cols="44" name="tbCertArea"></textarea>
        </td>
    </tr>  
    <tr>           
        <td align="right"><input disable="true" value="Записать сертификат на Рутокен ЭЦП" onclick="saveCertToToken();" type="button"> </td>
    </tr>                  
    <tr>
        <td height="6">
        </td>
    </tr> 
    <tr>         
        <td>Введите путь к файлу для подписи:</a></td>
    </tr> 
    <tr>
        <td align="left"><input id="fileForSign" maxlength="64" size="50" name="tbFileForSign"></td>
    </tr>
    <tr>
        <td height="3">
        </td>
    </tr>
    <tr>           
        <td align="right"><input disable="true" value="Подписать файл" onclick="signFile();" type="button"> </td>
    </tr>
    <tr>
        <td align="right"><textarea id="signature" rows="6" cols="44" name="tbSignatureArea"></textarea>
        </td>
    </tr>
     
  </tbody>
  </table>     
</td></tr></tbody>
</table>

</td></tr></tbody></table>		
</td></tr>


<!--End top half-->

</tbody></table>


</tbody></table>


</center>
</body></html>
Автор: @VicTun
Компания «Актив»
рейтинг 32,50

Комментарии (29)

  • +6
    Мда. Плюсую, актуально.
    Хотя исходники, имхо, бы лучше на какой-нить опен-сорс хостинг, а не в текст статьи…
  • 0
    Как раз на днях думал, а поддерживают ли not-IE браузеры такого рода фишки, в частности рутокен. Для ентерпрайза большой плюс.
  • 0
    В OpenJDK работает?
    • 0
      Не проверял
  • +1
    А почему бы не использовать их же webtoken ( www.rutokenweb.ru/ )?
    • 0
      Речь идет о самом токене Рутокен Web или же о комплексном решении «Рутокен Web + плагины к браузеру»?
      • +1
        Вместе с плагином, потому что сам по сееб Web не сильно отличается от ECP
        • +1
          Основная разница в том, что java-апплет построен на базе OpenSSL c ГОСТом и апплет расширяем, так как даны его исходники. То есть все что есть в OpenSSL можно добавить в апплет. А OpenSSL поддерживает цифровые сертификаты X.509 и списки отзыва CRL, все форматы защищенных (подписанных и зашифрованных) сообщений — PKCS#7, CMS, S/MIME, заявки на сертификаты в форматах PKCS#10/SPARC, функциональность PKI, различные криптографические протоколы и т.п.
        • +1
          Решение Рутокен Web закрытое. Оно имеет четко очерченный функционал, направленный на аутентификацию пользователя. Предлагаемое решение, это я так понимаю, конструктор для разработки собственных решений.
  • 0
    Речь идет о самом токене Рутокен Web или же о комплексном решении «Рутокен Web + плагины к браузеру»?
  • 0
    Предлагаемое решение не выдерживает никакой критики. Оно абсолютно нежизнеспособно.

    Во-первых, в яве постоянно находят новые уязвимости. Глядя на историю таких уязвимостей, становится очевидно, что с большой вероятностью в хакерском сообществе имеется информация о неизвестных сейчас уязвимостях и средства их эксплуатации (т.н zeroday exploits). Что вы скажете вашим клиентам, когда их взломают через эту яву и уведут, например деньги с ДБО? Хотя я знаю, вы скажете, что виноват Оракл, а вы тут не при чем. С современным уровнем ответственности разработчиков («this software is provided without warranty of any kind») никто не хочет ни за что отвечать.

    Я например отключил плагин ява во всех браузерах. Те, кто его не отключили, не имеют (морального) права предъявлять претензии в случае взлома, так как все прекрасно знают, что это решето, и если человек его не отключает, он сам себе Буратино.

    Я надеюсь, что Оракл когда-нибудь либо уберет веб-плагин, либо перестанет его включать при установке Явы, либо добрые ребята из Гугла заблокируют его на уровне браузера, ибо так продолжать нельзя. Флеш мы потихоньку выдавливаем с ПК, теперь пора и яве подвинуться.

    Во-вторых, решение уродливое и громоздкое. Какие-то тонны бибиотек (бинарных, только x86, никем не проверенных, нгеизвестно что в себе содержащих) и непонятного кода, написанного какими-то анонимусами. И это предлагается запускать на своем компьютере? это неразумно.

    Решение делать и устанавливать какие-то проприетарные плагины, тоже глупо. Для работы средств ЭЦП давно уже пора предусмотреть какие-то возможности в стандарте HTML, и поддержку таких средств должны реализовать производители браузеров. И делать это на основе открытых стандартов. А рутокен — местечковая временная контора, и как только уважаемые компании вроде Гугл или Яндекс примут участие в разработке, уверен, ситуация с массовой криптографией и ЭЦП улучшится. Потому что сегодня в разработке средств ЭЦП принимают участие сомнительные фирмочки, продвигающие плохие, непроверенные, непроверяемые (нет исходников), проприетарные, with vendor lock-in feature средства, место которым на свалке истории. Массовая криптография и ЭЦП станут повсеместными в будущем, и хотелось бы, чтобы у них был надежный проверенный фундамент, а не чтобы какие-то сомнительные компании захватили этот рынок с проприетарными продуктами, как например это удалось сделать когда-то майкрософту, последствия чего мы до сих пор расхлебываем.

    > а при загрузке на web-странице будет распаковывать их в папку %TEMP% и уже оттуда использовать

    Это жесть. Ява имеет право создавать файлы на клиенте? Неудивительно, что через нее трояны и устанавливаются.
    • +1
      >Во-первых, в яве постоянно находят новые уязвимости. Глядя на историю таких уязвимостей, становится >очевидно, что с большой вероятностью в хакерском сообществе имеется информация о неизвестных сейчас >уязвимостях и средства их эксплуатации (т.н zeroday exploits).

      Эмоционально и необоснованно. В любых платформах бывают уязвимости и разработчики платформы с ними успешно разбираются. Ява в этом плане абсолютно нормальная платформа, которая достаточно распространена, а, значит, оттестирована. И как раз многие системы ДБО уважаемых банков сделаны на яве.

      >Что вы скажете вашим клиентам, когда их >взломают через эту яву и уведут, например деньги с ДБО?
      >Хотя я знаю, вы скажете, что виноват Оракл, а вы тут >не при чем. С современным уровнем ответственности >разработчиков («this software is provided without warranty >of any kind») никто не хочет ни за что отвечать.

      Вы, как я понял, пишете о вашем личном уровне ответственности?

      >Во-вторых, решение уродливое и громоздкое. Какие-то тонны бибиотек (бинарных, только x86, никем не >проверенных, нгеизвестно что в себе содержащих) и непонятного кода, написанного какими-то анонимусами.
      >И это предлагается запускать на своем компьютере? это неразумно.

      Вы либо поленились разбираться, либо квалификация не позволяет. Эти библиотеки, в основном — OpenSSL. Который доступен в исходниках. OpenSSL используется в таких распространенных продуктах как Apache, OpenVPN, серверах IBM. То есть это реально уважаемая в мире библиотека. А то что x86 — для примера остаточно. Эти все библиотеки есть под x64 и не только по винду. Непонятный код написанный анонимусами открыт — разработчики разберутся в нем и он станет понятным.

      Если вам что-то непонятно, то значит вы не целевая аудитория для этой статьи.

      >Решение делать и устанавливать какие-то проприетарные плагины, тоже глупо. Для работы средств ЭЦП >давно уже пора предусмотреть какие-то возможности в стандарте HTML, и поддержку таких средств должны >реализовать производители браузеров. И делать это на основе открытых стандартов.

      Вы некомпетентны. ЭЦП и делается на основе открытых стандартов. ГОСТы давно уже в RFC, PKCS#7, CMS, S/MIME — в RFC. Мало того, я использую открытую реализацию этих стандартов — OpenSSL.

      >А рутокен — >местечковая временная контора, и как только уважаемые компании вроде Гугл или Яндекс >примут участие в >разработке, уверен, ситуация с массовой криптографией и ЭЦП улучшится. Потому что >сегодня в разработке >средств ЭЦП принимают участие сомнительные фирмочки, продвигающие плохие, >непроверенные, >непроверяемые (нет исходников), проприетарные, with vendor lock-in feature средства, >место которым на свалке истории. Массовая криптография и ЭЦП станут повсеместными в будущем, и >хотелось бы, чтобы у них был надежный проверенный фундамент, а не чтобы какие-то сомнительные >компании захватили этот рынок с проприетарными продуктами, как например это удалось сделать когда-то >майкрософту, последствия чего мы до сих пор расхлебываем.

      Бла, бла, бла… Эти черные, а те белые. Юношеский максимализм однако. Ах, простите, вам же 20 лет:) Ну почему нет исходников? Я все сделал на open source библиотеке (OpenSSL) и вам отдал все исходники, так вы же их смотреть не хотите.

      >Это жесть. Ява имеет право создавать файлы на клиенте? Неудивительно, что через нее трояны и >устанавливаются.

      ПОДПИСАННЫЙ java-апплет!
    • +1
      Что вы скажете вашим клиентам, когда их взломают через эту яву и уведут, например деньги с ДБО?


      А что Вы скажете клиентам, если из взломают через винду, через акробат, через МАС ось, через социальную инженерию в конце концов?

      Здесь нужен административный подход. ИМХО: явную уязвимость своим продуктом разработчики не создают.
      Но если лично Вас что-то не устраивает — предложите автору создать решение под Ваши нужды за определённое вознаграждение. Всем ведь не угодишь. Знаете сколько таких «Нехочух» вроде Вас найдётся. И что: автору за бесплатно всем свои версии переделывать?
  • 0
    Большое спасибо за полноценное решение по использованию OpenSSL + ruToken в апплете!
    А есть возможность использовать eToken таким же образом?

    По поводу исп. JNI в апплете есть замечания — в случае обновления странички с апплетом
    может произайти ошибка «UnsatisfiedLinkError: Native Library *.dll already loaded in another classloader».
    Это связано с тем что DLL может быть загружена только один раз. Надо эту ошибку как-то специально обрабатывать.

    И я не совсем согласен с тем что нужно DLLки хранить как ресурсы, это увеличивает размер апплета. Апплет будет долго грузиться а этот процесс никак не контролируется, апплет в это время будет недоступен.
    • 0
      >А есть возможность использовать eToken таким же образом?

      Таким же образом (через OpenSSL) — нет, так как eToken с ГОСТом не «прикручен» к OpenSSL.

      >По поводу исп. JNI в апплете есть замечания — в случае обновления странички с апплетом
      >может произайти ошибка «UnsatisfiedLinkError: Native Library *.dll already loaded in another classloader».
      >Это связано с тем что DLL может быть загружена только один раз. Надо эту ошибку как-то специально >обрабатывать.

      Спасибо за замечание. При тестировании примера не нарывался.

      >И я не совсем согласен с тем что нужно DLLки хранить как ресурсы, это увеличивает размер апплета. >Апплет будет долго грузиться а этот процесс никак не контролируется, апплет в это время будет
      недоступен.

      Как вариант для DLL-ок можно сделать установщик, который бы не требовал прав сисадмина (на NSYS, например). Он бы их складывал в определенное место, а апплет их бы там искал. Для безопасности инсталлятор так же можно подписывать, используя для проверки подписи на винде механизм верификаци дистрибутивов.
  • 0
    Ситуация всё равно катастрофическая. Пользователь может только верить, что он подписывает именно то, что видит, ведь апплет-то пришёл с того же источника, который отправляет ему документ на подписание. Предвижу уже, как пользователям подбрасывают документы типа расписок в больших суммах денег, использовав открытый или даже не слишком, WiFi, и атакуя с помощью MitM.

    Я бы гораздо больше доверял скриптлету, который обращается за подписанием к локальному веб-сервису, работающему напрямую с токеном. Токен же умеет аппаратно подписывать документы?

    И уж поверьте, подобное решение было бы легковеснее (5Мб Lua с библиотеками, к примеру, для веб-сервиса) и переносимее. И что самое главное — более устойчиво к атакам и гораздо более прозрачно для пользователя.
    • 0
      Часть приложений, использующих подпись, не столь требовательны к реальной безопасности :)
      • 0
        Это видео вы мне дали, чтобы я расслабился и подумал о хорошем минутку?
        • +2
          Конечно, Новый Год на носу!
          По существу вопроса — Компания Актив разработал и выпустил на рынок решение Рутокен PINPad, я писал о нем тут. Идет активная работа по встраиванию этого решения в российские банки.
          • 0
            Да, читал, отличная штука, но к ЭЦП в браузере её не прикрутишь.
            • +1
              Почему же не прикрутишь? Рутокен PINPad «внутри» такой же криптографический токен, как и Рутокен ЭЦП, только с экраном, на котором можно смотреть — что подписывается. У них даже программные интерфейсы практически одинаковые. Тут вот есть пример подписи в браузере с помощью PINPada pinpad.rutoken.ru/demo.php.
              • +1
                Тогда цены такой штуке нет, правда только если то, что подписываешь — удобочитаемо. Мне пока приходилось общаться с таким вот, и человеку там делать нечего, хотя подписывать это просят именно человека, а за отправку с подписью неправильных данных иногда грозит уголовная ответственность.
            • 0
    • 0
      Хочу добавить некоторые первоисточники к тем проблемам, которые я описал.
      Наглядность подписания.
      USB токен с JavaScript API (увы, только аутентификация). Поскольку у нас не видно особых альтернатив рутокену, и дополнительный уровень абстракции для использования любых токенов не нужен, можно использовать аналогичную систему драйверов. Либо предложенный мной вариант.
    • 0
      >Я бы гораздо больше доверял скриптлету, который обращается за подписанием к локальному веб-сервису, >работающему напрямую с токеном.

      А в чем разница? Сервис вы ставите? Значит вы уверены в том, что это доверенный софт, что его никто не подменил.
      В случае с подписанным апплетом (а неподписанному апплету java просто не разрешит вызывать native библиотеки) ситуация такая же. Перед началом работы апплета java скажет «подписан тем-то», а там уж ваше дело — доверять или не доверять.

      >Токен же умеет аппаратно подписывать документы?

      С помощью Java-апплета документы как раз подписываются аппаратно.

      >И уж поверьте, подобное решение было бы легковеснее (5Мб Lua с библиотеками, к примеру, для >веб-сервиса) и переносимее. И что самое главное — более устойчиво к атакам и гораздо более прозрачно >для пользователя

      Все библиотеки внутри апплета весят 1.9Мб, а упакованный JAR-архив — 942 Кб.
      Ваше решение не более устойчиво к атакам (почему, я описал выше) и гораздо менее прозрачно для пользователя.
      • 0
        Если сервис идёт на CD, или может скачиваться с сайта производителя, и однократно устанавливается, у меня к нему больше доверия, чем к некоему апплету, который поставляется третьей стороной (банком, гос. сайтом и т.п.), который ко всему прочему ещё и качается браузером.
        Да, с подписью апплетов это замечательно, но когда тебе нужно провести какой-то платёж, а вылезает назойливое окно, говорящее о том, что не ясно, стоит ли доверять источнику, пользователь в большем количестве случаев предпочтёт ответить «доверять», а возможно, ещё и поставит галочку «всегда доверять этому издателю», что может привести для него к очень печальным последствиям.

        Это понятно, что JA нужен только как прокладка между содержимым страницы и USB устройством (а точнее, MS CryptoAPI. Можете для тупых, пожалуста, разжевать, зачем же нужен gost.dll?

        Поясните, пожалуйста, в чём меньшая устойчивость к атакам, и атакам какого рода.
        Также интересно, чем же меньше прозрачность GM плагина, код которого можно посмотреть прямо из браузера, поставляемых производителем токена, перед байткодовым JA, и библиотекой gost.dll и её ещё более загадочными компаньонами, поставляемыми третьими лицами?
        • 0
          >Если сервис идёт на CD, или может скачиваться с сайта производителя, и однократно устанавливается, у меня >к нему больше доверия, чем к некоему апплету, который поставляется третьей стороной (банком, гос. сайтом >и т.п.), который ко всему прочему ещё и качается браузером.
          >Да, с подписью апплетов это замечательно, но когда тебе нужно провести какой-то платёж, а вылезает >назойливое окно, говорящее о том, что не ясно, стоит ли доверять источнику, пользователь в большем >количестве случаев предпочтёт ответить «доверять», а возможно, ещё и поставит галочку «всегда доверять >этому издателю», что может привести для него к очень печальным последствиям.

          Кроме подмены апплета возможны и другие атаки, которые приведут к подмене вашего же скриптлета на хакерский. И что, пользователь, который как вы считаете не может правильно ответить на вопрос браузера о подписи апплета, полезет смотреть код скриптлета?

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

          >Это понятно, что JA нужен только как прокладка между содержимым страницы и USB устройством (а >точнее, MS CryptoAPI. Можете для тупых, пожалуста, разжевать, зачем же нужен gost.dll?

          MS CryptoAPI здесь вообще не используется. JA обращается к OpenSSL, который в свою очередь через engine pkcs11_gost (плагин к OpenSSL) и libp11 (open source PKCS#11 wrapper) обращается к библиотеке rtPKCS11ECP, которая в свою очередь через CCID-драйвер обращается с помощью APDU команд к Рутокен ЭЦП.

          С помощью Gost.dll производится упаковка полученной аппаратно подписи ГОСТ Р 34-10.2001 в формат PKCS#7 в соответствии с криптопрошными rfc.

          Я не писал что ваше решение менее устойчиво к атакам. «Не более».

          >также интересно, чем же меньше прозрачность GM плагина, код которого можно посмотреть прямо из >браузера, поставляемых производителем токена, перед байткодовым JA, и библиотекой gost.dll и её ещё >более загадочными компаньонами, поставляемыми третьими лицами?

          Потому что этот плагин требует установленного локально сервиса, а апплет — нет. На современных ОС для работы апплета даже драйвера Рутокен ЭЦП ставить не требуется, так как CCID-дравер по умолчанию имеется в системе.

          А для работы вашего решения машину нужно подготавливать, да еще поди и с правами сисадмина. Соответственноо идет привязка к одной машине, из интернет-кафе уже не поработаешь.

          А про «код которого можно посмотреть прямо из браузера» — это мало кому надо.
  • 0
    >Если сервис идёт на CD, или может скачиваться с сайта производителя, и однократно устанавливается, у меня >к нему больше доверия, чем к некоему апплету, который поставляется третьей стороной (банком, гос. сайтом >и т.п.), который ко всему прочему ещё и качается браузером.
    >Да, с подписью апплетов это замечательно, но когда тебе нужно провести какой-то платёж, а вылезает >назойливое окно, говорящее о том, что не ясно, стоит ли доверять источнику, пользователь в большем >количестве случаев предпочтёт ответить «доверять», а возможно, ещё и поставит галочку «всегда доверять >этому издателю», что может привести для него к очень печальным последствиям.

    Кроме подмены апплета возможны и другие атаки, которые приведут к подмене вашего же скриптлета на хакерский. И что, пользователь, который как вы считаете не может правильно ответить на вопрос браузера о подписи апплета, полезет смотреть код скриптлета?

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

    >Это понятно, что JA нужен только как прокладка между содержимым страницы и USB устройством (а >точнее, MS CryptoAPI. Можете для тупых, пожалуста, разжевать, зачем же нужен gost.dll?

    MS CryptoAPI здесь вообще не используется. JA обращается к OpenSSL, который в свою очередь через engine pkcs11_gost (плагин к OpenSSL) и libp11 (open source PKCS#11 wrapper) обращается к библиотеке rtPKCS11ECP, которая в свою очередь через CCID-драйвер обращается с помощью APDU команд к Рутокен ЭЦП.

    С помощью Gost.dll производится упаковка полученной аппаратно подписи ГОСТ Р 34-10.2001 в формат PKCS#7 в соответствии с криптопрошными rfc.

    Я не писал что ваше решение менее устойчиво к атакам. «Не более».

    >также интересно, чем же меньше прозрачность GM плагина, код которого можно посмотреть прямо из >браузера, поставляемых производителем токена, перед байткодовым JA, и библиотекой gost.dll и её ещё >более загадочными компаньонами, поставляемыми третьими лицами?

    Потому что этот плагин требует установленного локально сервиса, а апплет — нет. На современных ОС для работы апплета даже драйвера Рутокен ЭЦП ставить не требуется, так как CCID-дравер по умолчанию имеется в системе.

    А для работы вашего решения машину нужно подготавливать, да еще поди и с правами сисадмина. Соответственноо идет привязка к одной машине, из интернет-кафе уже не поработаешь.

    А про «код которого можно посмотреть прямо из браузера» — это мало кому надо.
  • +2
    «У меня есть мечта…»
    В самом деле — у меня есть мечта, что когда-нибудь в будущем я смогу обойтись без связки «windows + IE» для клиент-банков и прочего софта, который использует крипто-про. СБИС++, например.
    Блин, я надеюсь, что это когда-нибудь будет.

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

Самое читаемое Разработка