GitHub — Keyintegrity/pycryptoprosdk: Библиотека для работы с КриптоПро CSP в python

GitHub - Keyintegrity/pycryptoprosdk: Библиотека для работы с КриптоПро CSP в python ЭЦП
Содержание
  1. /certificate/private_key — установка приватного колюча
  2. /certificate/root — установка корневых сертификатов
  3. /certificates — все установленные сертификаты пользователя
  4. /license?serial_number= — установка серийной лицензии
  5. /signer — подписание документов
  6. /unsigner — получение исходного файла без подписей
  7. /verify — проверка подписанного документа
  8. I. портирование кода модуля tclpkcs11 в модуль pyp11 для python
  9. Ii. сборка и установка модуля pyp11
  10. Iii. управление токенами pkcs#11
  11. Pycryptoprosdk,keyintegrity
  12. V. проверка электронной подписи сертификата
  13. Vi. работа с объектами токена
  14. Аналоги
  15. Без скачивания на диск
  16. Возможные проблемы
  17. Запуск контейнера
  18. Использование контейнера на удаленной машине
  19. Криптопро 5.0 в докер контейнере c расширением pycades
  20. Лицензия
  21. Обработка ошибок
  22. Подписание документа
  23. Получение исходного файла из sig-файла
  24. Примеры использования
  25. Проверка подписи
  26. Просмотр установленных сертификатов
  27. Системные требования
  28. Установка
  29. Установка корневых сертификатов
  30. Установка сертификатов пользователя для проверки и подписания
  31. Формат данных
  32. Через скачивание на диск
  33. Заключение

/certificate/private_key — установка приватного колюча

Для установки приватного колюча нужно передать архив в сервис.
В каталоге certificates/ содержатся различные комбинации тестового сертификата и закрытого ключа, с PIN кодом и без:

С пин-кодом:

Без пин-кодом:

/certificate/root — установка корневых сертификатов

Для установки коневых сертификатов нужно передать файл (с расширением cer или p7b) в сервис.

/certificates — все установленные сертификаты пользователя

Если сертификатов нет:

Если сертификаты есть:

{
  "data_certificates": {
    "certificate_1": {
      "privateKey": {
        "providerName": "Crypto-Pro GOST R 34.10-2022 KC1 CSP",
        "uniqueContainerName": "HDIMAGE\eb5f6857.000\D160",
        "containerName": "eb5f6857-a08a-4510-8a96-df2f75b6d65a"
      },
      "algorithm": {
        "name": "ГОСТ Р 34.10-2022 256 бит",
        "val": "1.2.643.7.1.1.1.1"
      },
      "valid": {
        "from": "23.08.2021 12:07:25",
        "to": "23.08.2022 12:17:25"
      },
      "issuer": {
        "CN": "Test",
        "O": "Test",
        "OU": "Test",
        "STREET": "Test",
        "L": "Москва",
        "C": "RU",
        "raw": "CN=Test, O=Test, OU=Test, STREET=Test, L=Москва, S=77 Москва, C=RU, INN=Test, OGRN=Test"
      },
      "subject": {
        "E": "Test@Test.ru",
        "C": "RU",
        "L": "г Москва",
        "O": "Test",
        "CN": "Test",
        "STREET": "Test",
        "G": "Test",
        "SN": "Test ",
        "raw": "SNILS=Test, OGRN=Test, INN=Test, E=Test@Test.ru, C=RU, S=77 г. Москва, L=г Москва, O=Test, CN=Test, STREET=Test, T=Test, G=Test, SN=Test"
      },
      "thumbprint": "982AA9E713A2F99B10DAA07DCDC94A4BC32A1027",
      "serialNumber": "120032C3567443029CC358FCDF00000032C356",
      "hasPrivateKey": true
    }
  }
}

/license?serial_number= — установка серийной лицензии

Для установки серийного номера лицензии нужно передать номер.

/signer — подписание документов

Для подписания нужно передать файл в сервис.

С пин-кодом:

Без пин-кода:

Вернется JSON — документ, в signedContent будет содержаться подписанный файл и в filename новое имя файла.

/unsigner — получение исходного файла без подписей

Исходный файл вернется в поле content.

/verify — проверка подписанного документа

Для проверки подписи передаем подписанный и не подписанный файлы.

Если файл прошел проверку, вернется список подписантов signers.

I. портирование кода модуля tclpkcs11 в модуль pyp11 для python

Портирование заключается в адаптации кода модуля tclpkcs11 к требованиям со стороны Python. Все изменения в проекте будут касаться только модуля tclpkcs11.c. Поэтому, первое, что мы сделаем, скопируем модуль tclpkcs11.c в файл pythonpkcs11.c и в дальнейшем будем работать именно с ним. Модуль для Python назовем

pyp11

Использовать для его создания будем C API Python. Почему-то этот способ многие (но не я) считают самым трудным, но зато он самый эффективный. Анализ C API для Tcl и C API для Python показал их значительное сходство, что и позволило очень быстро провести портирование.

Первое, в файле pythonpkcs11.c заменяем все объявления Tcl_Obj на PyObject, что вполне естественно: Tcl работает со своими объектами, а Python со своими.

Второе касается передачи параметров.

