В одной из наших заметок было высказано намерение написать графические оболочки для пакетов OpenSSL и NSS (Network Security Services). GUI для NSS было написано:
Следует отметить, что сегодня openssl с ГОСТ-ым engine-ом завоевывает все большую популярность на просторах российского PKI (инфраструктуры открытых ключей).
И именно поэтому разработка расширения OpenSSL для Tcl может оказаться вполне востребованной. И вот теперь мы покажем как можно разработать GUI для OpenSSL, но не на базе командной утилиты openssl, а используя непосредственно функционал библиотек openssl (libcrypto, libssl).
Напрямую родные библиотеки OpenSSL использовать не удастся. Как и во многих других случаям (яркий пример Java), необходимо написать обертку (wrap) с учетом особенностей Tcl. Но эти особенности прозрачны и не вызывают трудностей.
Для начала надо определиться с синтаксисом создаваемого расширения, а фактически с синтаксисом обращения к функциям openssl. И так, любая программа на Tcl состоит из команд, разделённых символами новой строки или точками с запятой. Я рекомендую точку с запятой ставить всегда в конце команды. В свою очередь, каждая команда состоит из набора полей, разделённых пробелами. Первое поле является именем команды, а необязательные остальные поля — передаваемые этой команде аргументы. Команда возвращает результат. Исходя из этого и опираясь на синтаксис утилиты openssl, можно предложить следующий синтаксис для расширения openssl:
где lscw – имя команды. Поле <операция> по аналогии с утилитой командной строки openssl (например, openssl x509 ) будет указывать на выполняемую подфункцию. Параметры подфункции задаются следом за ней.
Мы рассмотрим создание расширения или, как еще его называют, пакета на примере получения содержимого сертификата (или запроса на сертификат) с последующим отображением его в виджете. Команда tcl может выглядеть так:
которая интерпретируется так: сохранить в переменной dump содержимое (параметр print) сертификата (параметр x), находящегося в файла, путь к которому хранится в переменной $file. Все параметры являются позиционными. Если вместо параметра x/X задать r/R, то будет распечатано содержимое запроса на сертификат (PKCS#10), если c/C – список отозванных сертификатов.
Поддержка команд из пакета поддерживается соответствующей библиотекой. Для загрузки этой библиотеки используется Tcl-команда load, которая в простейшем случае имеет следующий вид:
Первым параметром команды является имя библиотеки, а вторым имя пакета, который она реализует. Теперь, зная все это и решив, что пакет будет иметь имя lscw, мы можем написать Tcl/Tk-скрипт, который позволяет загрузить библиотеку расширения, выбрать файл с сертификатом и просмотреть его в окне (аналогичный скрипт есть для NSS):
Сам код не требует дополнительных комментариев. Теперь нам осталось создать библиотеку на языке C. Говоря о расширении для OpenSSL, нас интересует поддержка в нем российской криптографии. В стандартном OpenSSL эта поддержка осуществляется через engine gost. Именно поэтому в нашей библиотеке должно быть предусмотрено подключение этого engine.
Как мы уже говорили, загрузка библиотеки осуществляется посредством команды load, в которой первым параметром указывается путь в библиотеки, а вторым имя пакета.
Имя пакета фактически задает имя процедуры инициализации, которая вызывается сразу же после загрузки библиотеки. Имя процедуры инициализации выглядит следующим образом:
Отметим еще одну особенность именования процедуры инициализации: первый символ имени пакета всегда переводится в верхний регистр. Именно поэтому команда
и команда
идентичны и при загрузки библиотеки в обоих случаях будет вызвана процедура инициализации Lscw_Init:
Для сборки использовались статические библиотеки openssl:
После сборки можно смело запускать скрипт, приведенный в начале статьи.
Теперь самое время остановиться на OpenSSL с поддержкой российских криптоалгоритмов. В сожалению, тот ГОСТ-engine, который входит в состав OpenSSL не поддерживает новые криптографические алгоритмы, в частности:
• выработку значения хэш-функции в соответствии с требованиями ГОСТ Р 34.11-2012 «Информационная технология. Криптографическая защита информации. Функция хэширования»;
• формирование и проверку ЭЦП в соответствии с требованиями ГОСТ Р 34.10-2012 «Информационная технология. Криптографическая защита информации. Процессы формирования и проверки электронной цифровой подписи»;
• зашифрование/расшифрование данных алгоритмами шифрования «Кузнечик» (КУЗьмин, НЕЧаев И Компания) и «Магма» в соответствии с требованиями «ГОСТ Р 34.12-2015 Информационная технология. Криптографическая защита информации. Блочные шифры» и «ГОСТ Р 34.13-2015 Информационная технология. Криптографическая защита информации. Режимы работы блочных шифров».
Есть и еще недочеты. Сегодня можно найти модификации OpenSSL, в том числе и сертифицированные в системе сертификации ФСБ России, с полной поддержкой российской криптографии и соответствующие всем нормативным требованиям, в том числе и к квалифицированным сертификатам. Библиотеку lscw, собранную на одном из таких проектов, можно скачать здесь и использовать для просмотра квалифицированных сертификатов:
И наконец, хотелось уйти от tcl-скрипта и получить настоящий бинарный код. Для этого убираем tcl-скрипт в C-код openssl_print_main.c:
И выполяем:
Выбирайте библиотеку, далее сертификат и любуйтесь (см. выше).
Конечный проект по использованию OpenSSL в Tcl/Tk выглядит так:
Проект можно скачать здесь.
Теперь дело осталось за малым, за Android:
Следует отметить, что сегодня openssl с ГОСТ-ым engine-ом завоевывает все большую популярность на просторах российского PKI (инфраструктуры открытых ключей).
И именно поэтому разработка расширения OpenSSL для Tcl может оказаться вполне востребованной. И вот теперь мы покажем как можно разработать GUI для OpenSSL, но не на базе командной утилиты openssl, а используя непосредственно функционал библиотек openssl (libcrypto, libssl).
Напрямую родные библиотеки OpenSSL использовать не удастся. Как и во многих других случаям (яркий пример Java), необходимо написать обертку (wrap) с учетом особенностей Tcl. Но эти особенности прозрачны и не вызывают трудностей.
Для начала надо определиться с синтаксисом создаваемого расширения, а фактически с синтаксисом обращения к функциям openssl. И так, любая программа на Tcl состоит из команд, разделённых символами новой строки или точками с запятой. Я рекомендую точку с запятой ставить всегда в конце команды. В свою очередь, каждая команда состоит из набора полей, разделённых пробелами. Первое поле является именем команды, а необязательные остальные поля — передаваемые этой команде аргументы. Команда возвращает результат. Исходя из этого и опираясь на синтаксис утилиты openssl, можно предложить следующий синтаксис для расширения openssl:
lscw <операция> [параметр 1] [параметр 2] … [параметр N];
где lscw – имя команды. Поле <операция> по аналогии с утилитой командной строки openssl (например, openssl x509 ) будет указывать на выполняемую подфункцию. Параметры подфункции задаются следом за ней.
Мы рассмотрим создание расширения или, как еще его называют, пакета на примере получения содержимого сертификата (или запроса на сертификат) с последующим отображением его в виджете. Команда tcl может выглядеть так:
set dump [lscw print $file_cert x ];
которая интерпретируется так: сохранить в переменной dump содержимое (параметр print) сертификата (параметр x), находящегося в файла, путь к которому хранится в переменной $file. Все параметры являются позиционными. Если вместо параметра x/X задать r/R, то будет распечатано содержимое запроса на сертификат (PKCS#10), если c/C – список отозванных сертификатов.
Поддержка команд из пакета поддерживается соответствующей библиотекой. Для загрузки этой библиотеки используется Tcl-команда load, которая в простейшем случае имеет следующий вид:
load <библиотека> <имя пакета>;
Первым параметром команды является имя библиотеки, а вторым имя пакета, который она реализует. Теперь, зная все это и решив, что пакет будет иметь имя lscw, мы можем написать Tcl/Tk-скрипт, который позволяет загрузить библиотеку расширения, выбрать файл с сертификатом и просмотреть его в окне (аналогичный скрипт есть для NSS):
encoding system utf-8;
#Устанавливаем цвет главного окна
. configure -background {#FFDAB9};
#Устанавливаем заголовок главного окна
wm title . {GUI OpenSSL X509};
set homeDir $env(HOME);
set types {{{Библиотека liblscw} {.so} }
{{Библиотека liblscw MS} {.dll} }
{{Любой файл} * }
};
set typesCert { {{Сертификат} {.crt} }
{{Сертификат} {.cer} }
{{Сертификат} {.der} }
{{Сертификат} {.pem} }
{{Любой файл} * }
};
#Сворачиваем главное окно
wm iconify .;
set liblscw [tk_getOpenFile -filetypes $types -initialdir $homeDir -title {Выберите библиотеку расширения для OpenSSL}];
if { $liblscw == {} } {exit 1};
#Загружаем wrap-библиотеку для OpenSSL
if {[catch {load $liblscw lscw} res]} {
puts $res;
exit 1;
};
set loadlib {loading shared lib: };
set loadlib $loadlib$liblscw;
puts $loadlib;
#puts {loading shared lib: $liblscw}
set fileCert [tk_getOpenFile -filetypes $typesCert -initialdir $homeDir -title {Выберите сертификат/запрос для просмотра}];
if { $fileCert == {} } {exit 1};
if {[catch {lscw print $fileCert x} res]} {
puts $res;
exit 1;
};
#Восстанавливаем главное окно
wm deiconify .;
#Готовим text-виджет для отображения сертификата
text .prcert -background snow;
pack .prcert -expand 1 -fill both -in . -pady 6 -padx 6;
#Кновка завершения
button .but -text Насмотрелся -command exit -background orange -activebackground green;
pack .but -pady 6;
#Чистим поле для сертификата
.prcert delete 0.0 end ;
#размещаем сертификат
.prcert insert end $res;
#Все
Сам код не требует дополнительных комментариев. Теперь нам осталось создать библиотеку на языке C. Говоря о расширении для OpenSSL, нас интересует поддержка в нем российской криптографии. В стандартном OpenSSL эта поддержка осуществляется через engine gost. Именно поэтому в нашей библиотеке должно быть предусмотрено подключение этого engine.
Как мы уже говорили, загрузка библиотеки осуществляется посредством команды load, в которой первым параметром указывается путь в библиотеки, а вторым имя пакета.
Имя пакета фактически задает имя процедуры инициализации, которая вызывается сразу же после загрузки библиотеки. Имя процедуры инициализации выглядит следующим образом:
<имя пакета>_Init
Отметим еще одну особенность именования процедуры инициализации: первый символ имени пакета всегда переводится в верхний регистр. Именно поэтому команда
load $liblscw lscw;
и команда
load $liblscw Lscw;
идентичны и при загрузки библиотеки в обоих случаях будет вызвана процедура инициализации Lscw_Init:
int Lscw_Init(Tcl_Interp* interp)
{
OpenSSL_add_all_algorithms();
#ifdef OPENSSL_GOST
ENGINE_load_builtin_engines();
//Загружаем ГОСТ-ый engine
// gost = load_engine("/usr/lib64/openssl/1.0.2m/engines/libgost.so", 0);
gost = load_engine("OPENSSL/libgost.so", 0);
if (!ENGINE_set_default (gost, ENGINE_METHOD_ALL))
{
Tcl_SetResult(interp, "OpenSSL error: ENGINE_set_default failed on engine", (Tcl_FreeProc*)0);
return TCL_ERROR;
}
#endif
//Привязка команды lscw к процедуре lsцrapCmd
Tcl_CreateCommand(interp, "lscw", (Tcl_CmdProc *)lswrapCmd, (ClientData) 0,
(void (*)()) NULL);
return TCL_OK;
}
Полный код библиотеки можно посмотреть здесь.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <openssl/pem.h>
#include <openssl/engine.h>
#include <tcl.h>
#include <tk.h>
#ifdef OPENSSL_GOST
void ENGINE_load_gost(void);
static ENGINE *gost;
static ENGINE *load_engine(const char *engine, int debug);
#endif
char *X509View(char *nickfile, char typeX509);
int lscwPrintSubCmd(ClientData lscmd, Tcl_Interp* interp);
/*Вид подфункций в пакете*/
typedef int ((*subProc)(ClientData, Tcl_Interp*));
/*Структура подфункций*/
typedef struct {
char* subname; //Имя подфункции
subProc f; //Адрес функции
int minStack; //Количество параметров
} subEntry;
static subEntry subTable[] = {
{"print", lscwPrintSubCmd, 2},
//Остальные напиши сам
{ 0, 0}
};
int lscwPrintSubCmd(ClientData lscmd, Tcl_Interp* interp)
{
char **argv;
char *res;
argv = (void *)lscmd;
res = (char *)X509View((char*)argv[2], (char)argv[3][0]);
if (res == NULL) {
//Устанавливает результат в контекст Tcl
Tcl_SetResult(interp, "Ошибка печати.", (Tcl_FreeProc*)0);
//Возврат в Tcl с кодом TCL_ERROR
return TCL_ERROR;
}
//Устанавливает результат в контекст Tcl
Tcl_SetResult(interp, res, (Tcl_FreeProc*)0);
//Возврат в Tcl с кодом TCL_ERROR
return TCL_OK;
}
char *X509View(char *nickfile, char typeX509)
{
X509 *x509 = NULL;
X509_REQ *x509_req = NULL;
BIO *mem = BIO_new(BIO_s_mem());
char *p = NULL;
char *res;
int len;
//Загружаем файл
BIO *bio;
if ( NULL == ( bio = BIO_new_file(nickfile, "rb"))){
fprintf(stderr, "X509View: failed to open file=%s\n", nickfile);
return NULL;
}
switch(typeX509){
/*Просматриваем сертификат X509*/
case 'x':
case 'X':
// DER
x509 = d2i_X509_bio(bio, NULL);
if (NULL == x509){
// PEM
BIO_reset( bio );
x509 = PEM_read_bio_X509( bio, NULL, NULL, NULL );
if (NULL == x509){
BIO_free(bio);
return NULL;
}
}
#ifdef OPENSSL_GOST
X509_print_ex(mem, x509, XN_FLAG_SEP_MULTILINE|ASN1_STRFLGS_UTF8_CONVERT, X509_FLAG_COMPAT);
#else
X509_print_ex(mem, x509, XN_FLAG_SEP_MULTILINE|ASN1_STRFLGS_UTF8_CONVERT, X509_FLAG_COMPAT);
// X509_print(mem , x509);
#endif
X509_free(x509);
break;
/*Просматриваем запрос на сертификат PKCS#10*/
case 'r':
case 'R':
// DER
x509_req = d2i_X509_REQ_bio(bio, NULL);
if (NULL == x509_req){
// PEM
BIO_reset( bio );
x509_req = PEM_read_bio_X509_REQ( bio, NULL, NULL, NULL );
if (NULL == x509_req){
BIO_free(bio);
return NULL;
}
}
X509_REQ_print(mem, x509_req);
X509_REQ_free(x509_req);
break;
/*Просматриваем список отозванных сертификатов CRL*/
case 'c':
case 'C':
/*Сделай сам*/
fprintf(stderr, "Сделай сам!!! file=%s, type=%c\n", nickfile, typeX509);
break;
defaut:
return NULL;
}
len = BIO_get_mem_data(mem, &p);
if(len == 0){
BIO_free(mem);
fprintf(stderr, "BIO_get_mem_data ERROR!!! file=%s, type=%c\n", nickfile, typeX509);
return NULL;
}
res = strdup(p);
res[len - 1] = '\0';
BIO_free(mem);
return res;
}
/* Главная процедура */
static int lswrapCmd(ClientData dummy, Tcl_Interp* interp, int argc, char** argv)
{
int i;
int retv;
Tcl_ResetResult(interp);
if (argc < 2)
return TCL_ERROR;
//Поиск подфункции
for(i = 0; subTable[i].subname != 0; i++)
if(strcmp(argv[1], subTable[i].subname) == 0) {
if((argc -2) != subTable[i].minStack){
char er[1024];
Tcl_SetResult(interp, "Usage: lircw Init gui|line", (Tcl_FreeProc*)0);
sprintf(er, " not enough args: dano=%i, nado=%i ", (argc -2), subTable[i].minStack);
Tcl_AppendResult(interp, er, (char*)0);
return TCL_ERROR;
}
//Выполнение подфункции
retv = subTable[i].f((void*)argv, interp);
return retv;
}
if(subTable[i].subname == 0) {
Tcl_AppendResult(interp, "Плохая команда в lscw: ", argv[1], 0);
return TCL_ERROR;
}
}
int Lscw_Init(Tcl_Interp* interp)
{
OpenSSL_add_all_algorithms();
#ifdef OPENSSL_GOST
ENGINE_load_builtin_engines();
//Загружаем ГОСТ-ый engine
// gost = load_engine("/usr/lib64/openssl/1.0.2m/engines/libgost.so", 0);
gost = load_engine("OPENSSL/libgost.so", 0);
if (!ENGINE_set_default (gost, ENGINE_METHOD_ALL))
{
fprintf(stderr, "OpenSSL error: ENGINE_set_default failed on engine \n");
return TCL_ERROR;
}
#endif
Tcl_CreateCommand(interp, "lscw", (Tcl_CmdProc *)lswrapCmd, (ClientData) 0,
(void (*)()) NULL);
return TCL_OK;
}
#ifdef OPENSSL_GOST
//Функция загрузки engine
static ENGINE *load_engine(const char *engine, int debug)
{
ENGINE *e = ENGINE_by_id("dynamic");
if (e)
{
if (!ENGINE_ctrl_cmd_string(e, "SO_PATH", engine, 0)
|| !ENGINE_ctrl_cmd_string(e, "LOAD", NULL, 0))
{
ENGINE_free(e);
e = NULL;
}
}
return e;
}
#endif
Для сборки использовались статические библиотеки openssl:
$cc -shared -o liblscw.so -DOPENSSL_GOST -DUSE_INTERP_RESULT -fPIC openssl_print.c OPENSSL/libcrypto.a -ldl -pthread –lz
$
После сборки можно смело запускать скрипт, приведенный в начале статьи.
Теперь самое время остановиться на OpenSSL с поддержкой российских криптоалгоритмов. В сожалению, тот ГОСТ-engine, который входит в состав OpenSSL не поддерживает новые криптографические алгоритмы, в частности:
• выработку значения хэш-функции в соответствии с требованиями ГОСТ Р 34.11-2012 «Информационная технология. Криптографическая защита информации. Функция хэширования»;
• формирование и проверку ЭЦП в соответствии с требованиями ГОСТ Р 34.10-2012 «Информационная технология. Криптографическая защита информации. Процессы формирования и проверки электронной цифровой подписи»;
• зашифрование/расшифрование данных алгоритмами шифрования «Кузнечик» (КУЗьмин, НЕЧаев И Компания) и «Магма» в соответствии с требованиями «ГОСТ Р 34.12-2015 Информационная технология. Криптографическая защита информации. Блочные шифры» и «ГОСТ Р 34.13-2015 Информационная технология. Криптографическая защита информации. Режимы работы блочных шифров».
Есть и еще недочеты. Сегодня можно найти модификации OpenSSL, в том числе и сертифицированные в системе сертификации ФСБ России, с полной поддержкой российской криптографии и соответствующие всем нормативным требованиям, в том числе и к квалифицированным сертификатам. Библиотеку lscw, собранную на одном из таких проектов, можно скачать здесь и использовать для просмотра квалифицированных сертификатов:
И наконец, хотелось уйти от tcl-скрипта и получить настоящий бинарный код. Для этого убираем tcl-скрипт в C-код openssl_print_main.c:
Исходный код openssl_print_main.c
транслируем его#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <tcl.h>
#include <tk.h>
//строка с Tcl/Tk - скриптом
char strtcl[] = " \
encoding system utf-8; \
. configure -background {#FFDAB9}; \
wm title . {GUI OpenSSL X509}; \
set homeDir $env(HOME); \
set types {{{Библиотека liblscw} {.so} } \
{{Библиотека liblscw MS} {.dll} } \
{{Любой файл} * } \
}; \
set typesCert { {{Сертификат} {.crt} } \
{{Сертификат} {.cer} } \
{{Сертификат} {.der} } \
{{Сертификат} {.pem} } \
{{Любой файл} * } \
}; \
wm iconify . ; \
set liblscw [tk_getOpenFile -filetypes $types -initialdir $homeDir -title {Выберите библиотеку расширения для OpenSSL}]; \
if { $liblscw == {} } {exit 1}; \
if {[catch {load $liblscw} res]} { \
puts $res; \
exit 1; \
}; \
set loadlib {loading shared lib: } ; \
set loadlib $loadlib$liblscw ; \
puts $loadlib ; \
set fileCert [tk_getOpenFile -filetypes $typesCert -initialdir $homeDir -title {Выберите сертификат/запрос для просмотра}]; \
if { $fileCert == {} } {exit 1}; \
if {[catch {lscw print $fileCert x} res]} { \
puts $res; \
exit 1; \
}; \
wm deiconify .; \
text .prcert -background snow; \
pack .prcert -expand 1 -fill both -in . -pady 6 -padx 6; \
button .but -text Насмотрелся -command exit -background orange -activebackground green; \
pack .but -pady 6; \
.prcert delete 0.0 end ; \
.prcert insert end $res; \
";
Tcl_Interp * tcl_interp ;
main(int argc, char* argv[]){
int code;
Tcl_FindExecutable(argv[0]);
/*Создание tcl-интерпретатора*/
tcl_interp = Tcl_CreateInterp();
/*Инициализация созданного tcl-интерпретатора*/
Tcl_Init(tcl_interp);
/*Инициализация Tk-интерпретатора в контексте созданного tcl-интерпретатора*/
Tk_Init(tcl_interp);
/*Загружаем наш Tcl/Tk код из строки*/
code = Tcl_Eval(tcl_interp, strtcl);
/*Но Tcl/Tk код можно загрузить и из файла:
code = Tcl_EvalFile(tcl_interp, filetcl);
*/
/*Добавляем к нашей кнопки вызов процедуры lenstr при ее нажатии*/
// code = Tcl_Eval(tcl_interp, ".but3 configure -command lenstr;");
/*Связываем имя процедуры "lenstr" с функцией lenStr в этом C-коде*/
// Tcl_CreateCommand(tcl_interp, "lenstr", (Tcl_CmdProc *)lenStr, NULL, NULL);
Tk_MainLoop();
fprintf(stderr, "Конец!\n");
return 0;
}
$cc -o openssl_print_main openssl_print_main.c -ltcl –ltk
$
И выполяем:
$./openssl_print_main
Выбирайте библиотеку, далее сертификат и любуйтесь (см. выше).
Конечный проект по использованию OpenSSL в Tcl/Tk выглядит так:
Проект можно скачать здесь.
Теперь дело осталось за малым, за Android: