I'm writing a gSoap client with OpenSSL that uses Windows certificates. I have a PEM certificate and a PEM private key. When I combine them into a single file and give it to gSoap it works fine:
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 */
)
But when I install the certificate into the Windows storage and load it from there via X509_STORE_add_cert it does not work. My guess is that I have to use the private key somehow but I don't know in what way. What should I do?
You are correct that you need to load the private key and also X509_STORE_add_cert is the incorrect. If you want to use a certificate for a server or client you need to set the certificate into a ssl context using SSL_CTX_use_xxx or SSL_use_xxx for the certificate and for the private key of the certificate.
e.g.
SSL_CTX_use_certificate_chain_file(ctx, "cert.pem");
SSL_CTX_use_PrivateKey_file(ctx, "cert.pem", SSL_FILETYPE_PEM);
The above assumes the "cert.pem" holds both the certificate chain and the private key.
UPDATE:
I am assuming that by "Windows Storage" you mean the "Windows Certificate Store".
The main problem with using a certificate in the windows certificate store is the use of the private key. If the private key is marked as "non-exportable" then you can only "use" the private key using the 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 functions.
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;
}