OpenSSL без общего шифра

Как я могу использовать сертификат, считанный из хранилища сертификатов Windows в OpenSSL?

Я настроил проект Windows на основе https://www.boost.org/doc/libs/1_72_0/libs/beast/example/http/server/async-ssl/http_server_async_ssl.cpp — фактически добавил сервер https в мой проект. Когда я использую жестко заданный сертификат в этом примере, все работает, но когда я экспортирую этот сертификат и загружаю его в свой код, я получаю «no shared cipher».

Я создал минимальный пример. В main.cpp:79-83 я использую load_server_certificate, который загружает сертификат на основе отпечатка - это то, что терпит неудачу с "no shared cipher". Если я вместо этого прокомментирую это и использую load_static_server_certificate (исходный пример жестко закодированных ключей) это работает (я могу публиковать сообщения, получать ответы и т. д., и т. д.).

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

ex/certificate_helpers.cpp:

509 = d2i_X509(nullptr, const_cast<const BYTE**>(&pCertContext->pbCertEncoded), pCertContext->cbCertEncoded);
bio = BIO_new(BIO_s_mem());
PEM_write_bio_X509(bio, x509);
ctx.add_certificate_authority(boost::asio::buffer(certificates.data(), certificates.size()), ec);

Мой контекст настроен до запуска моего потока (это, казалось, был общий вопрос), мой сертификат, похоже, загружается правильно. В хранилище сертификатов мой сертификат имеет закрытый ключ.

Мой следующий шаг - выяснить, могу ли я извлечь этот закрытый ключ и потенциально загрузить его с помощью add_private_key, я также вижу, что в примере используется параметр Диффи-Хеллмана, если это также требуется, я понятия не имею, как его использовать - сделайте Мне это надо?


person Matt    schedule 12.02.2020    source источник


Ответы (1)


Для использования сертификата вы можете использовать use_certificate, как и для интерфейса add_certificate_authority.

Для закрытого ключа становится немного сложнее. См. мой ответ здесь в качестве примера чтения закрытого ключа RSA в EVP_PKEY (ECC потребует другого кода). Затем вы можете использовать функцию PEM_write_bio_PrivateKey для создания pem-блоба. и используйте use_private_key интерфейс для его использования.

Вы также можете пропустить разговор в формате PEM, если вы вызываете функции openssl напрямую, используя ssl_context native_handle.

e.g.

X509 *cert = readCert();
SSL_CTX_use_certificate(ctx.native_handle(), cert); // instead of the use_certificate call

EVP_PKEY *key = readKey();
SSL_use_PrivateKey(ctx.native_handle(), key); // instead of the use_private_key call

X509 *cert = readChainCert();
SSL_CTX_add_extra_chain_cert(ctx.native_handle(), cert); // use chain cert

X509 *cert = readCaCert();
X509_STORE *store = SSL_CTX_get_cert_store(ctx.native_handle()); // instead of the add_certificate_authority call
X509_STORE_add_cert(store, cert);

Обновление: добавьте пример использования PCCERT_CONTEXT (т. е. использует CryptAcquireCertificatePrivateKey API).

