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();
}

Reply via email to