В одной из наших заметок было высказано намерение написать графические оболочки для пакетов OpenSSL и NSS (Network Security Services). GUI для NSS было написано:

image

Следует отметить, что сегодня openssl с ГОСТ-ым engine-ом завоевывает все большую популярность на просторах российского PKI (инфраструктуры открытых ключей).

И именно поэтому разработка расширения OpenSSL для Tcl может оказаться вполне востребованной. И вот теперь мы покажем как можно разработать GUI для OpenSSL, но не на базе командной утилиты openssl, а используя непосредственно функционал библиотек openssl (libcrypto, libssl).

image Напрямую родные библиотеки 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 не поддерживает новые криптографические алгоритмы, в частности:

image выработку значения хэш-функции в соответствии с требованиями ГОСТ Р 34.11-2012 «Информационная технология. Криптографическая защита информации. Функция хэширования»;

• формирование и проверку ЭЦП в соответствии с требованиями ГОСТ Р 34.10-2012 «Информационная технология. Криптографическая защита информации. Процессы формирования и проверки электронной цифровой подписи»;

• зашифрование/расшифрование данных алгоритмами шифрования «Кузнечик» (КУЗьмин, НЕЧаев И Компания) и «Магма» в соответствии с требованиями «ГОСТ Р 34.12-2015 Информационная технология. Криптографическая защита информации. Блочные шифры» и «ГОСТ Р 34.13-2015 Информационная технология. Криптографическая защита информации. Режимы работы блочных шифров».

Есть и еще недочеты. Сегодня можно найти модификации OpenSSL, в том числе и сертифицированные в системе сертификации ФСБ России, с полной поддержкой российской криптографии и соответствующие всем нормативным требованиям, в том числе и к квалифицированным сертификатам. Библиотеку lscw, собранную на одном из таких проектов, можно скачать здесь и использовать для просмотра квалифицированных сертификатов:

image

И наконец, хотелось уйти от 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

image

Выбирайте библиотеку, далее сертификат и любуйтесь (см. выше).

Конечный проект по использованию OpenSSL в Tcl/Tk выглядит так:

image

Проект можно скачать здесь.

Теперь дело осталось за малым, за Android:

image

Комментарии (3)


  1. vip1953
    26.12.2017 12:25

    Ждем Android!


  1. AntonAlekseevich
    27.12.2017 07:57

    Должна быть изюминка в продукте. И она у нас есть.

    Вот уже что-то более интересное и удобное.


    1. saipr Автор
      27.12.2017 09:39

      Спасибо