Tcl/Tk – создание расширений/пакетов на базе динамических библиотек

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

    Подробнее
    Реклама

    Комментарии 4

      0
      Ждем Android!
        0

        Уже на подходе:
        image

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

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

          0

          Спасибо

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

        Самое читаемое