Как стать автором
Обновить
73.07

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

Время на прочтение 26 мин
Количество просмотров 23K
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>
Теги:
Хабы:
+29
Комментарии 29
Комментарии Комментарии 29

Публикации

Информация

Сайт
aktiv-company.ru
Дата регистрации
Дата основания
1994
Численность
101–200 человек
Местоположение
Россия