Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

loading and saving the private key to PEM file using CAPI engine

549 views
Skip to first unread message

LN

unread,
Dec 3, 2012, 4:32:24 AM12/3/12
to
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();
}

0 new messages