Электронная подпись в браузере с помощью 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>
    
    Метки:
    «Актив» 73,04
    Компания
    Поделиться публикацией

    Вакансии компании «Актив»

    Комментарии 29
    • +6
      Мда. Плюсую, актуально.
      Хотя исходники, имхо, бы лучше на какой-нить опен-сорс хостинг, а не в текст статьи…
      • 0
        Как раз на днях думал, а поддерживают ли not-IE браузеры такого рода фишки, в частности рутокен. Для ентерпрайза большой плюс.
        • 0
          В OpenJDK работает?
        • +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
                            Хочу добавить некоторые первоисточники к тем проблемам, которые я описал.
                            Наглядность подписания.
                            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» для клиент-банков и прочего софта, который использует крипто-про. СБИС++, например.
                                Блин, я надеюсь, что это когда-нибудь будет.

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

                                Самое читаемое
                                Интересные публикации