(2019-11-25 15:59:23 отредактировано Binger)

Вопросы по работе с OpenSSL C++

Здравствуйте. Пишу программу на C++ с использованием инструмента OpenSSL и другими вашими библиотеками. Возникли вопросы:
1. Как можно получить список получателей из зашифрованного сообщения, созданного примером из sdk\openssl\rtengine\samples\EncryptCMS? 
2. Как шифровать сообщение, используя сертификат на токене, а не читая его из локальной папки на пк? Например, по серийному номеру сертификата?
3. И ещё как можно вынуть данные из подписанного сообщения, допустим, время подписи?
Интересует именно как это можно сделать в среде программирования, а не консольное использование OpenSSL. И где вообще можно найти описание всех функций OpenSSL?
Безмерно буду вам благодарен за любую помощь.

Re: Вопросы по работе с OpenSSL C++

Binger, здравствуйте!

1. Пример парсинга CMS: https://github.com/openssl/openssl/blob … /cms_dec.c

2.  а) Сначала нужно, прочитать тело сертификата с помощью PKCS#11. Он будет всегда в кодировке DER.
См. примеры SDK. Часть как получить тела всех сертификатов на токене.

        rv = functionList->C_OpenSession(slots[i], CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &session);
        CHECK("    C_OpenSession", rv == CKR_OK, finalize_pkcs11);

        rv = functionList->C_FindObjectsInit(session, &findObjectTemplate, 1);
        CHECK("    C_FindObjectsInit", rv == CKR_OK, close_session);

        certHandles.clear();
        certHandles.resize(10000);
        rv = functionList->C_FindObjects(session, certHandles.data(), (CK_ULONG)certHandles.size(), &certCount);
        CHECK("    C_FindObjects", rv == CKR_OK, close_session);

        for (size_t i = 0; i < certCount; ++i) {
            CK_ATTRIBUTE certValueAttr = { CKA_VALUE, NULL_PTR, 0 };
            std::vector<CK_BYTE> certValue;

            rv = functionList->C_GetAttributeValue(session, certHandles[i], &certValueAttr, 1);
            CHECK("    C_GetAttributeValue", rv == CKR_OK, close_session);

            certValue.resize(certValueAttr.ulValueLen);
            certValueAttr.pValue = certValue.data();

            rv = functionList->C_GetAttributeValue(session, certHandles[i], &certValueAttr, 1);
            CHECK("    C_GetAttributeValue", rv == CKR_OK, close_session);

            pemCerts.emplace_back(certToPem(certValue));
        }

        rv = functionList->C_FindObjectsFinal(session);
        CHECK("    C_FindObjectsFinal", rv == CKR_OK, close_session);

     б) Сконвертировать его в PEM, а затем в X509*

std::string certToPem(const std::vector<uint8_t>& certBuf) {
    if (certBuf.at(0) == 0x30) {
        bioPtr cert_bio(BIO_new_mem_buf(certBuf.data(), (int)certBuf.size()), BIO_free_all);
        if (!cert_bio) return {};

        x509Ptr cert(d2i_X509_bio(cert_bio.get(), nullptr), X509_free);
        if (!cert) return {};

        bioPtr outBio(BIO_new(BIO_s_mem()), BIO_free_all);
        if (!outBio) return {};

        int r = PEM_write_bio_X509(outBio.get(), cert.get());
        if (!r) return {};

        BUF_MEM* mem;
        r = BIO_get_mem_ptr(outBio.get(), &mem);
        if (r < 1) return {};

        return std::string(mem->data, mem->length);
    }

    return std::string(certBuf.begin(), certBuf.end());
}

X509* certToX509(const std::vector<uint8_t>& certBuf) {
    auto pem = certToPem(certBuf);
    if (pem.empty()) return nullptr;

    bioPtr bio(BIO_new_mem_buf(pem.data(), (int)pem.size()), BIO_free_all);
    if (!bio) return nullptr;

    return PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr);
}

3. Распарсить CMS. Cм. п.1

(2019-11-25 17:56:01 отредактировано Binger)

Re: Вопросы по работе с OpenSSL C++

Павел Анфимов, предложенный вами пример парсинга CMS: https://github.com/openssl/openssl/blob … /cms_dec.c описывает работу с S/MIME сообщениями. Меня интересуют PKCS7 сообщения. Можете помочь?

Re: Вопросы по работе с OpenSSL C++

