certopenstore криптопро

certopenstore криптопро Электронная цифровая подпись
Содержание
  1. Введение.
  2. Re: КриптоПро расширение для PHP 7
  3. Re: КриптоПро расширение для PHP 7
  4. Re: КриптоПро расширение для PHP 7
  5. Re: КриптоПро расширение для PHP 7
  6. Re: КриптоПро расширение для PHP 7
  7. #1 2010-02-24 11:58:21
  8. Crypto API
  9. #2 Ответ от MKurskiy 2010-02-25 15:10:45
  10. Re: Crypto API
  11. #3 Ответ от Алексей Зикеев 2010-02-25 15:26:45
  12. Re: Crypto API
  13. #4 Ответ от Алексей Зикеев 2010-02-25 15:28:43
  14. Re: Crypto API
  15. #5 Ответ от MKurskiy 2010-02-25 16:12:37
  16. Re: Crypto API
  17. #6 Ответ от Алексей Зикеев 2010-02-25 16:25:12
  18. Re: Crypto API
  19. #7 Ответ от Алексей Зикеев 2010-03-09 12:27:22
  20. Re: Crypto API
  21. #8 Ответ от GoodMind 2010-06-04 14:45:11
  22. Re: Crypto API
  23. #9 Ответ от MKurskiy 2010-06-04 14:55:42
  24. Re: Crypto API
  25. #10 Ответ от GoodMind 2010-06-04 15:00:35
  26. Re: Crypto API
  27. #11 Ответ от MKurskiy 2010-06-04 15:11:30
  28. Re: Crypto API
  29. #12 Ответ от Алексей Зикеев 2010-06-04 15:12:33
  30. Re: Crypto API
  31. #13 Ответ от GoodMind 2010-06-04 15:17:20
  32. Re: Crypto API
  33. #14 Ответ от MKurskiy 2010-06-04 15:19:17
  34. Re: Crypto API
  35. #15 Ответ от GoodMind 2010-06-04 22:34:30
  36. Re: Crypto API
  37. Реализация взаимодействия с КриптоПро CSP на C.
  38. Загрузка сертификата в хранилище.
  39. Загрузка файла отозванных сертификатов.
  40. Постороение цепочки сертификата.
  41. Прверка подписи.
  42. Теоритическая часть.
  43. Заключение.

Введение.

В одной из поддерживаемых мной систем цифровая подпись сообщений проверялась с помощью КриптоПро CSP и библиотека отвечающая за это функцию периодически падала с ошибкой. Библиотека эта писалась в спешке и не мной, поэтому я решил переделать ее “по-человечески” и оформить в виде python модуля. Ниже я опишу процесс разработки и опишу с какими трудностями я столкнулся.

Всем привет!

Не могу победить ситуацию.   Пытаюсь перенести закрытый ключ с VIPnet 4.2/ 4.4  Windows  в PFX на Крипто-про. Уже делал это с прошлыми ключами.

Но сейчас неожиданно получил эффект:  Закрытый ключ не экспортируется или экспортируется с каким-то хитрым алгоритмом. Крипто-Про в логах ругается

pfx_AddCertAttributesToContext!() pfx - pkcs12 attribute unsupported
cptools:pfx_AddCertAttributesToContext!() pfx - pkcs12 attribute unsupported
cptools:

Обновился на  VipNet 4.4 (Windows), но не может обратно импортировать только что собой созданный PFX говорит  пароль “не подходит” (ага, только что созданный типа 123, 12345678)

openssl pkcs12 -in container.pfx -nodes

тоже не видит закрытого ключа. в раннем PFX  видит.

При этом  ключ из контейнера работает, VipNet 4.2 (Linux) подписывает что надо, на Windows тоже работает.
Но закрытый ключ не удается достать достать для переноса.

Предыдущие выпускал в Тензор, и ИТК  в начале года,  новый ключ в августе  в Тензор. Ключи выпускались удаленно в VipNet контенер.
Проверил типы шифрования одинаковые ГОСТ 34.10-2012/512

Единственное различие нашел:  Новый ключ имеет поле:
Embedded License    : CryptoPro CSP

Куда копать?  Как еще можно получить закрытый ключ из контейнера?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
#include <windows.h>
#include <wincrypt.h>
 
unsigned long LoadCertFromSysStore(X509 *tmp_user_cert)
{
        unsigned char *ptr;
        int  len = 0;
    HCERTSTORE hSysStore;
    PCCERT_CONTEXT pCert, pCACert = NULL;
    CRYPT_HASH_BLOB *pAKI;
    CERT_NAME_BLOB issuerPrev, subjectCur;
 
    ptr = NULL;
    len = i2d_X509(tmp_user_cert, &ptr);
    
        pCert = CertCreateCertificateContext(MY_ENCODING_TYPE, ptr, len); // 1 функция
    issuerPrev = pCert->CertInfo->Issuer;
 
 
    int i;
    char *OID_AKID = "2.5.29.35";
    DWORD dwLenAKI;
    CERT_AUTHORITY_KEY_ID2_INFO *pValueAKI = NULL;
    int cExt = pCert->pCertInfo->cExtension;
    for (i = 0; i < cExt; i++)
    {
        if (!strcmp(pCert->pCertInfo->rgExtension[i].pszObjId, OID_AKID))
        {
            CryptDecodeObjectEx(X509_ASN_ENCODING, X509_AUTHORITY_KEY_ID, pCert->pCertInfo->rgExtension[i].Value.pbData, pCert->pCertInfo->rgExtension[i].Value.cbData, CRYPT_DECODE_NOCOPY_FLAG, NULL, NULL, &dwLenAKI); // 2 функция
            pValueAKI = (CERT_AUTHORITY_KEY_ID2_INFO *)malloc(dwLenAKI);
                        CryptDecodeObjectEx(X509_ASN_ENCODING, X509_AUTHORITY_KEY_ID, pCert->pCertInfo->rgExtension[i].Value.pbData,pCert->pCertInfo->rgExtension[i].Value.cbData, CRYPT_DECODE_NOCOPY_FLAG, NULL, pValueAKI, &dwLenAKI);
            break;
 
            break;
        }
    }
     
    pAKI = &(pValueAKI->KeyId);
    hSysStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER, L"CA"); // 3 функция
        
        pCACert = CertFindCertificateInStore(hSysStore, MY_ENCODING_TYPE, 0, CERT_FIND_KEY_IDENTIFIER, pAKI, pCACert); // 4 функция
    if (!pCACert)
    {
        hSysStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_CURRENT_USER, L"Root");
        pCACert = CertFindCertificateInStore(hSysStore, MY_ENCODING_TYPE, 0, CERT_FIND_KEY_IDENTIFIER, pAKI, NULL);
        if (!pCACert)
        {
            KC_InternalSaveError(1, KCR_CACERTNOTFOUND, KCF_ENG_LOADCERTFROMSYSSTORE, KCR_ENG_CACERTNOTFOUND);
            rv = KCR_CACERTNOTFOUND;
            goto end;
        }   
    }
    subjectCur = pCACert->pCertInfo->Subject;
    int k = CertCompareCertificateName(MY_ENCODING_TYPE, &issuerPrev, &subjectCur); // 5 функция
    
    switch (CertVerifyTimeValidity(NULL, pCACert->pCertInfo)) // 6 функция
    {
        case -1:
        {
            ...
        }
        case 1:
        {
            ...
        }
        case 0:  
                        ...
    }
 