В общем виде объявление функции, реализующей ту или иную команду Tcl, в С-коде выглядит следующим образом (применительно к нашему коду):

name_proc_tcl (CliendData cd, Tcl_Interp *interp, int objc, Tcl_Obj[] *objv[] ){
          . . .
};

В Python аналогичный заголовок функции будет выглядеть так:

name_proc_py (PyObject *self, PyObject *args){
          . . .
};


В C-коде для tcl проверка количества входных параметров проводится с использованием переменной objc.

name_proc_tcl (CliendData cd, Tcl_Interp *interp, int objc, Tcl_Obj[] *objv[] ){
  if (objc != 4) {
          . . .
    Tcl_SetObjResult(interp, Tcl_NewStringObj("wrong # args: should be "pki::pkcs11::login handle slot password"", -1));
    return(TCL_ERROR);
  }
          . . .
};

В Python параметры передаются в виде кортежа. Поэтому число переданных параметров вычисляется функцией PyTuple_Size(args):

name_proc_py (PyObject *self, PyObject *args){
//Вводим переменную для числа параметров
  int objc;
  objc = PyTuple_Size(args);
          . . .
  if (objc != 3) {
        PyErr_SetString(PyExc_TypeError, "pyp11_login args error (count args != 3)");
	return NULL;
  }
          . . .
};

Отметим, что число параметров в коде для Tcl на единицу больше, т.к. в objv[0] хранится имя функции (аналогично функции main в C).

В приведенном коде наглядно видно как обрабатываются ошибки в Tcl и Python.

Вызов прерывания в случае ошибки для Tcl выполняется оператором return (TCL_ERROR);

Текстовое сообщение об ошибке формируется оператором TclSetObjResult.

Для Python будут использоваться операторы return NULL и PyErr_SetString.

Теперь самое главное — разбор параметров.

В Tcl каждый параметр передается как отдельный Tcl-объект, а в Python — как кортеж параметров в виде Python-объектов. Поэтому, если мы хотим вносить минимальные изменения в код, целесообразно сначала распаковать кортеж по отдельным объектам, например (применительно к функции pyp11_login):

…
char *tcl_handle;
long slotid_long;
char *password;
//Массив PyObject-ов для входных параметров
PyObject *argspy[3];
//Растаскиваем входные параметры/объекты   ("OOO" - три объекта) по своим ячейкам 
PyArg_ParseTuple(args, "OOO", &argspy[0], &argspy[1], &argspy[2])
…


Полученные объекты распаковываем с их функциональным назначением:

…
//Получаем строку (s) с handle библиотеки PKCS11
PyArg_Parse(argspy[0], "s", &tcl_handle);
//Получаем номер слота (l), в котором находится токен
PyArg_Parse(argspy[1], "l", &slotid_long);
//Получаем строку (s) с PIN-кодом владельца 
PyArg_Parse(argspy[2], "s", &password);
...

Сразу оговоримся, что в C API Python имеется функция, которая позволяет сразу разбирать кортеж параметров. В этом случае можно обойтись одним оператором:

PyArg_ParseTuple(args, «sls», &tcl_handle, &slotid_long, &password);

Как ни парадоксально, это практически все рекомендации.

Осталось последнее, — возвращаемые значения.

Результаты выполнения команд возвращаются либо в виде строки, либо в виде списка, либо в виде словаря.

Приведём некоторые соответствия. Так для создания списка в коде для Tcl используется функция Tcl_NewObj(), а в коде для Python используется функция PyListNew(0).

Для добавления элемента в список для Tcl используется функция TclListObjAppendElement, а для Python — функция PyList_Append. Все эти соответствия можно найти, сравнив код TclPKCS11 и код pyp11 (ССЫЛКА).

Также вместо используемых функций ckalloc и ckfree в tclpkcs11.c для Tcl, в модуле pythonpkcs11.c используются стандартные функции работы с памятью — malloc и free.После проведенного анализа модификация кода вместе с тестированием заняла пару рабочих дней.

Ii. сборка и установка модуля pyp11

Итак, скачиваем

и распаковываем его. Заходим в папку PythonPKCS11 и выполняем команду установки:

python3 setup.py install


Лично я тестировал на платформах Windows, Linux, OS X. Отметим, что пакет TclPKCS11 успешно работает и на платформе

После установки модуля переходим в папку tests и начинаем тестирование.

Pаботоспособность модуля pyp11 можно проверить даже без токена. В составе модуля есть функция pyp11.dgst, которая не привязана к токенам и позволяет посчитать хэш по ГОСТ Р 34.10-2022:

bash-4.4$ python3
Python 3.7.9 (default, Feb  1 2021, 16:55:33) 
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyp11
#Считаем хэш по ГОСТ Р 34.11-2022-256 (stribog256)
>>> hash256 = pyp11.dgst("stribog256", "Текст для хэширования")
#Считаем хэш по ГОСТ Р 34.11-2022-512 (stribog512)
>>> hash512 = pyp11.dgst("stribog512", "Текст для хэширования")
>>> print("STRIBOG256="   hash256)
STRIBOG256=26b8865c37831aa254706e6c3514fb23f386358e9dd858703a24d4825d2c4794
>>> print("STRIBOG512="   hash512)
STRIBOG512=e92ff2063c586ec6e9c9569dad7dd503de1c88faafc8b1bf43909bfa36db92ccbf3823f0b8f5d877f10933ed7e670081018dac0929d17729422f05ce1f4c4f25
>>> quit()
bash-4.4$