Вы пробовали запускать? Файл smencr.txt создается с помощью CMS_Encrypt  из соседнего примера: https://github.com/openssl/openssl/blob … /cms_enc.c

(2019-11-25 17:39:54 отредактировано Binger)

Re: Вопросы по работе с OpenSSL C++

Павел Анфимов, Пробовал, но читал шифрованный файл, сформированный из примера sdk\openssl\rtengine\samples\EncryptCMS - и там не получалось распарсить, ошибка вылетала. Сейчас попытался запустить шифрование из примера по вашей ссылке и у меня вылезает ошибка. Прикрепил скрины.
https://forum.rutoken.ru/uploads/images/2019/11/9f3dbbb2d77063ccd3135959acaf6705.png
https://forum.rutoken.ru/uploads/images/2019/11/d418f72f6216b66484a12f0d2ef126e1.png

Re: Вопросы по работе с OpenSSL C++

Павел Анфимов, ещё при запуске примера дешифрования https://github.com/openssl/openssl/blob … /cms_dec.c у меня появляется ошибка на моменте чтения закрытого ключа. Прикрепил скрины тоже. Я думаю, это связано с тем, что тестовые ключи на токене неэкспортируемые, и получается сертификат без ключевой пары экспортируется.
https://forum.rutoken.ru/uploads/images/2019/11/80c4b7e419af274c57e07ccc1129f669.png
https://forum.rutoken.ru/uploads/images/2019/11/8331c12c58348619a33e19845c7a4c54.png

Re: Вопросы по работе с OpenSSL C++

Binger

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

Закомментируйте лишние вызовы, проверьте что выводит SMIME_read_CMS из примера https://github.com/openssl/openssl/blob … /cms_dec.c

Re: Вопросы по работе с OpenSSL C++

Павел Анфимов, Null

Re: Вопросы по работе с OpenSSL C++

Binger, посмотрите на https://stackoverflow.com/questions/580 … -cms-via-c

(2019-11-26 17:36:41 отредактировано Binger)

Re: Вопросы по работе с OpenSSL C++

Павел Анфимов, в 2.б x509Ptr и bioPtr - откуда эти переменные? Где объявлены?

Ещё не очень понял, что у вас в 2.а код делает? Вы направляете на пример из SDK или же можно использовать код 2.а для получения тел всех сертификатов на токене?

Из 2.а что я должен подавать на вход в функции в 2.б?   certHandles?

Павел Анфимов пишет:

Binger, здравствуйте!

1. Пример парсинга CMS: https://github.com/openssl/openssl/blob … /cms_dec.c

2.  а) Сначала нужно, прочитать тело сертификата с помощью PKCS#11. Он будет всегда в кодировке DER.
См. примеры SDK. Часть как получить тела всех сертификатов на токене.

        rv = functionList->C_OpenSession(slots[i], CKF_SERIAL_SESSION | CKF_RW_SESSION, NULL, NULL, &session);
        CHECK("    C_OpenSession", rv == CKR_OK, finalize_pkcs11);

        rv = functionList->C_FindObjectsInit(session, &findObjectTemplate, 1);
        CHECK("    C_FindObjectsInit", rv == CKR_OK, close_session);

        certHandles.clear();
        certHandles.resize(10000);
        rv = functionList->C_FindObjects(session, certHandles.data(), (CK_ULONG)certHandles.size(), &certCount);
        CHECK("    C_FindObjects", rv == CKR_OK, close_session);

        for (size_t i = 0; i < certCount; ++i) {
            CK_ATTRIBUTE certValueAttr = { CKA_VALUE, NULL_PTR, 0 };
            std::vector<CK_BYTE> certValue;

            rv = functionList->C_GetAttributeValue(session, certHandles[i], &certValueAttr, 1);
            CHECK("    C_GetAttributeValue", rv == CKR_OK, close_session);

            certValue.resize(certValueAttr.ulValueLen);
            certValueAttr.pValue = certValue.data();

            rv = functionList->C_GetAttributeValue(session, certHandles[i], &certValueAttr, 1);
            CHECK("    C_GetAttributeValue", rv == CKR_OK, close_session);

            pemCerts.emplace_back(certToPem(certValue));
        }

        rv = functionList->C_FindObjectsFinal(session);
        CHECK("    C_FindObjectsFinal", rv == CKR_OK, close_session);

     б) Сконвертировать его в PEM, а затем в X509*