....
//дальше


Offline

Artem Vasilev

 

Оставлено
:

25 ноября 2021 г. 17:27:01(UTC)

Собрал модуль для php 7.4 с последним сертифицированым дитрибутивом КриптоПРО 5 (5.0.12000)
Получил тестовый сертификат, поставил в контейнер, подтянул цепочки, запускаю скрипт test_extension.php, получаю ошибку

Цитата:

Aborted (core dumped)

Захожу и удаляю в коде установку штампа времени.

Запускаю снова – подписалось, хэш на экране есть, а следом

Цитата:

munmap_chunk(): invalid pointer
Aborted (core dumped)

Ошибка возникает в месте вызова метода VerifyCades();

Операционная система:

Цитата:

LSB Version: :core-4.1-amd64:core-4.1-noarch:cxx-4.1-amd64:cxx-4.1-noarch:desktop-4.1-amd64:desktop-4.1-noarch:languages-4.1-amd64:languages-4.1-noarch:printing-4.1-amd64:printing-4.1-noarch
Distributor ID: AlmaLinux
Description: AlmaLinux release 8.5 (Arctic Sphynx)
Release: 8.5
Codename: ArcticSphynx


Offline

Artem Vasilev

 

Оставлено
:

25 ноября 2021 г. 17:59:22(UTC)

Добавлю информации

Удалил все корневые и промежуточные сертификаты, которые ставил

Поставил сертификаты отсюда: http://q20.cryptopro.ru/

Дополнительно поставил сертификат CRYPTO-PRO Test Center 2

В результате перестал получать даже подпись. Скрипт проверки падает в ошибку

Segmentation fault (core dumped)

или

munmap_chunk(): invalid pointer
Aborted (core dumped)

Через раз

UPD: Не хватало одного сертификата. Поставил и ошибка “Segmentation fault (core dumped)” вернулась

Попробовал включить лог

Код:


sudo /opt/cprocsp/sbin/amd64/cpconfig -loglevel сades -mask 0xF
sudo /opt/cprocsp/sbin/amd64/cpconfig -loglevel tsp -mask 0xF
sudo /opt/cprocsp/sbin/amd64/cpconfig -loglevel ocsp -mask 0xF

Вывод journalctl -f:

Отредактировано пользователем 25 ноября 2021 г. 18:23:47(UTC)
 | Причина: Не указана


Offline

Санчир Момолдаев

 

Оставлено
:

25 ноября 2021 г. 18:30:41(UTC)

thanks 1 пользователь поблагодарил Санчир Момолдаев за этот пост.


Offline

Artem Vasilev

 

Оставлено
:

26 ноября 2021 г. 11:29:46(UTC)

Автор: Санчир Момолдаев Перейти к цитате

Спасибо!

Да, действительно, “подсунуть” системный libcurl помогло

Для потомков:

В Almalinux нужно проверить наличие libcurl, в принципе

Код:

ls -lah /usr/lib64/libcurl*

Если в выдаче есть /usr/lib64/libcurl.so, то всё “ок”
Если нет – делайте софтлинк

Код:

ln -s /usr/lib64/libcurl.so.4 /usr/lib64/libcurl.so

Дальше прописываем в конфиг КриптоПро путь к этой библиотеке

Код:

/opt/cprocsp/sbin/amd64/cpconfig -ini '\cryptography\apppath' -add string 'libcurl.so' '/usr/lib64/libcurl.so'

Подозреваю, что данный метод будет работать на всех RHEL 8 совместимых системах

Аватара пользователя

rozen

Сообщения: 169
Зарегистрирован: 25 мар 2015, 12:11
Имя: Андрей
Откуда: Красноярск

Re: КриптоПро расширение для PHP 7

Расширение заработало, но при добавлении сертификата 502 Bad Gateway
В логах сервера:

Код: Выделить всё

2020/10/28 08:58:22 [error] 1067#1067: *4188 recv() failed (104: Connection reset by peer) while reading response header from upstream, 
client: 10.89.*.*, server: cto.krw.rzd, request: "POST /rukovoditel/index.php?module=users/signature_account&action=update HTTP/2.0", 
upstream: "fastcgi://unix:/var/run/php/php7.4-fpm.sock:", host: "cto.krw.rzd", 
referrer: "https://cto.krw.rzd/rukovoditel/index.php?module=users/signature_update"

Проверка настройки показывает: test init OK
Сертификаты добавлены.

В системных логах такое:

Код: Выделить всё

Oct 29 10:03:44 10 ool www: <capi10>CryptEnumOIDInfo!failed: LastError = 0xEA
Oct 29 10:03:44 10 ool www: <capi10>CryptVerifySignatureW!failed: LastError = 0x80090006
Oct 29 10:03:44 10 ool www: <capi20>CryptVerifyCertificateSignature!failed: LastError = 0x80090006
Oct 29 10:03:44 10 ool www: <capi20>CertOpenStore!failed: LastError = 0x2
Oct 29 10:03:44 10 ool www: <capi20>CertOpenStore!failed: LastError = 0x2
Oct 29 10:03:49 10 ool www: <capi10>CryptEnumOIDInfo!failed: LastError = 0xEA
Oct 29 10:03:49 10 ool www: <capi10>CryptVerifySignatureW!failed: LastError = 0x80090006
Oct 29 10:03:49 10 ool www: <capi20>CryptVerifyCertificateSignature!failed: LastError = 0x80090006
Oct 29 10:03:49 10 ool www: <capi20>CertOpenStore!failed: LastError = 0x2
Oct 29 10:03:49 10 ool www: <capi20>CertOpenStore!failed: LastError = 0x2

Решил проверить хранилище сертификатов: с нем пусто.
Но добавление проходит без ошибок. Вот только куда?

Код: Выделить всё

 /opt/cprocsp/bin/amd64/certmgr -list
Certmgr 1.1 (c) "КРИПТО-ПРО", 2007-2020.
Программа для работы с сертификатами, CRL и хранилищами.
=============================================================================
Список сертификатов пуст

Требуемый сертификат не существует.

Аватара пользователя

rozen

Сообщения: 169
Зарегистрирован: 25 мар 2015, 12:11
Имя: Андрей
Откуда: Красноярск

Re: КриптоПро расширение для PHP 7

Сообщение

rozen » 30 окт 2020, 04:06

Сейчас так: сертификатов больше. Показал один.

Код: Выделить всё

sudo /opt/cprocsp/bin/amd64/certmgr -list -store mRoot
[sudo] password for rozen:
Certmgr 1.1 (c) "КРИПТО-ПРО", 2007-2020.
Программа для работы с сертификатами, CRL и хранилищами.
=============================================================================
1-------
Издатель            : E=dit@minsvyaz.ru, C=RU, S=77 Москва, L=г. Москва, STREET="улица Тверская, дом 7", O=Минкомсвязь России, OGRN=1047702026701, INN=007710474375, CN=Минкомсвязь России
Субъект             : E=uc@mil.ru, OGRN=1037700255284, INN=007704252261, C=RU, S=77 г. Москва, L=Москва, STREET="ул. Знаменка, д. 19", OU=4 центр (удостоверяющий) войсковой части 31659, O=Министерство обороны Российской Федерации, CN=Министерство обороны Российской Федерации
Серийный номер      : 0x3252B37400000000044E
Хэш SHA1            : 1e4977c9cad2358853391267d4e5d47e4ca7ba6f
Идентификатор ключа : 650b083aa72a8dc7a6449682b97df99d6b8e2aa8
Алгоритм подписи    : ГОСТ Р 34.11-2012/34.10-2012 256 бит
Алгоритм откр. кл.  : ГОСТ Р 34.10-2012 (512 бит)
Выдан               : 31/07/2020  11:15:02 UTC
Истекает            : 31/07/2035  11:15:02 UTC
Ссылка на ключ      : Нет
URL сертификата УЦ  : http://reestr-pki.ru/cdp/guc_gost12.crt
URL списка отзыва   : http://reestr-pki.ru/cdp/guc_gost12.crl
URL списка отзыва   : http://company.rt.ru/cdp/guc_gost12.crl
URL списка отзыва   : http://rostelecom.ru/cdp/guc_gost12.crl

Что не работает – не понимаю. Либо модуль сбоит, но проверку проходит. Либо … либо.
Кстати, тест от КриптоПро не проходит.

Код: Выделить всё

Cannot find object or property. (0x80092004)TEST FAIL 

Код: Выделить всё

PHP Fatal error:  Uncaught Error: Class 'CPStore' not found 

Аватара пользователя

rozen

Сообщения: 169
Зарегистрирован: 25 мар 2015, 12:11
Имя: Андрей
Откуда: Красноярск

Re: КриптоПро расширение для PHP 7

Сообщение

rozen » 30 окт 2020, 07:22

Выполнение через командную строку кода:

Код: Выделить всё

<?php
ini_set("log_errors", 1);
ini_set ('display_errors', 1);
error_reporting (E_ALL);  
try
{   $sd = new CPSignedData();
    $content = "test content"; 
    $sd = new CPSignedData();
    $sd->set_Content($content); 
    printf("test init OK\n");
}
catch (Exception $e)
{
    printf($e->getMessage());
} 
?>

Ответ:

Код: Выделить всё

Fatal error: Uncaught Error: Class 'CPSignedData' not found in /var/www/html/site/csp.php:6

Аватара пользователя

rozen

Сообщения: 169
Зарегистрирован: 25 мар 2015, 12:11
Имя: Андрей
Откуда: Красноярск

Re: КриптоПро расширение для PHP 7

Сообщение

rozen » 02 ноя 2020, 12:38

Продолжу:

Сделал:

Код: Выделить всё

php -i|grep php.ini
Configuration File (php.ini) Path => /etc/php/7.4/cli
Loaded Configuration File => /etc/php/7.4/cli/php.ini

Это странно, так как phpinfo показывает

Код: Выделить всё

Loaded Configuration File /etc/php/7.4/fpm/php.ini

Я вносил extension=libphpcades.so в файл /etc/php/7.4/fpm/php.ini
Сейчас внес в /etc/php/7.4/cli/php.ini

Делаю проверку:

Код: Выделить всё

sudo php -f /opt/cprocsp/src/phpcades/test_extension.php
[sudo] password for rozen:
Cannot find object or property. (0x80092004)TEST FAIL