Значение хэш возвращается в шестнадцатеричном виде.

Для перевода хэш-а в бинарный вид можно воспользоваться следующей функцией:

>>> hash256_bin = bytes(bytearray.fromhex(hash256))

Напомним, как перевести бинарный код в шестнадцатеричный:

>>> hash256 = bytes(hash256_bin).hex()
>>> print("STRIBOG256_NEW="   hash256)
STRIBOG256_NEW=26b8865c37831aa254706e6c3514fb23f386358e9dd858703a24d4825d2c4794
>>>

Есть еще одна функция, которая также может работать без токена. Это функция parsecert. На вход этой функции подается сертификат в DER-формате, упакованный в шестнадцатеричную кодировку:

bash-4.4$ python3
Python 3.7.9 (default, Feb  1 2021, 16:55:33) 
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import pyp11
>>> #Читаем серификат в DER-кодировке из файла
>>> with open("cert_256.der", "rb") as f:
...    cert_der = f.read()
... 
>>> #Упаковываем сертификат der в hex
>>> cert_der_hex = bytes(cert_der).hex()
>>> #Распарсиваем сертификат
>>> pubk = pyp11.parsecert(cert_der_hex)
>>> 

Результатом выполнения команды pyp11.parsecert является словарь (ассоциированный список):

>>>print (pubk.keys())
dict_keys(['pkcs11_id', 'pubkeyinfo', 'pubkey', 'subject', 'issuer', 'serial_number', 'tbsCertificate', 'signature_algo', 'signature'])
>>>

В этом словаре находятся as1-структуры элементов сертификата. Все элементы закодированы в шестнадцатеричный формат. Среди элементов находится элемент pubkeyinfo со значением asn1-структуры subjectpublickeyinfo, элемент pubkey со значением публичного ключа, серийный номер сертификата, tbs-сертификат, который будет использоваться для проверки подписи сертификата, алгоритм подписи сертификата и значение самой подписи, а также элементы с информацией о владельце и издателе сертификата, полученные из сертификата и закодированные в шестнадцатеричное представление:

>>> subject = pubk['subject']
>>> print ('SUBJECT='   subject)
SUBJECT=30820205310b3009060355040613025255312a3028060355042a0c21d09fd0b0d0b2d0b5d0bb20d090d0bdd0b0d182d0bed0bbd18cd0b5d0b2d0b8d1873135303306035504030c2cd09ed09ed09e20d09ad09ed09cd09fd090d09dd098d0af20d0add09ad09e2dd0a1d0a2d0a0d09ed099203937311d301b06092a864886f70d010901160e696e666f4072746564632e6f72673118301606052a85036401120d313137373734363733343433393116301406052a85036403120b3133383632313537373734311a301806082a85030381030101120c3030393732393131303536393130302e060355040c0c27d093d0b5d0bdd0b5d180d0b0d0bbd18cd0bdd18bd0b920d0b4d0b8d180d0b5d0bad182d0bed180310a3008060355040b0c013031353033060355040a0c2cd09ed09ed09e20d09ad09ed09cd09fd090d09dd098d0af20d0add09ad09e2dd0a1d0a2d0a0d09ed099203937315f305d06035504090c5631313931333620d0b32e20d09cd0bed181d0bad0b2d0b020d0bfd1802dd0b420312dd0b920d0a1d0b5d182d183d0bdd18cd181d0bad0b8d0b920d0b42e203130d09020d181d182d1802e203120d0bfd0bed0bc2e20323115301306035504070c0cd09cd0bed181d0bad0b2d0b0311c301a06035504080c13373720d0b32e20d09cd0bed181d0bad0b2d0b0311b301906035504040c12d0a5d0b0d180d0b8d182d0bed0bdd0bed0b2
>>>

Элемент pkcs11_id берётся не из сертификата, а рассчитывается как значение хэш по SHA-1 от значения публичного ключа. При использовании функции pyp11.parsecert в данном контексте (без подключенного токена) pkcs11_id будет равен -1:

>>> pkcs11_id = pubk['pkcs11_id']
>>> print ('PKCS11_ID='   pkcs11_id)
PKCS11_ID=-1
>>>

Кто-то может сказать, а что, разве в Python нет средств разбора сертификатов? А как же, например, asn1crypto? Ответ заключается в том, что в этих средствах не учтены особенности российской криптографии. И вот, чтобы получить максимальную самодостаточность пакета pyp11, в него помимо функций, связанных с генерацией ключевой пары, формирования и проверки подписи, включены дополнительные функции.

В папке tests проекта в файлах test0_* находятся соответствующие тесты.

############УБРАТЬ про FSB795 ################################

Отметим также, что для разбора сертификатов с российской криптографией можно воспользоваться пакетом fsb795:

>>> import fsb795
>>> #Парсим наш сертификат с помощью fsb795
>>> mycert = fsb795.Certificate(cert_der)
>>> #читаем данные о владельце сертификата и типе владельце
>>> dn, type = mycert.subjectCert()
>>> #DN - это словарь/ассоциированный список
>>> for key in dn.keys():
...     print (key   '='   dn[key])
... 
Country=RU
GN=Имя Отчество
CN=ООО КОМПАНИЯ 
E=info@ooo.org
OGRN=xxxxxxxxxxxx
SNILS=xxxxxxxxxxx
INN=xxxxxxxxxxxx
title=Генеральный директор
OU=0
O=ООО КОМПАНИЯ 
street=119136 г. Москва 
L=Москва
ST=77 г. Москва
SN=Харитонов
>>> 

Теперь можно переходить к работе с токенами.

Iii. управление токенами pkcs#11

Для тестирования функций управления подойдет любой токен PKCS#11, даже токен без поддержки какой-либо криптографии, например RuTokenLite. Но поскольку мы ведём речь о российской криптографии, то целесообразно сразу иметь токен с поддержкой российской криптографии.


Установить программный токен или получить доступ к облачному токену можно, воспользовавшись утилитой cryptoarmpkcs.

После запуска утилиты необходимо зайти на вкладку «Создать токены»:

На вкладке можно найти инструкции для получения токенов.

Итак, у нас токен и библиотека для работы с ним. После загрузки модуля pyp11 требуется загрузить библиотеку для работы с нашим токеном. В примерах будут использоваться библиотека librtpkcs11ecp-2.0 для работы с аппаратным токеном, библиотека libls11sw2022 для работы с программным токеном и библиотека libls11cloud.so для работы с облачным токеном.

Итак, загружаем библиотеку командой loadmodule:

bash-4.4$ python3  
Python 3.7.9 (default, Feb  1 2021, 16:55:33) 
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> import pyp11
>>> #Выбираем библиотеку pkcs11
>>> lib = "/usr/local/lib64/librtpkcs11ecp_2.0.so"
>>> #Обработка ошибки при загрузке библиотеки PKCS#11
>>> try:
... #Вызываем команду загрузки библиотеки и плохим числом параметров
...     handlelib = pyp11.loadmodule(lib, 2)
... except:
...     print ('Ошибка загрузки библиотеки: ')
...     e = sys.exc_info()[1]
...     e1 = e.args[0]
...     print (e1)
... 
Ошибка загрузки библиотеки: 

pyp11_load_module args error (count args != 1)
>>> #Загружаем с правильным синтаксисом
>>> idlib = pyp11.loadmodule(lib)
>>> #Печатаем дескриптор библиотеки
>>> print (idlib)
pkcs0
>>> 

Дескриптор загруженной библиотеки используется при её выгрузке:

>>> pyp11.unloadmodule(idlib) 


Теперь, когда библиотека загружена, можно получить список поддерживаемых её слотов и узнать есть ли в каких слотах токены. Для получения списка слотов с полной информацией о них и содержащихся в них токенах используется команда:

>>> slots = pyp11.listslots(idlib)
>>>

Команда pyp11.listslots возвращает список, каждый элемент которого содержит информацию о слоте:

[<info slot1>, <info slot2>, ... , <info slotN>]

В свою очередь, каждый элемент этого списка также является списком, состоящим из четырех элементов:

[<номер слота>, <метка токена, находящегося в слоте>, <флаги слота и токена>, <информация о токене>]

Если слот не содержит токен, то элементы и содержат пустое значение.

Наличие токена в слоте определяется по наличию флага TOKEN_PRESENT в списке <флаги слота и токена>:

bash-4.4$ python3
Python 3.7.9 (default, Feb  1 2021, 16:55:33) 
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> import pyp11
>>> #Выбираем библиотеку
>>> #lib = '/usr/local/lib64/libls11sw2022.so'
>>> lib = '/usr/local/lib64/librtpkcs11ecp_2.0.so'
>>> #Загружаем библиотеку
>>> libid = pyp11.loadmodule(lib)
>>> #Дескриптор библиотеки
>>> print (libid)
pkcs0
>>> #Загружаем список слотов
>>> slots = pyp11.listslots(libid)
>>> tokpr = 0
>>> #Ищем первый подключенный токен
>>> while (tokpr == 0):
... #Перебираем слоты
...     for v in slots:
...         #Список флагов текущего слота
...         flags = v[2]
... #Проверяем наличие в стоке токена
...         if (flags.count('TOKEN_PRESENT') !=0):
...             tokpr = 1
... #Избавляемся от лишних пробелов у метки слота
...             lab = v[1].strip()
...             infotok = v[3]
...             slotid = v[0]
...             break
...     if (tokpr == 0):
...         input ('Нет ни одного подключенного токена.nВставьте токен и нажмите ВВОД')
...     slots = pyp11.listslots(libid)
... #Информация о подключенном токене
... 
Нет ни одного подключенного токена.

Вставьте токен и нажмите ВВОД
''
>>> #Информация о подключенном токене
>>> print ('LAB="'   lab   '", SLOTID='   str(slotid))
LAB="Rutoken lite <no label>", SLOTID=0
>>> print ('FLAGS:', flags)
FLAGS: ['TOKEN_PRESENT', 'RNG', 'LOGIN_REQUIRED', 'SO_PIN_TO_BE_CHANGED', 'REMOVABLE_DEVICE', 'HW_SLOT']
>>>


Если взглянуть на флаги (FLAGS:) подключенного токена, то в них отсутствует флаг ‘TOKEN_INITIALIZED’. Отсутствие этого флага говорит о том, что токен не инициализирован и требуется его инициализация:

#Проверяем, что токен проинициализирован
>>> if (flags.count('TOKEN_INITIALIZED') == 0''):
...         #Инициализируем токен
...         dd = pyp11.inittoken (libid, 0, '87654321',"TESTPY2")
...         
>>>

Как видим, для инициализации токена используется следующая команда:

pyp11.inittoken (<дескриптор библиоткети>, <номер слота>, <SO-PIN>, <метка токена>)

Естественно, токен можно переинициализировать независимо от наличия флага ‘TOKEN_INITIALIZED’, только надо иметь в виду, что переинициализация токена ведет к уничтожению на нем всех объектов (ключи, сертификаты и т.д).

Pycryptoprosdk,keyintegrity

Hello I try to use on with cryptopro 5
and get error
In file included from pycryptoprosdk/libpycades.cpp:8:
pycryptoprosdk/libpycades.cpp: In function ‘char* GetHashOidByKeyOid(char*)’:
/opt/cprocsp/include/cpcsp/WinCryptEx.h:1164:29: warning: ISO C forbids converting a string constant to ‘char*’ [-Wwrite-strings]
1164 | #define szOID_CP_GOST_R3411 «1.2.643.2.2.9»
| ^~~~~~~~~~~~~~~
pycryptoprosdk/libpycades.cpp:35:13: note: in expansion of macro ‘szOID_CP_GOST_R3411’
35 | return szOID_CP_GOST_R3411;
| ^~~~~~~~~~~~~~~~~~~
/opt/cprocsp/include/cpcsp/WinCryptEx.h:1165:36: warning: ISO C forbids converting a string constant to ‘char*’ [-Wwrite-strings]
1165 | #define szOID_CP_GOST_R3411_12_256 «1.2.643.7.1.1.2.2»
| ^~~~~~~~~~~~~~~~~~~
pycryptoprosdk/libpycades.cpp:38:13: note: in expansion of macro ‘szOID_CP_GOST_R3411_12_256’
38 | return szOID_CP_GOST_R3411_12_256;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
/opt/cprocsp/include/cpcsp/WinCryptEx.h:1166:36: warning: ISO C forbids converting a string constant to ‘char*’ [-Wwrite-strings]
1166 | #define szOID_CP_GOST_R3411_12_512 «1.2.643.7.1.1.2.3»
| ^~~~~~~~~~~~~~~~~~~
pycryptoprosdk/libpycades.cpp:41:13: note: in expansion of macro ‘szOID_CP_GOST_R3411_12_512’
41 | return szOID_CP_GOST_R3411_12_512;
| ^~~~~~~~~~~~~~~~~~~~~~~~~~
pycryptoprosdk/libpycades.cpp: At global scope:
pycryptoprosdk/libpycades.cpp:655:27: error: variable ‘PyModuleDef libpycades’ has initializer but incomplete type
655 | static struct PyModuleDef libpycades = {
| ^~~~~~~~~~
pycryptoprosdk/libpycades.cpp:656:5: error: ‘PyModuleDef_HEAD_INIT’ was not declared in this scope
656 | PyModuleDef_HEAD_INIT,
| ^~~~~~~~~~~~~~~~~~~~~
pycryptoprosdk/libpycades.cpp: In function ‘void PyInit_libpycades()’:
pycryptoprosdk/libpycades.cpp:666:9: error: ‘PyModule_Create’ was not declared in this scope; did you mean ‘PyModule_Check’?
666 | m = PyModule_Create(&libpycades);
| ^~~~~~~~~~~~~~~
| PyModule_Check
pycryptoprosdk/libpycades.cpp:668:43: warning: ISO C forbids converting a string constant to ‘char*’ [-Wwrite-strings]
668 | CertDoesNotExist = PyErr_NewException(«libpycades.CertDoesNotExist», NULL, NULL);
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~
pycryptoprosdk/libpycades.cpp:672:12: error: return-statement with a value, in function returning ‘void’ [-fpermissive]
672 | return m;
| ^
error: command ‘x86_64-linux-gnu-gcc’ failed with exit status 1
how to fix this?

/opt/cprocsp/bin/amd64/certmgr -inst -store uroot -f certs/cert_cryptopro_test.cer 
Certmgr 1.0 (c) "CryptoPro",  2007-2022.
program for managing certificates, CRLs and stores

Install:
=============================================================================
1-------
Issuer              : [email protected], C=RU, L=Moscow, O=CRYPTO-PRO LLC, CN=CRYPTO-PRO Test Center 2
Subject             : [email protected], C=RU, L=Moscow, O=CRYPTO-PRO LLC, CN=CRYPTO-PRO Test Center 2
Serial              : 0x2B6E3351FD6EB2AD48200203CB5BA141
SHA1 Hash           : 0x046255290b0eb1cdd1797d9ab8c81f699e3687f3
SubjKeyID           : 15317cb08d1ade66d7159c4952971724b9017a83
Signature Algorithm : ГОСТ Р 34.11/34.10-2001
PublicKey Algorithm : ГОСТ Р 34.10-2001 (512 bits)
Not valid before    : 05/08/2022  13:44:24 UTC
Not valid after     : 05/08/2022  13:54:03 UTC
PrivateKey Link     : No                  
=============================================================================

[ErrorCode: 0x00000000]

А есть ли возможность как то подписывать с использованием сертификатов из хранилищей, на которые установлены пароли? Посмотрел исходники, но не увидел никакой возможности передачи пароля в качестве аргумента

Подскажите, каким образом привязать private.key к самоподписанному x509 сертификату, чтобы Cades смог подписывать контент?

from OpenSSL import crypto
from pycryptoprosdk import CryptoProSDK

CERT_FILE = "selfsigned.crt"
PRIVATE_KEY_FILE = "private.key"
PUBLIC_KEY_FILE = "public.key"
COMMON_NAME = 'Фамилия Имя Отчество'

def raw_cert(bytes_content):
    return ('n'.join(bytes_content.decode().split('n')[1:-2]) 'n').encode()

def create_self_signed_cert(common_name=COMMON_NAME, country='RU', state = 'Test state', city='Test city', organization='test organization', organizational_unit = 'test organizational unit'):
        # create a key pair
        k = crypto.PKey()
        k.generate_key(crypto.TYPE_RSA, 1024)
        # create a self-signed cert
        cert = crypto.X509()
        cert.get_subject().C = country
        cert.get_subject().ST = state
        cert.get_subject().L = city
        cert.get_subject().O = organization
        cert.get_subject().OU = organizational_unit
        cert.get_subject().CN = common_name
        cert.set_serial_number(1000)
        cert.gmtime_adj_notBefore(0)
        cert.gmtime_adj_notAfter(10*365*24*60*60)#10 years
        cert.set_issuer(cert.get_subject())
        cert.set_pubkey(k)
        cert.sign(k, 'sha1')

        open(CERT_FILE, "wb ").write(
            crypto.dump_certificate(crypto.FILETYPE_PEM, cert))
        open(PRIVATE_KEY_FILE, "wb ").write(
            crypto.dump_privatekey(crypto.FILETYPE_PEM, k))
        open(PUBLIC_KEY_FILE, "wb ").write(
            crypto.dump_publickey(crypto.FILETYPE_PEM, k))

create_self_signed_cert() #creates selfsigned.crt, public.key, private.key
sdk = CryptoProSDK()
sdk.install_certificate('MY', raw_cert(open(CERT_FILE, 'rb').read()))
content = "test content"
cert = sdk.get_cert_by_subject('MY',COMMON_NAME)
signature = sdk.sign(content, cert.thumbprint, 'MY', detached=True)

"""
Exception has occurred: ValueError       (note: full exception trace is shown but execution is paused at: _run_module_as_main)
CadesSignMessage failed (error 0x8009200b).
"""

Есть ли возможность получить сам файл публичного сертификата, не в виде объекта, а в виде file-like object или конкретно в виде файла. Из примеров вижу, что сертификат можно положить в хранилище, или удалить, а просто получить публичную часть возможности нет

В README.md есть кусок примера кода with open('certificate.cer'), 'rb') as f:. Тут лишняя закрывающая скобка.
И в Dockerfile используется /opt/cprocsp/bin/amd64/certmgr -inst -store uca -f /certs/kazna.cer, хотя данны сертификат отсутвует в certs/. Где его можно получить?

