在 gSoap 中将私钥与 Windows 证书存储区一起使用

Use private key with Windows certficate store in gSoap

我正在用使用 Windows 证书的 OpenSSL 编写一个 gSoap 客户端。我有一个 PEM 证书和一个 PEM 私钥。当我将它们组合成一个文件并将其提供给 gSoap 时,它工作正常:

soap_ssl_client_context( &soap,
                         SOAP_SSL_DEFAULT,
                         "certkey.pem", /* required only when client must authenticate to server         */
                         NULL, /* password to read the key file (not used with GNUTLS)                 */
                         NULL, /* cacert file to store trusted certificates                            */
                         NULL, /* capath to directory with trusted certificates                        */
                         NULL  /* if randfile!=NULL: use a file with random data to seed randomness    */
                                 )

但是当我将证书安装到 Windows 存储并通过 X509_STORE_add_cert 从那里加载它时,它不起作用。我的猜测是我必须以某种方式使用私钥,但我不知道以何种方式。我该怎么办?

你说得对,你需要加载私钥,X509_STORE_add_cert也是不正确的。如果要为服务器或客户端使用证书,则需要使用 SSL_CTX_use_xxx 或 SSL_use_xxx 将证书设置为 ssl 上下文,并为证书和证书的私钥设置证书。

例如

SSL_CTX_use_certificate_chain_file(ctx, "cert.pem");
SSL_CTX_use_PrivateKey_file(ctx, "cert.pem", SSL_FILETYPE_PEM);

以上假定 "cert.pem" 同时持有证书链和私钥。

更新:

我假设 "Windows Storage" 你指的是 "Windows Certificate Store"。 在 windows 证书库中使用证书的主要问题是私钥的使用。如果私钥被标记为 "non-exportable" 那么您只能 "use" 使用 Windows Crypto API. So if you wish to use a certificate with the private key stored in the windows certificate store you need to "export" the certificate (easy enough) and the private key into openssl x509 and rsa objects to be used in the SSL_CTX_xxx functions. The best I have found to export a private key is using the NCryptExportKey using the BCRYPT_RSAFULLPRIVATE_BLOB blob type and then breaking up the BCRYPT_RSAKEY_BLOB manually into a openssl RSA structure using the RSA_setxxx 函数的私钥。

RSA* 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;
    }

    RSA* rsa = 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)
            {
                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);
            }
        }
    }

    if(free_key)
    {
        NCryptFreeObject(key_handle);
    }

    return rsa;
}

bool set_ctx_certificate_and_private_key(SSL_CTX* ctx, const PCCERT_CONTEXT context)
{
    auto const x509 = d2i_X509(nullptr, const_cast<const unsigned char **>(&context->pbCertEncoded), context->cbCertEncoded);
    if (!x509)
    {
        return false;
    }

    if(!SSL_CTX_use_certificate(ctx, x509))
    {
        X509_free(x509);
        return false;
    }
    X509_free(x509);

    auto const rsa = extract_private_key(context);
    if (!rsa)
    {
        return false;
    }

    auto const success = SSL_CTX_use_RSAPrivateKey(ctx, rsa) == 1;
    RSA_free(rsa);
    return success;
}