Ответ поддержки КриптоПро:

Код: Выделить всё

>>sudo php -f /opt/cprocsp/src/phpcades/test_extension.php
>>[sudo] password for rozen:
>>Cannot find object or property. (0x80092004)TEST FAIL

Расширение корректно работает.

Если Вы запускаете проверочный скрипт через sudo, то под пользователем root у Вас должен быть установлен личный сертификат с привязкой к соответствующему ключевому контейнеру с указанной строкой (по умолчанию в скрипте – “Test”) в поле “Субъект” сертификата.

Вы можете сменить строку в скрипте или пользователя для прохождения проверки с помощью тестового скрипта.


Аватара пользователя

Евгений

Сообщения: 446
Зарегистрирован: 11 июл 2016, 13:21
Имя: Евгений
Откуда: Петропавловск-Камчатский

Re: КриптоПро расширение для PHP 7

Сообщение

Евгений » 12 апр 2022, 02:18

rozen писал(а): ↑

20 июл 2020, 11:42

Добрый день!
Кто-то установил расширение для PHP от КриптоПро?

Подскажите пожалуйста, какой версией php у вас получилось работать с ЭЦП на сервере Руководитель?
У меня PHP Version 7.3.31-1~deb10u1

Я настроил модуль ЭЦП, сертификаты видны в списке выбора, но при нажатии на кнопку “Выбрать” сертификат не подключается, выпадает ошибка: “при проверке токена, некорректные данные: []”

Screenshot_20220412_111737.png

Страницы 1

Чтобы отправить ответ, нужно авторизоваться или зарегистрироваться