EVP_PKEY* extract_private_key(const PCCERT_CONTEXT context)
{
    HCRYPTPROV_OR_NCRYPT_KEY_HANDLE key_handle;
    DWORD key_spec = 0;
    BOOL free_key;
    if (!CryptAcquireCertificatePrivateKey(context, CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG | CRYPT_ACQUIRE_SILENT_FLAG, nullptr, &key_handle, &key_spec, &free_key))
    {
        return nullptr;
    }

    EVP_PKEY* pkey = nullptr;
    DWORD length = 0;
    if(SUCCEEDED(NCryptExportKey(key_handle, NULL, BCRYPT_RSAFULLPRIVATE_BLOB, nullptr, nullptr, 0, &length, 0)))
    {
        auto data = std::make_unique<BYTE[]>(length);

        if(SUCCEEDED(NCryptExportKey(key_handle, NULL, BCRYPT_RSAFULLPRIVATE_BLOB, nullptr, data.get(), length, &length, 0)))
        {
            // https://docs.microsoft.com/en-us/windows/desktop/api/bcrypt/ns-bcrypt-_bcrypt_rsakey_blob
            auto const blob = reinterpret_cast<BCRYPT_RSAKEY_BLOB*>(data.get());

            if(blob->Magic == BCRYPT_RSAFULLPRIVATE_MAGIC)
            {
                auto rsa = RSA_new();

                // n is the modulus common to both public and private key
                auto const n = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp, blob->cbModulus, nullptr);
                // e is the public exponent
                auto const e = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB), blob->cbPublicExp, nullptr);
                // d is the private exponent
                auto const d = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 + blob->cbPrime2 + blob->cbPrime1 + blob->cbPrime2 + blob->cbPrime1, blob->cbModulus, nullptr);

                RSA_set0_key(rsa, n, e, d);

                // p and q are the first and second factor of n
                auto const p = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus, blob->cbPrime1, nullptr); 
                auto const q = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1, blob->cbPrime2, nullptr); 

                RSA_set0_factors(rsa, p, q);

                // dmp1, dmq1 and iqmp are the exponents and coefficient for CRT calculations
                auto const dmp1 = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 + blob->cbPrime2, blob->cbPrime1, nullptr); 
                auto const dmq1 = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 + blob->cbPrime2 + blob->cbPrime1, blob->cbPrime2, nullptr); 
                auto const iqmp = BN_bin2bn(data.get() + sizeof(BCRYPT_RSAKEY_BLOB) + blob->cbPublicExp + blob->cbModulus + blob->cbPrime1 + blob->cbPrime2 + blob->cbPrime1 + blob->cbPrime2, blob->cbPrime1, nullptr); 

                RSA_set0_crt_params(rsa, dmp1, dmq1, iqmp);

                pkey = EVP_PKEY_new();

                // ownership of rsa transferred to pkey
                EVP_PKEY_assign_RSA(pkey, rsa);
            }
        }
    }

    if(free_key)
    {
        NCryptFreeObject(key_handle);
    }

    return pkey;
}
person Shane Powell    schedule 12.02.2020
comment
эй, спасибо!. Просто чтобы быть уверенным, readCert, readKey, readhChainCert и readCaCert — это всего лишь примеры функций, верно? Чтобы получить ключ, я бы использовал CryptAcquireCertificatePrivateKey, CertGetCertificateChain, и т.д.? Требуется ли каждая часть этого? Или есть вещи, которые я могу пропустить? Все это кажется более сложным, чем я изначально думал, когда услышал, что добавить https. :) - person Matt; 12.02.2020
comment
Примеры «cert» — это всего лишь результат вызова d2i_X509. Ключ будет результатом вызова моего ответа на чтение закрытого ключа rsa. Он просто пропускает часть преобразования cer/key в формат pem только для того, чтобы использовать их в asio (вызовы pem_write_bio_xxx). - person Shane Powell; 12.02.2020
comment
ответ, кажется, уже начинается с дескриптора ключа (я думаю, из CryptAcquireCertificatePrivateKey?) Не мог бы я просто использовать это и прочитать его в SSL_use_PrivateKey? Я не уверен, как использовать код в этом ответе (извините, я действительно новичок в сертификатах). И все (cert/pkey/chain cert/authority) требуется? Спасибо еще раз. - person Matt; 12.02.2020
comment
Да, вы можете использовать CryptAcquireCertiicatePrivateKey. Проблема в том, что нет общего формата закрытых ключей между windows и openssl, который можно извлечь из win32 API. Вы должны разбить закрытый ключ на его биты, а затем восстановить его на другой стороне. Это то, что делает мой пример ответа - person Shane Powell; 12.02.2020
comment
Спасибо @ShanePowell, это именно то, что нужно моей команде. Не возражаете ли вы, если мы повторно используем ваше решение в проекте ОС (apache-minifi, под лицензией Apache-2.0 License)? - person Adam Hunyadi; 01.12.2020
comment
@AdamHunyadi, у меня нет проблем с тем, что ты это делаешь. - person Shane Powell; 01.12.2020