V. проверка электронной подписи сертификата


Используя полученные знания, напишем пример проверки электронной подписи сертификата:

Vi. работа с объектами токена

Основными объектами, с которыми приходится иметь дело, работая с токенами PKCS#11, являются сертификаты и ключи. И те и другие имеют атрибуты. Нас в первую очередь интересуют атрибуты CKA_LABEL или метка объекта и СКА_ID или идентификатор объекта. Именно атрибут CKA_ID используется для доступа и к сертификатам и ключам.

Уже имея в своем распоряжении рассмотренные выше команды модуля pyp11, можно создать ключевую пару и сформировать подписанный запрос на сертификат. Отправить полученный запрос в удостоверяющий центр и получить там сертификат.

Как работает команда? Первым делом она вычисляет по открытому ключу сертификата идентификатор CKA_ID. Именно этот идентификатор будет возвращен в hex-кодировке после успешного размещения сертификата на токене. После установки сертификата на токен в DER-формате, устанавливаются его атрибуты CKA_ID и CKA_LABEL.

Если вам необходимо связать тройку <сертификат> x <открытый ключ> x <закрытый ключ> не только по CKA_ID, но и по метке CKA_LABEL, то необходимо установить метку у ключевой пары аналогичную метке сертификата. Для этого используется команда rename:

pyp11.rename(<идентификатор библиотеки>, <слот токена>, <тип объекта>, <ассоциированный список>)


В указывается, к каким типам объектов будет применяться команда: ‘cert’ | ‘key’ | ‘all’ (сертификаты, ключевая пара, к тому и другому).

Команда rename позволяет менять не только CKA_LABEL, но и CKA_ID. Конкретные объекты могут задаваться идентификаторами объектов CKA_ID (pkcs11_id), например:

#Импортируем сертификат и получаем его CKA_ID
labcert = 'LabelNEW'
ckaid = pyp11.importcert(aa, 0, cert_der_hex, labcert)
#Устанавливаем метку сертификата и для ключей
#Готовим словарь
ldict = dict(pkcs11_id=ckaid, pkcs11_label=labcert)
#Меняем метки у ключей
pyp11.rename(aa, 0, 'key', ldict)

Аналогичным образом меняется атрибут CKA_ID. В этом случае в словарь вместо метки указывается новый CKA_ID:

ldict = dict(pkcs11_id=ckaid, pkcs11_id_new=11111)


Аналогичным образом можно удалить объекты:

pyp11.delete(<идентификатор библиотеки>, <слот токена>, <тип объекта>, <ассоциированный список>)

При уничтожении в словарь попадает только один элемент, который будет указывать на удаляемые объекты. Это либо CKA_ID (ключ pkcs11_id) либо непосредственно handle-объекта (как правило, его можно получить по команде pyp11.listobjects, ключ pkcs11_handle):

ldict = dict(pkcs11_id=ckaid)
#Или с handle-объекта:
#ldict = dict(hobj=pkcs11_handle)
#Уничтожить личный сертификат с ключами
pyp11.login(aa. 0, '01234567')
pyp11.delete(aa, 0, 'all', ldict)
pyp11.logout(aa, 0)

Упомянем еще об одной очень редко используемой команде. Это команда закрытия сессий на токене:

pyp11.closesession(<идентификатор библиотеки>)

Эту команду следует вызывать, когда возникнет ошибка «PKCS11_ERROR SESSION_HANDLE_INVALID», а затем повторить команду, на которой возникла ошибка. Эта ошибка может возникнуть при кратковременном извлечении токена из компьютера при работе вашей программы.

И завершим мы рассмотрение командой pyp11.listcerts:

Вот пример кода:

#!/usr/bin/python3
#-*- coding: utf-8 -*-
import sys
import time
import pyp11
print('Список сертификатов токена')
aa = pyp11.loadmodule('/usr/local/lib64/libls11sw2022.so')
lcerts = pyp11.listcerts(aa, 0)
if (len(lcerts) == 0):
    print ('На токене нет сертификатов')
    quit()
#Перебираем сертификаты
for cert in lcerts:
    #Информация о сертификате
    for key in cert:
        print (key   ': '   cert[key])
#Сравним с pyp11.listobjects
lm = pyp11.listobjects(aa, 0, 'cert', 'value')
print('Работа с listobjects:')
for obj in lm:
    for key in obj:
        print (key   ': '   obj[key])
quit()

Команды pyp11.listobjects для сертификатов и команда pyp11.listcerts фактически дублируют друг друга, но так сложилось исторически.

Аналоги

Существует аналогичный пакет:

Без скачивания на диск

Примечание: по какой-то причине иногда «заедает», но при повторном запуске — срабатывает.

Возможные проблемы

В Dockerfile содержатся названия пакетов, например lsb-cprocsp-devel_5.0.12000-6_all.deb, которые могут заменить новой версией. Следует поправить названия пакетов в Dockerfile.

Запуск контейнера

Запустим контейнер под именем cryptopro, к которому будем обращаться в примерах:

docker run -it --rm -p 8095:80 --name cryptopro cryptopro_5

Использование контейнера на удаленной машине

В примерах выше команды выглядят так: cat … | docker … или curl … | docker …, то есть контейнер запущен на локальной машине. Если же докер контейнер запущен на удаленной машине, то команды нужно отправлять через ssh клиент. Например, команда подписания:

Опция -q отключает приветствие из файла /etc/banner (хотя оно все равно пишется в stderr). А /etc/motd при выполнении команды по ssh не выводится.

В качестве эксперимента можно отправить по ssh на свою же машину так:

Криптопро 5.0 в докер контейнере c расширением pycades

Инструкция по установке и сборке расширения для языка Python

Содержимое контейнера:

Лицензия

Установка серийного номера:

Просмотр:

Обработка ошибок

Успешные действия возвращают код 200 и «status»: «ok».

Действия с ошибками возвращают 4xx и 5xx коды и «status»: «fail», в полях errMsg содержится описание ошибки, в errCode — ее код.

Например, обращение с неправильным методом

выведет такую ошибку:

Подписание документа

Для примера установим этот тестовый сертификат:

Его SHA1 Hash равен dd45247ab9db600dca42cc36c1141262fa60e3fe (узнать: certmgr -list), который будем использовать как указатель нужного сертификата.

Теперь передадим на stdin файл, в качестве команды — последовательность действий, и на stdout получим подписанный файл:

Получилось довольно неудобно. Скрипт scripts/sign делает то же самое, теперь команда подписания будет выглядеть так:

Об ошибке можно узнать через стандартный $?.

Получение исходного файла из sig-файла

Возьмем файл из примера выше:

То же самое, но с использованием скрипта:

Примеры использования

>>>frompycryptoprosdkimportCryptoProSDK>>>sdk=CryptoProSDK()


# Создание и проверка отсоединенной подписи:>>>content='test content'>>>cert=sdk.get_cert_by_subject('MY', 'Ivan')
>>>signature=sdk.sign(content, cert.thumbprint, 'MY', detached=True)
>>>result=sdk.verify_detached(content, signature)

# статус проверки:>>>result.verification_status0# 0: Успешная проверка подписи.# 1: Отсутствуют или имеют неправильный формат атрибуты со ссылками и значениями доказательств подлинности.# 2: Сертификат, на ключе которого было подписано сообщение, не найден.# 3: В сообщении не найден действительный штамп времени на подпись.# 4: Значения ссылок на доказательства подлинности и сами доказательства, вложенные в сообщение, не соответствуют друг другу.# 5: Не удалось построить цепочку для сертификата, на ключе которого подписано сообщение.# 6: Ошибка проверки конечного сертификата на отзыв.# 7: Ошибка проверки сертификата цепочки на отзыв.# 8: Сообщение содержит неверную подпись.# 9: В сообщении не найден действительный штамп времени на доказательства подлинности подписи.# 10: Значение подписанного атрибута content-type не совпадает со значением, указанным в поле encapContentInfo.eContentType.# сертификат подписанта:>>>result.cert.as_dict()
{'CN': 'Ivan'}


# создание хэша файла алгоритмом ГОСТ Р 34.11-94:>>>sdk.create_hash('some text', alg='CALG_GR3411')
'046255290b0eb1cdd1797d9ab8c81f699e3687f3'# поиск сертификата в хранилище MY по отпечатку:>>>cert=sdk.get_cert_by_thumbprint('MY', '046255290b0eb1cdd1797d9ab8c81f699e3687f3')


# поиск сертификата по имени:>>>cert=sdk.get_cert_by_subject('MY', 'CRYPTO-PRO Test Center 2')


# установка сертификата в хранилище MY:>>>withopen('certificate.cer', 'rb') asf:
>>>cert_content=f.read()
>>>sdk.install_certificate('MY', cert_content)


# удаление сертификата из хранилища MY по отпечатку:>>>sdk.delete_certificate('MY', '9e78a331020e528c046ffd57704a21b7d2241cb3')


# извлечение сертификата подписанта из подписи:>>>withopen('signature.sig', 'rb') asf:
>>>signature_content=f.read()
>>>cert=sdk.get_signer_cert_from_signature(signature_content)

Проверка подписи

Подпишем файл из примера выше и сохраним его на диск:

Тогда проверка подписанного файла будет выглядеть так:

То же самое, но с использованием скрипта:

Просмотр установленных сертификатов

Сертификаты пользователя:

Корневые сертификаты:

Системные требования

  • Python >= 3.5
  • КриптоПро CSP >= 4

Установка

  • Установить КриптоПро CSP.
  • Установить пакеты lsb-cprocsp-devel-.noarch.rpm и cprocsp-pki-amd64-cades.rpm из состава КриптоПро ЭЦП SDK.
  • При необходимости, создать симлинк:
ln -s /opt/cprocsp/lib/amd64/libcades.so.2.0.0 /opt/cprocsp/lib/amd64/libcades.so

Пример установки пакетов можно посмотреть в pycryptoprosdk/compose/Dockerfile.

  • Установить pycryptoprosdk:
pip install pycryptoprosdk

Установка корневых сертификатов

Для установки корневых сертификатов нужно на stdin скрипта /scripts/root передать файл с сертификатами. Если в файле несколько сертификатов, все они будут установлены.

Установка сертификатов пользователя для проверки и подписания

Необходимо специальным образом сформировать zip-архив bundle.zip и отправить его на stdin скрипта /scripts/my. Пример такого zip-файла:

Как получить сертификат КриптоПро.

Первый найденный файл в корне архива будет воспринят как сертификат, а первый найденный каталог — как связка файлов закрытого ключа. Пароль от контейнера, если есть, передается первым параметром командной строки.

В каталоге certificates/ содержатся различные комбинации тестового сертификата и закрытого ключа, с PIN кодом и без:

Примеры:

Формат данных

Возвращаются данные в формате JSON.

Через скачивание на диск

Скачаем сертификат на диск с помощью curl и передадим полученный файл на stdin с запуском команды его установки:

Заключение

Опыт использования аналогичного модуля tclpkcs11 показывает, что функциональности, заложенной в модуль pyp11 для Python, с лихвой хватит для его использования в

ИОК

для работы с электронной подписью на базе российской криптографии. Более того, во

будет рассмотрен класс token, в рамках которого будут создаваться объекты для подключенных токенов. И это позволит ещё больше упростить работу с токенами. Кстати, аналогичный класс для tclpkcs11 уже имеется.


Но в заключении я хотел бы вернуться к началу статьи, а именно к проекту PyKCS11.

Когда я писал письмо авторам проекта PyKCS11, то я уже добавил в него поддержку российской криптографии и сообщал им об этом:

Сейчас заканчивается тестирования PyKCS11 для российской криптографии. Кстати, модуль pyp11 хорошо дополняет PyKCS11. Поэтому должна появиться и третья часть статьи, в которой будет рассказано, как добавить поддержку российской криптографии в проект PyKCS11.

Оцените статью
ЭЦП64
Добавить комментарий