#1 2010-02-24 11:58:21

  • Алексей Зикеев

    Crypto API

    Добрый день ! Подскажите ,пожалуйста, как средствами CryptoApi открыть сертификат с RuToken ? Т.е. хочется сделать CertOpenStore, но чтобы она открыла именно токен, находящийся на данный момент в компьютере.

    #2 Ответ от MKurskiy 2010-02-25 15:10:45

    • MKurskiy

      Re: Crypto API

      Добрый день.Алексей, подскажите, какая перед Вами стоит задача? что Вам необходимо получить в итоге?

      Дело в том, что функция CertOpenStore работает с локальным хранилищем.В рамках интерфейса CryptoAPI в драйверах Rutoken реализован интерфейс Cryptographic Service Provider (CSP), с помощью которого можно получить сертификат как свойство ключевой пары, и зарегестрировать его в локальном хранилище, в нужном разделе.Кроме того, сертификаты регистрируются в локальном хранилище автоматически после подключения Rutoken по средствам Certificate Propogation Service.

      Михаил КурскийРуководитель отдела разработки прикладного ПО Rutoken, Компания “Актив”

      #3 Ответ от Алексей Зикеев 2010-02-25 15:26:45

      • Алексей Зикеев

        Re: Crypto API

        Для каждого пользователя на сервере сохранены данные, зашифрованные его открытым ключом. Кроме того сохранена информация о Issuer и SerialNumber из того сертификата, который использовался для каждого набора зашифрованных данных. При запуске моей программы необходимо установить чей токен воткнут, и расшифровать соответствующие этому пользователю данные.  собственно задача – понять, чьи данные отправлять на расшифровку.

        #4 Ответ от Алексей Зикеев 2010-02-25 15:28:43

        • Алексей Зикеев

          Re: Crypto API

          Может сохранять с зашифрованными данными открытые ключи и сравнивать его с экспортированным с токена посредствам Active CSP ?

          #5 Ответ от MKurskiy 2010-02-25 16:12:37

          • MKurskiy

            Re: Crypto API

            Это можно сделать по средствам CSP.
            1. Перебрать доступные контейнеры с подключенного Rutoken:
                1.1 CryptAcquireContext(… CRYPT_VERIFYCONTEXT …);
                1.2 CryptGetProvParam(… PP_ENUMCONTAINERS…);
                1.3 Для каждого контейнера получить хендл ключевой пары – CryptGetUserKey;
                1.4 Получить у ключа свойство сертификата – CryptGetKeyParam(… KP_CERTIFICATE…);
            2. Создать контекст из данных сертификата – CertCreateCertificateContext.
            3. Получить из контекста сертификата необходимые свойства – CertGetCertificateContextProperty, сравнить с имеющимися.
            4. Расшифровать данные по средствам CryptAPI и CSP.

            Единственно, не рекомендую Вам использовать открытый ключ ассиметричного алгоритма для шифрования конечных данных – это очень медленная операция по сравнению с симетричным шифрованием.
            Эфективнее создать по средствам CSP сессионный ключ, зашифровать им данные, а потом выгрузить его из CSP в зашифрованном на открытом ключе RSA, и сохранить рядом с зашифрованными данными. И в открытом виде положить рядом информацию о владельце сертификата.
            О использовании сессионных ключей лучше посмотреть здесь: http://msdn.microsoft.com/en-us/library … S.85).aspx

            Михаил КурскийРуководитель отдела разработки прикладного ПО Rutoken, Компания “Актив”

            #6 Ответ от Алексей Зикеев 2010-02-25 16:25:12

            • Алексей Зикеев

              Re: Crypto API

              Спасибо большое, буду пробовать ! Я шифрую от 10 до 24 байт, так что нет смысла связываться с сессионными ключами.

              #7 Ответ от Алексей Зикеев 2010-03-09 12:27:22

              • Алексей Зикеев

                Re: Crypto API

                Все получилось, спасибо большое !

                #8 Ответ от GoodMind 2010-06-04 14:45:11

                • GoodMind

                  Re: Crypto API

                  Здравствуйте.

                  Я никак не могу получить хендл ключевой пары в CryptGetUserKey – что нужно указать в параметре dwKeySpec?
                  Что бы я не указывал, ответ один – NTE_NO_KEY (The key requested by the dwKeySpec parameter does not exist.)

                  #9 Ответ от MKurskiy 2010-06-04 14:55:42

                  • MKurskiy

                    Re: Crypto API

                    Это зависит от свойств ключевой пары в контейнере.Тип ключевой пары контейнера задается на этапе его создания, либо указан в шаблоне сертификата.Возможных варианта два: AT_KEYEXCHANGE и AT_SIGNATURE.Если при обоих значениях выдается ошибка NTE_NO_KEY, значит в контейнере отсутствует ключевая пара – она либо не сгенерирована, либо не записана в контейнер.

                    Михаил КурскийРуководитель отдела разработки прикладного ПО Rutoken, Компания “Актив”

                    #10 Ответ от GoodMind 2010-06-04 15:00:35

                    • GoodMind

                      Re: Crypto API

                      Да, ошибка при обоих значениях AT_KEYEXCHANGE и AT_SIGNATURE. Как тогда можно получить сертификат (имя ключевого контейнера известно)?

                      #11 Ответ от MKurskiy 2010-06-04 15:11:30

                      • MKurskiy

                        Re: Crypto API

                        Сертификат, в рамках CryptoAPI, это свойство ключевой пары – KP_CERTIFICATE.Если в контейнере нет ключей, то контейнер пуст – в нем не может быть сертификата.Это возможно только в том случае, если ключ либо не был сгенерирован, либо не был записан в контейнер.

                        Михаил КурскийРуководитель отдела разработки прикладного ПО Rutoken, Компания “Актив”

                        #12 Ответ от Алексей Зикеев 2010-06-04 15:12:33

                        • Алексей Зикеев

                          Re: Crypto API

                          Я на некоторое время оставил этот проект и уже не все помню, но, по моему, есть некоторые особенности использования  CryptAcquireContext(… CRYPT_VERIFYCONTEXT …); в части использования параметра CRYPT_VERIFYCONTEXT. Я работал с сертификатами, созданными службой сертификации Windows

                          #13 Ответ от GoodMind 2010-06-04 15:17:20

                          • GoodMind

                            Re: Crypto API

                            Сертификат в контейнере есть, его можно посмотреть через консоль КриптоПро CSP

                            #14 Ответ от MKurskiy 2010-06-04 15:19:17

                            • MKurskiy

                              Re: Crypto API

                              CRYPT_VERIFYCONTEXT в нашем CSP не позволяет использовать только закрытый ключ (как требует документация MSDN).Если CryptAcquireContext был вызван с флагом CRYPT_VERIFYCONTEXT, и был получен хендл реального контейнера, то можно получить и хендл ключа, и сертификат.Ограничения могут быть только при использовании CryptSignHash и т.д.

                              В других CSP это может быть не так.

                              Михаил КурскийРуководитель отдела разработки прикладного ПО Rutoken, Компания “Актив”

                              #15 Ответ от GoodMind 2010-06-04 22:34:30

                              • GoodMind

                                Re: Crypto API

                                Да действительно дело было в параметре CRYPT_VERIFYCONTEXT. Без него в функции CryptAcquireContext все получилось.

                                Реализация взаимодействия с КриптоПро CSP на C.

                                Так как КриптоПро CSP(CPCSP) является доработкой CryptoApi от Microsoft, то большая часть примеров из официальной документации подходит идля “КриптоПро”. Чем я собствеено говоря и пользовался при написании модуля, так как с примерами у самого КриптоПро не очень все хорошо.

                                Загрузка сертификата в хранилище.

                                Для того, чтобы загрузить сертификат в хранилище нужно выполнить следующие шаги:

                                1. Считать сертификат из файла
                                2. Открыть хранилище сертификатов
                                3. Положить в него сертификат
                                4. Закрыть хранилище

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

                                typedef struct CERT {
                                    BYTE *content;
                                    DWORD size;
                                } CERT;
                                
                                CERT readFile(char *filename)
                                {
                                    CERT cert = {NULL, 0};
                                    FILE *fCert;
                                
                                    fCert = fopen(filename, "r");
                                    if (fCert)
                                    {
                                		fseek(fCert, 0, SEEK_END);   
                                		cert.size = ftell(fCert);
                                		rewind(fCert);
                                
                                	    cert.content = (unsigned char *)malloc(cert.size * sizeof(unsigned char));
                                	    fread(cert.content, cert.size, 1, fCert);
                                    }
                                    else
                                    {
                                		perror("Error open certificate file");
                                    }
                                    fclose(fCert);
                                
                                  return cert;
                                }
                                
                                PCCERT_CONTEXT ReadCertificateFromFile(char *filename)
                                {
                                    CERT fileCert; 
                                    PCCERT_CONTEXT cert = NULL;
                                
                                    fileCert = readFile(filename);
                                		
                                    cert = CertCreateCertificateContext(
                                		X509_ASN_ENCODING,
                                		fileCert.content,
                                		fileCert.size
                                	);
                                    
                                    if (!(cert))
                                    {
                                		perror("Error create cert");
                                    }
                                
                                  return cert;
                                }
                                

                                В коде выше файл считывается специальную структуру CERT, которая содержит размер и содержимое сертификата. Затем на основе этой информации формируется структура PCCERT_CONTEXT, которая в дальнейшем будет загружаться в хранилище CPCSP.

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

                                # define OPERATION_SUCCESS        0
                                # define OPEN_STORE_ERROR         1
                                # define ADD_CERT_TO_STORE_ERROR  2
                                # define CLOSE_STORE_ERROR        3
                                # define ADD_CRL_TO_STORE_ERROR   4
                                # define STR_TO_BIN_LEN_ERROR     5
                                # define STR_TO_BIN_CONTENT_ERROR 6
                                # define VERIFY_MSG_SIGNATURE     7
                                # define GET_CERT_CHAIN_ERROR     8
                                # define READ_CERT_ERROR          9
                                # define READ_CRL_ERROR           10
                                

                                Функция загрузки сертификата в хранилище будет выглядеть следующим образом:

                                int LoadCertificateToSystemStore(char *cert_file_path, char *store_name)
                                {
                                    HCERTSTORE     cpcsp_cert_store = NULL;
                                    PCCERT_CONTEXT cert_context;
                                
                                    cert_context = ReadCertificateFromFile(cert_file_path);
                                    if (!cert_context)
                                	return READ_CERT_ERROR;
                                  
                                    cpcsp_cert_store = CertOpenSystemStore(0, store_name);
                                
                                    if (!cpcsp_cert_store)
                                	return OPEN_STORE_ERROR;
                                
                                    if (!CertAddCertificateContextToStore(
                                	    cpcsp_cert_store,
                                	    cert_context,
                                	    CERT_STORE_ADD_REPLACE_EXISTING,
                                	    NULL))
                                	return ADD_CERT_TO_STORE_ERROR;
                                
                                    if (!CertCloseStore(cpcsp_cert_store, 0))
                                	return CLOSE_STORE_ERROR;
                                
                                    if (cert_context)
                                	CertFreeCertificateContext(cert_context);
                                
                                    return OPERATION_SUCCESS;
                                }
                                

                                В этой функции считывается файл сертификата (функция ReadCertificateFromFile), затем открываем системное хранилище методом CertOpenSystemStore. Если системное хранилище открылось успешно, то с помощью метода CertAddCertificateContextToStore, сертификат загрузается в хранилище. И в заключении хранилище закрывается функцией CertCloseStore.

                                Нужно отметить что функция CertOpenSystemStore ипользуется только для чтения системных хранилищ (root, ca, my), для остальных надо использовать CertOpenStore.

                                Загрузка файла отозванных сертификатов.

                                Функции чтения списка отозванных сертификатов(CRL) и загрузки их в хранилище идентичны функциям работы с сертификатами, за тем исключением, что для их чтения и загрузки используются функции CPCSP c CRL вместо Certificate в названии функции. Например CertAddCertificateContextToStore будет выглядеть как CertAddCRLContextToStore.

                                Таким образом код для загруки CRL будет таким:

                                int LoadCRLToSystemStore(char *cert_file_path, char *store_name)
                                {
                                    HCERTSTORE    cpcsp_cert_store = NULL;
                                    PCCRL_CONTEXT crl_context;
                                
                                    crl_context = ReadCRLFromFile(cert_file_path);
                                    if (!crl_context)
                                	return READ_CRL_ERROR;
                                  
                                    cpcsp_cert_store = CertOpenSystemStore(0, store_name);
                                
                                    if (!cpcsp_cert_store)
                                	return OPEN_STORE_ERROR;
                                
                                    if (!CertAddCRLContextToStore(
                                	    cpcsp_cert_store,
                                	    crl_context,
                                	    CERT_STORE_ADD_REPLACE_EXISTING,
                                	    NULL))
                                	return ADD_CRL_TO_STORE_ERROR;
                                
                                    if (!CertCloseStore(cpcsp_cert_store, 0))
                                	return CLOSE_STORE_ERROR;
                                
                                    if (crl_context)
                                	CertFreeCRLContext(crl_context);
                                
                                  return OPERATION_SUCCESS;
                                }
                                

                                Постороение цепочки сертификата.

                                Код функции проверки цепочки сертификатов выглядит следующим образом

                                int VerifyCertChain(char *certFilePath)
                                {
                                    PCCERT_CONTEXT           pCertContext;
                                    PCCERT_CHAIN_CONTEXT     pChainContext;
                                    CERT_ENHKEY_USAGE        EnhkeyUsage;
                                    CERT_USAGE_MATCH         CertUsage;
                                    CERT_CHAIN_PARA          ChainPara;
                                
                                    /*
                                      инициализация парметров поиска и сопоставления, которые 
                                      будут использоваться для построения цепочки сертификатов 
                                    */
                                    EnhkeyUsage.cUsageIdentifier = 0;
                                    EnhkeyUsage.rgpszUsageIdentifier = NULL;
                                    CertUsage.dwType = USAGE_MATCH_TYPE_AND;
                                    CertUsage.Usage  = EnhkeyUsage;
                                    ChainPara.cbSize = sizeof(CERT_CHAIN_PARA);
                                    ChainPara.RequestedUsage=CertUsage;
                                 
                                    pCertContext = ReadCertificateFromFile(certFilePath);
                                
                                    if (!CertGetCertificateChain(
                                	    NULL,
                                	    pCertContext,
                                	    NULL,
                                	    NULL,
                                	    &ChainPara,
                                	    0,
                                	    NULL,
                                	    &pChainContext))
                                    {
                                		perror("The chain could not be created");
                                    }
                                	
                                	int result = pChainContext->TrustStatus.dwErrorStatus;
                                
                                    if (pChainContext)
                                    {
                                		CertFreeCertificateChain(pChainContext);
                                    }
                                	
                                    return result;
                                }
                                

                                Помимо настроек цепочки, тут вызывается функция CertGetCertificateChain, которая формирует собственно цепочку сертификатов и записывает ее в структуру PCCERT_CHAIN_CONTEXT. В данной структуре поле TrustStatus отвечает за статус опреации, если цепочка построена корректно, то dwErrorStatus будет 0, иначе будет записан код ошибки.

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

                                Для начала я подумал сорфировать самоподписной сертификат для проверки функционирования функции, но оказалось, что CPCSP не поддерживает их, поэтому я создал сертификат в Тестовом УЦ КриптоПро. Я не буду описывать данный процесс, так как к библиотике он имеет посредственное отношение. Только скажу, что файл подписи я генерировал под Windows, потому как это было проще сделать через КриптоПро ЭЦП Browser plug-in.

                                Также надо отметить, что сертификат ЦС, надо загрузить в хранилище “Доверенные корневые…”. Иначе сгенерированный тестовый сертификат не установиться и плагин для ЭЦП не будет корректно работать. Код функции проверки подписи приведен ниже:

                                int VerifySignedMessage(char *signature)
                                {
                                    DWORD blob_size = 0;
                                    /* 
                                       определяем размер выходного der блоба
                                       для подписанного сообщения
                                    */
                                    if (!CryptStringToBinaryA(
                                	    signature,
                                	    strlen(signature),
                                	    CRYPT_STRING_BASE64,
                                	    NULL,
                                	    &blob_size,
                                	    NULL,
                                	    NULL))
                                	return STR_TO_BIN_LEN_ERROR;	
                                
                                
                                    /*
                                       заполняем блоб подписанного сообщения
                                     */
                                    BYTE *msg_blob;
                                    msg_blob = (BYTE *)malloc(blob_size);
                                    if (!CryptStringToBinaryA(
                                	    signature,
                                	    strlen(signature),
                                	    CRYPT_STRING_BASE64,
                                	    msg_blob,
                                	    &blob_size,
                                	    NULL,
                                	    NULL))
                                	return STR_TO_BIN_CONTENT_ERROR;	
                                
                                    /*
                                      выполняем проверку подписи
                                     */
                                    CRYPT_VERIFY_MESSAGE_PARA verify_params;
                                
                                    verify_params.cbSize = sizeof(CRYPT_VERIFY_MESSAGE_PARA);
                                    verify_params.dwMsgAndCertEncodingType = ENCODING_TYPE;
                                    verify_params.hCryptProv = 0;
                                    verify_params.pfnGetSignerCertificate = NULL;
                                    verify_params.pvGetArg = NULL;
                                    
                                    if(!CryptVerifyMessageSignature(
                                        &verify_params,
                                        0,
                                        msg_blob,
                                        blob_size,
                                        NULL,
                                        NULL,
                                        NULL))
                                	return VERIFY_MSG_SIGNATURE;
                                    
                                    return OPERATION_SUCCESS;
                                }
                                

                                Код снабжен коментариями, которые поясняют за что какой кусок кода отвечает.

                                Также надо отметить что функция CryptStringToBinaryA вызывается 2 раза, первый для получения размер подписи, а второй, чтобы получить данные раскодированные из base64 данные. Ну и затем подпись соответственно проверяется.

                                После того, как все функции написаны, то можно приступуть к реализации С обертки для python библиотеки и написанию тестов.
                                Для начала опишем заголовочный файл, который будет содержать описание вызываемых функций и исключений:

                                #ifdef __linux__
                                	#include <Python.h>
                                #elif __APPLE__
                                	#include <Python/Python.h>
                                #endif
                                
                                #ifndef LIBSIGNATURE_H_
                                #define LIBSIGNATURE_H_
                                
                                /* 
                                   Список экспортируемых функций
                                 */
                                PyObject * PyLoadCertificate(PyObject *self, PyObject *args);
                                PyObject * PyLoadCRL(PyObject *self, PyObject *args);
                                PyObject * PyVerifyCertChain(PyObject *self, PyObject *args);
                                PyObject * PyVerifySignedMessage(PyObject *self, PyObject *args);
                                
                                /*
                                  Типы исключений различных ситуаций
                                 */
                                extern PyObject *PyOpenStoreError;
                                extern PyObject *PyAddCertToStoreError;
                                extern PyObject *PyCloseStoreError;
                                extern PyObject *PyAddCrlToStoreError;
                                extern PyObject *PyStrToBinLenError;
                                extern PyObject *PyStrToBinContentError;
                                extern PyObject *PyVerifyMsgSignatureError;
                                extern PyObject *PyGetCertChainError;
                                extern PyObject *PyReadCertError;
                                extern PyObject *PyReadCrlError;
                                
                                #endif
                                

                                Как видно из этого файла, на каждый код ошибки С функций, будет соответствовать свое исключение. Реализацию самих функций можно посмотреть в файле py_cpcsp.c репозитория.

                                Код оберки для библиотеки выглядит следующим образом:

                                #include <stdio.h>
                                #include "libsignature.h"
                                
                                // Таблица методов реализуемых расширением
                                // название, функция, параметры, описание
                                static PyMethodDef LibsignatueMethods[] = {
                                    {"load_certificate",  PyLoadCertificate, METH_VARARGS, NULL},
                                    {"load_crl",  PyLoadCRL, METH_VARARGS, NULL},
                                    {"verify_chain_certificate",  PyVerifyCertChain, METH_VARARGS, NULL},
                                    {"vefigy_signature",  PyVerifySignedMessage, METH_VARARGS, NULL},
                                    {NULL, NULL,  0, NULL}
                                };
                                
                                PyObject *PyOpenStoreError;
                                PyObject *PyAddCertToStoreError;
                                PyObject *PyCloseStoreError;
                                PyObject *PyAddCrlToStoreError;
                                PyObject *PyStrToBinLenError;
                                PyObject *PyStrToBinContentError;
                                PyObject *PyVerifyMsgSignatureError;
                                PyObject *PyGetCertChainError;
                                PyObject *PyReadCertError;
                                PyObject *PyReadCrlError;
                                
                                // Инициализация
                                PyMODINIT_FUNC initlibsignature(void)
                                {
                                    PyObject *m;
                                
                                    // Инизиализруем модуль libsignature
                                    m = Py_InitModule("libsignature", LibsignatueMethods);
                                    if (m == NULL)
                                        return;
                                
                                    // Создание исключений при работе с расширением
                                    PyOpenStoreError = PyErr_NewException("libsignature.OpenStoreError",
                                	NULL,
                                	NULL
                                	);
                                    PyAddCertToStoreError = PyErr_NewException(
                                	"libsignature.AddCertToStoreError",
                                	NULL,
                                	NULL
                                	);
                                    PyCloseStoreError = PyErr_NewException(
                                	"libsignature.CloseStoreError",
                                	NULL,
                                	NULL
                                	);
                                    PyAddCrlToStoreError = PyErr_NewException(
                                	"libsignature.AddCRLToStoreError",
                                	NULL,
                                	NULL
                                	);
                                    PyStrToBinLenError = PyErr_NewException(
                                	"libsignature.StrToBinLenError",
                                	NULL,
                                	NULL
                                	);
                                    PyStrToBinContentError = PyErr_NewException(
                                	"libsignature.StrToBinContentError",
                                	NULL,
                                	NULL
                                	);
                                    PyVerifyMsgSignatureError = PyErr_NewException(
                                	"libsignature.VerifySignError",
                                	NULL,
                                	NULL
                                	);
                                    PyGetCertChainError = PyErr_NewException(
                                	"libsignature.ChainCertError",
                                	NULL,
                                	NULL
                                	);
                                    PyReadCertError = PyErr_NewException(
                                	"libsignature.ReadCertError",
                                	NULL,
                                	NULL
                                	);
                                    PyReadCrlError = PyErr_NewException(
                                	"libsignature.ReadCRLError",
                                	NULL,
                                	NULL
                                	);
                                
                                    Py_INCREF(PyOpenStoreError);
                                    Py_INCREF(PyAddCertToStoreError);
                                    Py_INCREF(PyCloseStoreError);
                                    Py_INCREF(PyAddCrlToStoreError);
                                    Py_INCREF(PyStrToBinLenError);
                                    Py_INCREF(PyStrToBinContentError);
                                    Py_INCREF(PyVerifyMsgSignatureError);
                                    Py_INCREF(PyGetCertChainError);
                                    Py_INCREF(PyReadCertError);
                                    Py_INCREF(PyReadCrlError);
                                
                                    PyModule_AddObject(m, "error", PyOpenStoreError);
                                    PyModule_AddObject(m, "error", PyAddCertToStoreError);
                                    PyModule_AddObject(m, "error", PyCloseStoreError);
                                    PyModule_AddObject(m, "error", PyAddCrlToStoreError);
                                    PyModule_AddObject(m, "error", PyStrToBinLenError);
                                    PyModule_AddObject(m, "error", PyStrToBinContentError);
                                    PyModule_AddObject(m, "error", PyVerifyMsgSignatureError);
                                    PyModule_AddObject(m, "error", PyGetCertChainError);
                                    PyModule_AddObject(m, "error", PyReadCertError);
                                    PyModule_AddObject(m, "error", PyReadCrlError);
                                }
                                

                                Что делается в этом файле подробно описано здесь.

                                Для проверки работоспособности питоновской библиотеки, напишем следующий тест:

                                import libsignature
                                
                                
                                class TestLibSignature(unittest.TestCase):
                                    """
                                    Класс для тестирования работы с КриптоПро CSP.
                                    """
                                    def setUp(self):
                                        """
                                        Задание путей до тестовых файлов
                                        """
                                        self._cert_file = os.path.join(current_path, "files/ca.cer")
                                        self._crl_file = os.path.join(current_path, "files/ca.crl")
                                        self._user_cert = os.path.join(current_path, "files/user.cer")
                                        self._sig_file = os.path.join(current_path, "files/test_sign.sig") 
                                        self._store = "ROOT"
                                
                                    def test_load_certificate(self):
                                        """
                                        Проверка загрузки сертификата.
                                        """
                                        result = libsignature.load_certificate(self._cert_file, self._store)
                                        self.assertIsNone(result)
                                
                                    def test_load_crl(self):
                                        """
                                        Проверка загрузки списка отозванных серитификатов.
                                        """
                                        result = libsignature.load_crl(self._crl_file, self._store)
                                        self.assertIsNone(result)
                                
                                    def test_verify_cert_chain(self):
                                        """
                                        Проверка корректности цепочки сертификатов
                                        """
                                        result = libsignature.verify_chain_certificate(self._user_cert)
                                        self.assertIsNone(result)
                                
                                    def verify_signature(self):
                                        """
                                        Проверка подписи сообщения
                                        """
                                        with open(self._sig_file, "rb") as sigfile:
                                            signature = sigfile.read()
                                            result = libsignature.vefigy_signature(signature)
                                            self.assertIsNone(result)
                                
                                
                                if __name__ == '__main__':
                                    unittest.main()
                                

                                Теперь все готово, и можно запусть команду make test для проверки работоспособности.

                                Теоритическая часть.

                                Для того чтобы проверить подпись какого-то конкретного сообщения на необходимо само сообщение с подписью, а также цепочка сертификатов, которые помогут проверить данную подпись. Подробнее об этом можно узнать в здесь.

                                Все ключи крипто про хранит в своих хранилищах, таких как root, ca, my. Чтобы в них загрузить сертификат в поставке Криптопро CSP идет специальная утилита certmgr. Синтаксих ее работы таков:

                                certmgr -inst -store <имя хранилища> -file <файл с сертификатом>
                                

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

                                certmgr -instl -crl -store <имя хранилища> -file <CRL файл>
                                

                                Побробную информацию по работе этой утилиты можно получить вызвав:

                                certmgr -help
                                

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

                                cryptcp -verify -f <файл сертификата> "text" -errchain
                                

                                Соответственно закрытым ключок соответсвующем проверяемому сертификату и можем подписать сообщение, а с помощью цепочки сертификатов из хранилища, мы сможем ее проверить.

                                Из всего вышесказанного я подумал, что в библиотеке мне нужны будут следующие функции:

                                • Загрузка сертификата в хранилище;
                                • Загрузка файла отозванных сертификатов;
                                • Постороение цепочки сертификата;
                                • Проверка подписи.

                                Заключение.

                                Процесс создания библиотеки получился трудоемкий, но на выходе получилась рабочая библиотека, которой можно пользоваться. В репозиотории можете найти пакет для работы из python, но также можно использовать только C-ную часть. Для работы С библиотеки нужно выполнить make build_c.

                                Читайте также:  СБИС - сеть деловых коммуникаций: сообщения, задачи, документы, отчеты
                                Оцените статью
                                ЭЦП Эксперт
                                Добавить комментарий