UPDATE. Готовое решение для электронной подписи в браузере — Рутокен Плагин
Потребность в решениях, помогающих реализовать электронную подпись в «браузере», возрастает. Главные требования к таким решениям — поддержка российких криптоалгоритмов, обеспечение безопасности ключа и нормальное usability. В данном топике мы напишем браузерный криптографический java-апплет, в который интегрирован OpenSSL ГОСТ c модулем поддержки Рутокен ЭЦП. Этот апплет не требует установки какого-либо клиентского софта (кроме java-машины, конечно) и позволяет подписывать файлы через браузер в формате PKCS#7 с ипользованием аппаратной реализации российских криптографических стандартов на «борту» USB-токена Рутокен ЭЦП. Для демонстрации в топике будет дан пример HTML-страницы, использующей данный апплет. На странице можно сгенерить ключ внутри токена, создать заявку PKCS#10 на сертификат для этого ключа, получить тестовый сертификат, записать его на токен, подписать файл.
Архитектура решения представлена на рисунке:
Рутокен ЭЦП — это компактное USB-устройство, внутри которого находятся защищенная память и микроконтроллер, реализующий российские криптографические стандарты. Так как операции проводятся на «борту» токена, то ключ пользователя невозможно украсть.
OpenSSL — это кроссплатформенный пакет с открытым программным кодом, в котором поддерживаются большинство современных криптографических алгоритмов, форматы PKCS, CMS, S/MIME, протоколы SSL/TLS и т.п. Начиная с версии 1.0.0 OpenSSL обеспечивает полнофункциональную поддержку российских криптоалгоритмов.
Про плагин, обеспечивающий поддержку Рутокен ЭЦП в OpenSSL, можно почитать тут же на хабре
habrahabr.ru/blogs/infosecurity/134725.
Библиотека Signature — это динамическая библиотека, «надстройка» над OpenSSL, которая инкапсулирует в себе вызовы OpenSSL. Предоставляет JNI-интерфейс для использования в 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
Листинг 2. Класс OpenSSL_Wrapper
Package Rutoken следует экспортировать в JAR-архив, а затем этот архив подписать с помощью утилиты jarsigner.
В ней реалиован следующий API.
1. Функция инициализации:
2. Функция генерации ключа подписи ГОСТ Р 34-10.2001 на Рутокен ЭЦП и создания заявки на сертификат в формате PKCS#10:
3. Функция записи сертификата на Рутокен ЭЦП
4. Функция подписи файла в формате PKCS#7 по ГОСТ Р 34.10-2001:
Функция инициализации имеет один параметр — путь к папке, в которую апплет распаковал бинарники. Этого достаточно, для того чтобы наша библиотека Signature могла загрузить OpenSSL, плагин к нему и библиотеку PKCS#11 для работы с Рутокен ЭЦП.
Для взаимодействия с Java-апплетом в библиотеке реализован так же JNI-интерфейс.
Листинг 3. Реализация JNI-интерфейса библиотеки Signature
Листинг 4. Реализация библиотеки Signature
Желающие могут сделать эту библиотеку кроссплатформенной (заменив LoadLibrary на соответствующий вызов), расширить эту библиотеку нужными им функциями.
Листинг 5. Пример страницы HTML, на которой JavaScript использует наш апплет
Потребность в решениях, помогающих реализовать электронную подпись в «браузере», возрастает. Главные требования к таким решениям — поддержка российких криптоалгоритмов, обеспечение безопасности ключа и нормальное usability. В данном топике мы напишем браузерный криптографический java-апплет, в который интегрирован OpenSSL ГОСТ c модулем поддержки Рутокен ЭЦП. Этот апплет не требует установки какого-либо клиентского софта (кроме java-машины, конечно) и позволяет подписывать файлы через браузер в формате PKCS#7 с ипользованием аппаратной реализации российских криптографических стандартов на «борту» USB-токена Рутокен ЭЦП. Для демонстрации в топике будет дан пример HTML-страницы, использующей данный апплет. На странице можно сгенерить ключ внутри токена, создать заявку PKCS#10 на сертификат для этого ключа, получить тестовый сертификат, записать его на токен, подписать файл.
Архитектура решения представлена на рисунке:
Рутокен ЭЦП — это компактное 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>