Hi, I'm using the OpenSSL CAPI engine and ENGINE_load_private_key to load the private key for a certificate in the windows certificate store. After loading the private key, I try to save it to a PEM file with PEM_write_bio_PrivateKey (or PEM_write_bio_RSAPrivateKey). The problem is that when I want to load the private key back from the PEM file with PEM_read_bio_PrivateKey (or PEM_read_bio_RSAPrivateKey) it won't work. Both functions return an error related to decoding and the private key is not loaded.
For the same purpose I used PEM_write_bio_PUBKEY and PEM_read_bio_PUBKEY and to my surprise it worked. So, I suspect that the provided function ENGINE_load_private_key actually loads the public key (somehow confirmed by the code inside the OpenSSL for this function). Is this true ? Please help with an advice or guidance on how to achieve saving the private key and reading it back from a PEM file. Thank you! #include "stdafx.h" #include <iostream> #include <openssl/ssl.h> #include <openssl/engine.h> #include <openssl/pem.h> //Note: using openssl-1.0.1c // libs: crypt32.lib libeay32.lib ssleay32.lib ws2_32.lib // Note: Comment this to have the key saved with PEM_write_bio_PrivateKey //#define SAVE_AS_RSA void saveToPEMFile(EVP_PKEY* pkey, X509* cert) { std::cout << "TEST Save CERT and PKEY to PEM file" << std::endl; if (!pkey || !cert) return; BIO* pem = BIO_new_file("./test.pem", "wt"); std::cout << "Saving private key type " << pkey->type << "..." << std::endl; const EVP_CIPHER* enc=EVP_des_ede3_cbc(); #ifdef SAVE_AS_RSA RSA* rsa = EVP_PKEY_get1_RSA(pkey); if(!PEM_write_bio_RSAPrivateKey(pem, rsa, enc, NULL, 0, NULL, NULL)) #else if(!PEM_write_bio_PrivateKey(pem, pkey, enc, NULL, 0, NULL, NULL)) #endif std::cout << "couldn't save the private key" << std::endl; // Write the certificate if (!PEM_write_bio_X509(pem, cert)) std::cout << "couldn't save the certificate" << std::endl; BIO_free(pem); std::cout << "TEST Save CERT and PKEY to PEM file done..." << std::endl; } void readPKEY() { std::cout << "TEST Reading private key" << std::endl; BIO* pem = BIO_new_file("./test.pem", "rt"); #ifdef SAVE_AS_RSA // Note: // This fails in OpenSSL, in d2i_PrivateKey when // calling // ameth->old_priv_decode(ret, pp, length) // with // RSAerr(RSA_F_OLD_RSA_PRIV_DECODE, ERR_R_RSA_LIB); RSA* ret = PEM_read_bio_RSAPrivateKey(pem, NULL, NULL, NULL); if (ret) std::cout << "Read RSA private key OK" << std::endl; else #else //Note: This fails in EVP_PKCS82PKEY when calling // pkey->ameth->priv_decode(pkey, p8) // Error: // EVPerr(EVP_F_EVP_PKCS82PKEY, // EVP_R_PRIVATE_KEY_DECODE_ERROR); EVP_PKEY* ret = PEM_read_bio_PrivateKey(pem, NULL, NULL, NULL); if (ret) std::cout << "Private key type " << ret->type << " read OK" << std::endl; else #endif std::cout << "couldn't read the private key" << std::endl; BIO_free(pem); #ifdef SAVE_AS_RSA RSA_free(ret); #else EVP_PKEY_free(ret); #endif std::cout << "TEST Reading private key done..." << std::endl; } void saveAndReadAsPUBKEY(EVP_PKEY* pkeyToSave) { std::cout << "TEST Saving and reading as PUB key" << std::endl; if (!pkeyToSave) return; // Open file for write BIO* pem = BIO_new_file("./testPUB.pem", "wt"); if(!PEM_write_bio_PUBKEY(pem, pkeyToSave)) std::cout << "couldn't save as PUB key" << std::endl; else std::cout << "Saved as PUB key OK" << std::endl; BIO_free(pem); // Open file for read pem = BIO_new_file("./testPUB.pem", "rt"); EVP_PKEY* pubKey = PEM_read_bio_PUBKEY(pem, NULL, NULL, NULL); if(!pubKey) std::cout << "couldn't read as PUB key" << std::endl; else std::cout << "PUB key read OK" << std::endl; EVP_PKEY_free(pubKey); BIO_free(pem); std::cout << "TEST Saving and reading as PUB key done..." << std::endl; } // Note: I have generated a certificate with openssl // 1) The RSA key 1024 bits // openssl genrsa -des3 -out test.key 1024 // // 2) the request + sign certificate // openssl req -new -key test.key -out cert.csr // openssl x509 -req -days 365 -in cert.csr -signkey test.key -out cert.crt // // 3) Created the certificate // type cert.crt cert.csr test.key > cert.pem // // Imported the certificate to windows store: // 1) Converted the certificate to PFX with openSSL // openssl pkcs12 -export -in cert.pem -out cert.pfx // // 2) Imported the cert.pfx into windows certificate store (from the Certificates MMC snapin) const char cert[] = "PUT THE CERTNAME HERE"; int _tmain(int argc, _TCHAR* argv[]) { // Using the CAPI engine for OpenSSL ENGINE_load_capi(); ENGINE* e = ENGINE_by_id("capi"); if(SSL_library_init()) { SSL_load_error_strings(); OpenSSL_add_all_algorithms(); } else return 1; if(ENGINE_init(e)) { EVP_PKEY* pStoreKey = NULL;// is this really the private key ? X509* pStoreCert = NULL; // will this contain the certificate chain also ?? //Note: // To load the certificate I had to add my own implementation // for capi_load_server_certificate callback because it is not // done, not even in openssl-1.0.1c pStoreCert= ENGINE_load_server_certificate(e, cert, NULL, NULL); // Load the private key with the OpenSSL CAPI engine pStoreKey = ENGINE_load_private_key(e, cert, NULL, NULL); saveToPEMFile(pStoreKey, pStoreCert); // Note: // 1) looking into ENGINE_load_private_key I have noticed that the // key is exported as a PUBLICKEYBLOB and that only the exponent // and the modulus are set into the returned RSA structure - maybe // this is why is too small in comparision with RSA private key genereted // by OpenSSL // 2) Passing the EVP_PKEY* directly as memory buffer to boost::asio::ssl // seems to work perfectly (the SSL communnication between client-server // works) - probably because the RSA keys can be used interchangeably readPKEY(); saveAndReadAsPUBKEY(pStoreKey); EVP_PKEY_free(pStoreKey); X509_free(pStoreCert); } ENGINE_cleanup(); ENGINE_finish(e); ENGINE_free(e); CRYPTO_cleanup_all_ex_data(); ERR_free_strings(); ERR_remove_thread_state(0); EVP_cleanup(); }