std::string certToPem(const std::vector<uint8_t>& certBuf) {
    if (certBuf.at(0) == 0x30) {
        bioPtr cert_bio(BIO_new_mem_buf(certBuf.data(), (int)certBuf.size()), BIO_free_all);
        if (!cert_bio) return {};

        x509Ptr cert(d2i_X509_bio(cert_bio.get(), nullptr), X509_free);
        if (!cert) return {};

        bioPtr outBio(BIO_new(BIO_s_mem()), BIO_free_all);
        if (!outBio) return {};

        int r = PEM_write_bio_X509(outBio.get(), cert.get());
        if (!r) return {};

        BUF_MEM* mem;
        r = BIO_get_mem_ptr(outBio.get(), &mem);
        if (r < 1) return {};

        return std::string(mem->data, mem->length);
    }

    return std::string(certBuf.begin(), certBuf.end());
}

X509* certToX509(const std::vector<uint8_t>& certBuf) {
    auto pem = certToPem(certBuf);
    if (pem.empty()) return nullptr;

    bioPtr bio(BIO_new_mem_buf(pem.data(), (int)pem.size()), BIO_free_all);
    if (!bio) return nullptr;

    return PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr);
}

3. Распарсить CMS. Cм. п.1

Re: Вопросы по работе с OpenSSL C++

#include <memory>
#include <openssl/pem.h>
#include <openssl/cms.h>

using x509Ptr = std::unique_ptr<X509, void(*)(X509*)>;
using bioPtr = std::unique_ptr<BIO, void(*)(BIO*)>;

Код из 2.а) получает тела всех сертификатов с токена. Естественно перед вставкой этого кода надо загрузить библиотеку pkcs#11(C_Initialize) и получить все слоты (C_GetSlotList). Для этого смотрите, например, пример из SDK: <sdk>\pkcs11\samples\PKIExtensions\GetCertificateInfo-GOST34.10-2012-256.

Функций из 2.б) вспомогательная используется в 2.а)

std::vector<std::string> pemCerts;
....
pemCerts.emplace_back(certToPem(certValue));

Затем в примере <sdk>\openssl\rtengine\samples\EncryptCMS
Можно сторку

/*************************************************************************
* Чтение сертификата из файла                                            *
*************************************************************************/
cert = PEM_read_bio_X509(inBio, NULL, NULL, NULL);
CHECK("  PEM_read_bio_X509", cert != NULL, free_in_bio);

заменить на:

/*************************************************************************
* Чтение сертификата с токена                                            *
*************************************************************************/
X509* certToX509(const std::string& cert) {
    if (cert.empty()) return nullptr;

    bioPtr bio(BIO_new_mem_buf(cert.data(), (int)cert.size()), BIO_free_all);
    if (!bio) return nullptr;

    return PEM_read_bio_X509(bio.get(), nullptr, nullptr, nullptr);
}

cert = certToX509(pemCerts[0]);
CHECK("  certToX509", cert != NULL, free_in_bio);

(2019-11-28 12:51:53 отредактировано Binger)

Re: Вопросы по работе с OpenSSL C++

Павел Анфимов, подскажите, пожалуйста, что такое в вашем случаем переменная certHandles (см. 2.а)? Как её объявить?

std::vector<CK_OBJECT_HANDLE>certHandles

?

Re: Вопросы по работе с OpenSSL C++

Binger, верно std::vector<CK_OBJECT_HANDLE> certHandles;

(2019-11-29 11:17:41 отредактировано Binger)

Re: Вопросы по работе с OpenSSL C++

Павел Анфимов, функция CMS_encrypt на вход требует BIO *in в качестве данных, которые будут шифроваться. Павел, каким образом я могу зашифровать данные прямо из массива байт unsigned char*? Т.е. не читая их из локального файла, а работая уже с массивом байт.

(2019-11-29 11:19:13 отредактировано Binger)

Re: Вопросы по работе с OpenSSL C++

Павел Анфимов, подскажите ещё, пожалуйста, возможно ли работать одновременно с OpenSSL и библиотекой PKCS11, подключая одновременно хэдеры и либы этих двух инструментов в проект? Не будет никаких конфликтов имён или тому подобных? У меня возникли конфликты какие-то, и я бы хотел этот вопрос уточнить:
https://forum.rutoken.ru/uploads/images/2019/11/6d6e4526c73e89c439e7823ab5db8a6c.png
Можно ли как-то решить данную проблему?