В одной из наших заметок было высказано намерение написать графические оболочки для пакетов 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:

