/* $Id: kt_rsa.c,v 1.2 2005/01/30 18:36:02 rumen Exp $ */
#include <openssl/engine.h>
#include <openssl/pem.h>
#include "mscrypto_err.h"
#include "mscrypto_int.h"


#if defined(__MINGW32__)

/* MSDN CryptAcquireCertificatePrivateKey flags */

#ifndef CRYPT_ACQUIRE_COMPARE_KEY_FLAG
#  define CRYPT_ACQUIRE_COMPARE_KEY_FLAG	0x4
/* The public key in the certificate is compared with the public key
   returned by the cryptographic service provider (CSP). If the keys
   do not match, the acquisition operation fails and the last error
   code is set to NTE_BAD_PUBLIC_KEY. If a cached handle is returned,
   no comparison is made. */
#endif


BOOL
WINAPI
CryptAcquireCertificatePrivateKey(
    IN PCCERT_CONTEXT pCert,
    IN DWORD dwFlags,
    IN void *pvReserved,
    OUT HCRYPTPROV *phCryptProv,
    OUT OPTIONAL DWORD *pdwKeySpec,
    OUT OPTIONAL BOOL *pfCallerFreeProv
);


#ifndef CERT_NAME_SIMPLE_DISPLAY_TYPE
#  define CERT_NAME_SIMPLE_DISPLAY_TYPE		4
#endif

#endif


/* note required side effect: increment dest*/
#define PUT_STRING_REV(dest, src, len) {      \
    size_t n = (size_t) len;                  \
    const char* p = (char*) src + n - 1;      \
                                              \
    for ( ; n > 0; n--, (char*)dest++, p--) { \
        *(char*)dest = *p;                    \
    }                                         \
}


static int/*bool*/
set_crypto_provider(MSCRYPTO_KEYCTX *keyctx) {
    int ret = 0;

    LPCTSTR pszContainer = "mscrypto_kt_rsa";
    /* pszContainer:
     *
     * [in] Key container name. This is a null-terminated string that
     * identifies the key container to the CSP. This name is
     * independent of the method used to store the keys. Some CSPs
     * store their key containers internally (in hardware), some use
     * the system registry, and others use the file system. When
     * dwFlags is set to CRYPT_VERIFYCONTEXT, pszContainer must be set
     * to NULL.
     *
     * When pszContainer is NULL, a default key container name is used.
     * For example, the Microsoft Base Cryptographic Provider uses the
     * logon name of the user currently logged on as the key container
     * name. Other CSPs can also have default key containers that can
     * be acquired in this way.
     *
     * Applications must not use the default key container to store
     * private keys. When multiple applications use the same container,
     * one application can change or destroy the keys that another
     * application needs to have available. If applications use key
     * containers linked to the application, the risk is reduced of
     * other applications tampering with keys necessary for proper
     * function.
     *
     * An application can obtain the name of the key container in use
     * by reading the PP_CONTAINER value with the CryptGetProvParam
     * function.
     */

    if (keyctx == NULL) {
        MSCRYPTOerr(MSCRYPTO_F_SET_CRYPTO_PROVIDER, MSCRYPTO_R_INVALID_ARGUMENT);
        goto done;
    }

    if (CryptAcquireContext(
        &keyctx->hCryptProv,
        pszContainer,
        (LPCTSTR) NULL /*pszProvider*/,
        (DWORD) PROV_RSA_FULL /*dwProvType*/,
        (DWORD) 0 /*dwFlags*/
    )) {
        ret = 1;
        goto done;
    }

    /*
     * An error occurred in acquiring the context. This could mean
     * that the key container requested does not exist. In this case,
     * the function can be called again to attempt to create a new key
     * container. Error codes are defined in Winerror.h.
     */
    if (GetLastError() != (DWORD) NTE_BAD_KEYSET) {
        MSCRYPTOerr(MSCRYPTO_F_SET_CRYPTO_PROVIDER, MSCRYPTO_R_W32ERR);
        MSCRYPTOw32err("CryptAcquireContext fail");
        goto done;
    }

    if (!CryptAcquireContext(
        &keyctx->hCryptProv,
        pszContainer,
        (LPCTSTR) NULL /*pszProvider*/,
        (DWORD) PROV_RSA_FULL /*dwProvType*/,
        (DWORD) CRYPT_NEWKEYSET /*dwFlags*/
    )) {
        MSCRYPTOerr(MSCRYPTO_F_SET_CRYPTO_PROVIDER, MSCRYPTO_R_W32ERR);
        MSCRYPTOw32err("CryptAcquireContext fail");
        goto done;
    }

    ret = 1;

done:
    return(ret);
}


static int/*bool*/
set_private_key(MSCRYPTO_KEYCTX *keyctx) {
    int ret = 0;

    if (keyctx == NULL) {
        MSCRYPTOerr(MSCRYPTO_F_SET_PRIVATE_KEY, MSCRYPTO_R_INVALID_ARGUMENT);
        goto done;
    }
    if (keyctx->pCert == NULL) {
        MSCRYPTOerr(MSCRYPTO_F_SET_PRIVATE_KEY, MSCRYPTO_R_CERT_IS_NOT_SET);
        goto done;
    }

    if (!CryptAcquireCertificatePrivateKey(
        keyctx->pCert,
        (DWORD) CRYPT_ACQUIRE_COMPARE_KEY_FLAG,
        (void*) NULL /*pvReserved*/,
        &keyctx->hCryptProv,
        &keyctx->dwKeySpec,
        &keyctx->fCallerFreeProv
    )) {
        MSCRYPTOerr(MSCRYPTO_F_SET_PRIVATE_KEY, MSCRYPTO_R_W32ERR);
        MSCRYPTOw32err("CryptAcquireCertificatePrivateKey fail");
        goto done;
    }
fprintf(stderr, "CSP context acquired:"
    " dwKeySpec=%d, fCallerFreeProv=%d.\n"
    , (int)keyctx->dwKeySpec, (int)keyctx->fCallerFreeProv);

    if (!CryptGetUserKey(
        keyctx->hCryptProv,
        keyctx->dwKeySpec,
        &keyctx->hKey
    )) {
        MSCRYPTOerr(MSCRYPTO_F_SET_PRIVATE_KEY, MSCRYPTO_R_W32ERR);
        MSCRYPTOw32err("CryptGetUserKey fail");
        goto done;
    }
fprintf(stderr, "The user pkey has been acquired.\n");

    ret = 1;

done:
    return(ret);
}


static int
mspubblob_to_rsa(const unsigned char* key_blob, RSA* rsa) {
    if (key_blob == NULL) return(0);
    if (rsa == NULL) return(0);


    {
        PUBLICKEYSTRUC *key = (PUBLICKEYSTRUC *) key_blob;
        if (key->bType != PUBLICKEYBLOB) return(-1);
        /*?key->bVersion = 0x02; */
        /*?key->reserved = 0; */
        if (key->aiKeyAlg != CALG_RSA_KEYX) return(-2);
    }

    {   /* construct public key blob from host public key */
        RSAPUBKEY *rsa_pubkey = (RSAPUBKEY*)(((unsigned char*)key_blob) + sizeof(PUBLICKEYSTRUC));
        int mod_size;
        unsigned char *bn;
        unsigned char *keybuf;

        /*?strncmp((char*) &rsa_pubkey->magic, "RSA1", 4); */
        mod_size  = rsa_pubkey->bitlen >> 3;

        bn = malloc(max(mod_size, sizeof(rsa_pubkey->pubexp)));
        if (bn == NULL) return(-3);

        /*get public exponent*/
        keybuf = (unsigned char*) &rsa_pubkey->pubexp;
        if (rsa->e) BN_clear_free(rsa->e);
        memcpy_rev(bn, keybuf, sizeof(rsa_pubkey->pubexp));
        rsa->e = BN_bin2bn(bn, sizeof(rsa_pubkey->pubexp), NULL);

        /*get modulus*/
        keybuf = ((unsigned char*)key_blob) + sizeof(PUBLICKEYSTRUC) + sizeof(RSAPUBKEY);
        if (rsa->n) BN_clear_free(rsa->n);
        memcpy_rev(bn, keybuf, mod_size);
        rsa->n = BN_bin2bn(bn, mod_size, NULL);
    }
    return(1);
}


static int/*bool*/
set_public_key(MSCRYPTO_KEYCTX *keyctx, RSA* rsa) {
    int ret = 0;

    BYTE *pbKeyBlob = NULL;
    DWORD dwBlobLen;


    if (keyctx == NULL) {
        MSCRYPTOerr(MSCRYPTO_F_SET_PUBLIC_KEY, MSCRYPTO_R_INVALID_ARGUMENT);
        goto done;
    }
    if (keyctx->hKey == (HCRYPTKEY)NULL) {
        MSCRYPTOerr(MSCRYPTO_F_SET_PUBLIC_KEY, MSCRYPTO_R_KEY_IS_NOT_SET);
        goto done;
    }

    if (!CryptExportKey(
        keyctx->hKey,
        (HCRYPTKEY)NULL,
        PUBLICKEYBLOB,
        (DWORD) 0 /*dwFlags*/,
        NULL,
        &dwBlobLen
    )) {
        MSCRYPTOerr(MSCRYPTO_F_SET_PUBLIC_KEY, MSCRYPTO_R_W32ERR);
        MSCRYPTOw32err("CryptExportKey cannot get BLOB length");
        goto done;
    }
fprintf(stderr, "Size of the BLOB for the public key determined.\n");

    /* allocate memory for the pbKeyBlob */
    pbKeyBlob = (BYTE*)malloc(dwBlobLen);
    if (pbKeyBlob == NULL) {
        MSCRYPTOerr(MSCRYPTO_F_SET_PUBLIC_KEY, MSCRYPTO_R_INSUFFICIENT_MEMORY);
        goto done;
    }
fprintf(stderr, "Memory has been allocated for the BLOB.\n");

    /* do the actual exporting into the key BLOB */
    if (!CryptExportKey(
        keyctx->hKey,
        (HCRYPTKEY)NULL,
        PUBLICKEYBLOB,
        (DWORD) 0 /*dwFlags*/,
        pbKeyBlob,
        &dwBlobLen
    )) {
        MSCRYPTOerr(MSCRYPTO_F_SET_PUBLIC_KEY, MSCRYPTO_R_W32ERR);
        MSCRYPTOw32err("CryptExportKey fail for PUBLICKEYBLOB");
        goto done;
    }
fprintf(stderr, "Contents have been written to the BLOB.\n");

#if 0
    if (!CryptImportKey(
        keyctx->hCryptProv,
        pbKeyBlob,
        dwBlobLen,
        (HCRYPTKEY) NULL,
        (DWORD) 0 /*dwFlags*/,
        (HCRYPTKEY*) &keyctx->hPubKey
    )) {
        MSCRYPTOerr(MSCRYPTO_F_SET_PUBLIC_KEY, MSCRYPTO_R_W32ERR);
        MSCRYPTOw32err("CryptImportKey fail");
        keyctx->hPubKey = (HCRYPTKEY)NULL;
        goto done;
    }
#endif

    if (mspubblob_to_rsa(pbKeyBlob, rsa) <=0) {
        /*can method above fail ?*/
        goto done;
    }

    ret = 1;

done:
    if (pbKeyBlob) {
        free(pbKeyBlob);
        pbKeyBlob = NULL;
    }
    return(ret);
}


EVP_PKEY*
mscrypto_load_privkey(
    ENGINE *e,
    const char *key_id,
    UI_METHOD *ui_method,
    void *callback_data
) {
    EVP_PKEY *pkey = NULL;

    UNUSED(e);
    UNUSED(ui_method);
    UNUSED(callback_data);

#ifdef MSCRYPTO_TRACE
fprintf(stderr, "TRACE[mscrypto] mscrypto_load_privkey(): key_id=%s\n", key_id);
#endif

{
    RSA *rsa = NULL;
    MSCRYPTO_KEYCTX *keyctx = NULL;
    wchar_t *name = NULL;

    rsa = RSA_new_method(e);
    if (rsa == NULL) {
        MSCRYPTOerr(MSCRYPTO_F_LOAD_PRIVKEY, MSCRYPTO_R_INSUFFICIENT_MEMORY);
        goto keydone;
    }

    keyctx = RSA_get_ex_data(rsa, 0);
    if (keyctx == NULL) {
        MSCRYPTOerr(MSCRYPTO_R_MISSING_ENGINE_CONTEXT, MSCRYPTO_R_MISSING_KEY_CONTEXT);
        goto keydone;
    }

    {
        size_t name_len;
        int n;

        n = strlen(key_id);
        name = malloc((n + 1) * sizeof(wchar_t));
        if (name == NULL) {
            MSCRYPTOerr(MSCRYPTO_F_LOAD_PRIVKEY, MSCRYPTO_R_INSUFFICIENT_MEMORY);
            goto keydone;
        }

        name_len = mbstowcs(name, key_id, n);
#ifdef MSCRYPTO_TRACE
fprintf(stderr, "TRACE[mscrypto] mscrypto_load_privkey() key_id=%s, n=%d, name_len=%d\n", key_id, (int) n, (int) name_len);
#endif
        if (name_len == (size_t) -1) {
            MSCRYPTOerr(MSCRYPTO_F_LOAD_PRIVKEY, MSCRYPTO_R_MBSTOWCS_FAIL);
            goto keydone;
        }
        name[name_len] = (wchar_t)0;
    }

    {
        MSCRYPTO_CTX *ctx = ENGINE_get_ex_data(rsa->engine, 0);

        if (ctx == NULL) {
            MSCRYPTOerr(MSCRYPTO_F_LOAD_PRIVKEY, MSCRYPTO_R_MISSING_ENGINE_CONTEXT);
            goto keydone;
        }

        keyctx->pCert = CertFindCertificateInStore(
            ctx->hStoreHandle,
            MSCRYPTO_ENCODING_TYPE,
            0,
            CERT_FIND_SUBJECT_STR,
            name,
            NULL
        );
        if (keyctx->pCert == NULL) {
            MSCRYPTOerr(MSCRYPTO_F_LOAD_PRIVKEY, MSCRYPTO_R_W32ERR);
            MSCRYPTOw32err("CertFindCertificateInStore fail");
            goto keydone;
        }
#ifdef MSCRYPTO_TRACE
#if 0
fprintf(stderr, "The signer's certificate (%p) was found.\n", p);
#else
{	/*bogus return*/
	char *n = NULL;
	size_t k;
	k = CertGetNameString(
		keyctx->pCert,
		CERT_NAME_SIMPLE_DISPLAY_TYPE,
		0,
		NULL,
		n, /*pszNameString*/
		0);
	n = malloc(k);
	if(CertGetNameString(
		keyctx->pCert,
		CERT_NAME_SIMPLE_DISPLAY_TYPE,
		0,
		NULL,
		n, /*pszNameString*/
		k))
	{
		fprintf(stderr, "The signer's certificate (%s) was found.\n", n);
	}
	free(n);
}
#endif
#endif
    }

    if (!set_private_key(keyctx)) {
        /*error is already set*/
        goto keydone;
    }

#if 1
    /* TODO:
     * to use RSA_METHOD_FLAG_NO_CHECK or
     * to get public key and to set n and e ????
     */
    if (!set_public_key(keyctx, rsa)) {
        /*error is already set*/
        goto keydone;
    }
#endif

    pkey = EVP_PKEY_new();
    if (pkey == NULL) {
        MSCRYPTOerr(MSCRYPTO_F_LOAD_PRIVKEY, MSCRYPTO_R_INSUFFICIENT_MEMORY);
        goto keydone;
    } else {
        EVP_PKEY_set1_RSA(pkey, rsa);
        rsa = NULL;
    }

keydone:
    if (rsa) {
        RSA_free(rsa);
        rsa = NULL;
    }
    if (name) {
         free(name);
         name = NULL;
    }
}

    if (pkey != NULL) goto done;

/*try to load from file*/
#ifdef MSCRYPTO_TRACE
fprintf(stderr, "TRACE[mscrypto] mscrypto_load_privkey() try to load from file\n");
#endif
{
    BIO *in = NULL;
    in = BIO_new_file(key_id, "r");
    if (!in) goto filedone;

    pkey = PEM_read_bio_PrivateKey(in, NULL, 0, NULL);
    switch (EVP_PKEY_type(pkey->type)) {
#ifndef OPENSSL_NO_RSA
        case EVP_PKEY_RSA: {
            RSA *rsa = EVP_PKEY_get1_RSA(pkey);
            const RSA_METHOD *meth = ENGINE_get_RSA(e);
            /*TODO: to check for errors*/
            if (RSA_get_method(rsa) !=  meth) {
                RSA_set_method(rsa, meth);
            }
            /*TODO: to set Context, hKey, hPubKey, etc ...
            MSCRYPTO_KEYCTX *keyctx = NULL;

            keyctx = RSA_get_ex_data(rsa, 0);
            if (keyctx == NULL) {
                MSCRYPTOerr(MSCRYPTO_F_LOAD_PRIVKEY, MSCRYPTO_R_MISSING_KEY_CONTEXT);
                goto done;
            }
            keyctx->hKey = mscrypto_RSA_private_to_hKey(rsa, keyctx->hProv);
            keyctx->hPubKey = mscrypto_RSA_public_to_hKey(rsa, keyctx->hProv);
            */
        } break;
#endif
#ifndef OPENSSL_NO_DSA
        case EVP_PKEY_DSA: {
            /*TODO*/
        } break;
#endif
        default:
            /*TODO*/
            goto filedone;
    }

filedone:
    if (in) {
        BIO_free(in);
        in = NULL;
    }
}

done:

#ifdef MSCRYPTO_TRACE
fprintf(stderr, "TRACE[mscrypto] mscrypto_load_privkey()^ pkey=%p\n", pkey);
#endif
    return(pkey);
}


HCRYPTKEY
mscrypto_get_hKey(const RSA *rsa, int pubkey) {
    HCRYPTKEY hKey = (HCRYPTKEY)NULL;
    MSCRYPTO_KEYCTX *keyctx = NULL;

#ifdef MSCRYPTO_TRACE
fprintf(stderr, "TRACE[mscrypto] mscrypto_get_hKey(rsa=%p, pubkey=%d):\n", rsa, pubkey);
#endif

    keyctx = RSA_get_ex_data(rsa, 0);
    if (keyctx == NULL) {
        MSCRYPTOerr(MSCRYPTO_F_GET_HKEY, MSCRYPTO_R_MISSING_KEY_CONTEXT);
        goto done;
    }

    hKey = (pubkey ? keyctx->hPubKey : keyctx->hKey);
    if (hKey == (HCRYPTKEY)NULL) {
        /* this should be error, but ...*/
#ifdef MSCRYPTO_TRACE
fprintf(stderr, "TRACE[mscrypto] mscrypto_get_hKey() hKey is NULL - external key?\n");
#endif
        if (keyctx->pCert == NULL) {
            /*external key*/
            if (keyctx->hCryptProv == (HCRYPTPROV)NULL) {
                if (!set_crypto_provider(keyctx)) {
                    /*error is already set*/
                    goto done;
                }
            }
            if (pubkey) {
                keyctx->hPubKey = mscrypto_RSA_public_to_hKey(rsa, keyctx->hCryptProv);
            } else {
                keyctx->dwKeySpec = AT_KEYEXCHANGE;
                keyctx->hKey = mscrypto_RSA_private_to_hKey(rsa, keyctx->hCryptProv);
            }
            hKey = (pubkey ? keyctx->hPubKey : keyctx->hKey);
        } else {
            if (pubkey) {
#ifdef MSCRYPTO_TRACE
fprintf(stderr, "TRACE[mscrypto] mscrypto_get_hKey() return private hKey\n");
#endif
                hKey = keyctx->hKey;
            }
        }
    }

done:
#ifdef MSCRYPTO_TRACE
fprintf(stderr, "TRACE[mscrypto] mscrypto_get_hKey()^ hKey=%p\n", (void*)hKey);
#endif
    return(hKey);
}


static ALG_ID
mscrypto_alg_id(int dgst_type) {
    static struct alg_table_s {
        int     dgst_type; /*OpenSSL*/
        ALG_ID  alg_id;    /*MSCRYPTO*/
    } *p, table[] = {
    #ifndef OPENSSL_NO_MD5
         { NID_md5, CALG_MD5 },
    #endif
    #ifndef OPENSSL_NO_MD4
         { NID_md4, CALG_MD4 },
    #endif
    #ifndef OPENSSL_NO_MD2
         { NID_md2, CALG_MD2 },
    #endif
    #ifndef OPENSSL_NO_SHA1
         { NID_sha1, CALG_SHA1 },
    #endif
    #ifndef OPENSSL_NO_SHA
    #if ALG_SID_SHA != ALG_SID_SHA1
        { NID_sha, CALG_SHA },
    #endif
    #endif
    #ifndef OPENSSL_NO_MDC2
    #ifdef CALG_MDC2
        { NID_mdc2, CALG_MDC2 }
    #endif
    #endif
    #ifndef OPENSSL_NO_RMD160
        #if 0
         { NID_ripemd160, CALG_RIPEMD160 }, /*Invalid algorithm specified !!!*/
         #else
         { NID_ripemd160, CALG_RIPEMD }, /*CryptCreateHash fail !!!*/
        #endif
    #endif
    #ifndef OPENSSL_NO_MD5
    #ifndef OPENSSL_NO_SHA
        { NID_md5_sha1, CALG_SSL3_SHAMD5 }, /*ssl connection*/
    #endif
    #endif

        /*not found*/
        {0, 0/*== (ALG_CLASS_HASH | ALG_TYPE_ANY | ALG_SID_ANY)*/}
    };

    for (p = table; p->dgst_type; p++) {
         if (p->dgst_type == dgst_type)
             return(p->alg_id);
    }

#ifdef MSCRYPTO_TRACE
fprintf(stderr, "TRACE[mscrypto] mscrypto_alg_id:"
" unsupported digest=%d(%s)\n"
, dgst_type, OBJ_nid2ln(dgst_type));
#endif
    return(0);
}


HCRYPTHASH
mscrypto_create_hash(
    HCRYPTPROV hCryptProv,
    int dtype,
    const unsigned char *m,
    unsigned int m_length
) {
    HCRYPTHASH hHash = (HCRYPTHASH)NULL;

    ALG_ID alg_id;

    UNUSED(m_length);

    if (hCryptProv == (HCRYPTPROV)NULL) {
        MSCRYPTOerr(MSCRYPTO_F_CREATE_HASH, MSCRYPTO_R_INVALID_ARGUMENT);
        goto done;
    }

    alg_id = mscrypto_alg_id(dtype);
    if (alg_id == 0) goto done;

    if (!CryptCreateHash(
        hCryptProv,
        (ALG_ID) alg_id,
        (HCRYPTKEY)NULL /*hKey*/,
        (DWORD) 0 /*dwFlags*/,
        &hHash
    )) {
        MSCRYPTOerr(MSCRYPTO_F_CREATE_HASH, MSCRYPTO_R_W32ERR);
        MSCRYPTOw32err("CryptCreateHash fail");
        goto done;
    }

    if (CryptSetHashParam(
        hHash,
        (DWORD) HP_HASHVAL /*dwParam*/,
        (BYTE*) m,
        (DWORD) 0 /*dwFlags*/
    )) goto done;

    MSCRYPTOerr(MSCRYPTO_F_CREATE_HASH, MSCRYPTO_R_W32ERR);
    MSCRYPTOw32err("CryptSetHashParam fail");

    CryptDestroyHash(hHash);
    hHash = (HCRYPTHASH)NULL;

done:
    return(hHash);
}


/*
From MSDN, Private Key blob:
PUBLICKEYSTRUC  publickeystruc ;
RSAPUBKEY rsapubkey;
BYTE modulus[rsapubkey.bitlen/8]; (n)
BYTE prime1[rsapubkey.bitlen/16]; (p)
BYTE prime2[rsapubkey.bitlen/16]; (q)
BYTE exponent1[rsapubkey.bitlen/16]; (dmp1)
BYTE exponent2[rsapubkey.bitlen/16]; (dmq1)
BYTE coefficient[rsapubkey.bitlen/16]; (iqmp)
BYTE privateExponent[rsapubkey.bitlen/8]; (d)

other:
public exponent (e)
*/
HCRYPTKEY
mscrypto_RSA_private_to_hKey(const RSA *key, HCRYPTPROV hProv) {
    HCRYPTKEY hKey = (HCRYPTKEY)NULL;

    DWORD mod_size;
    DWORD half_mod_size;
    size_t blob_len;
    BYTE *key_blob = NULL;
    unsigned char *keybuf;
    unsigned char *bn = NULL;

    if (key == NULL) {
         MSCRYPTOerr(MSCRYPTO_F_RSA_PRIVATE_TO_HKEY, MSCRYPTO_R_INVALID_ARGUMENT);
         goto done;
    }
    if (hProv == (HCRYPTPROV)NULL) {
         MSCRYPTOerr(MSCRYPTO_F_RSA_PRIVATE_TO_HKEY, MSCRYPTO_R_INVALID_ARGUMENT);
         goto done;
    }

    mod_size = BN_num_bytes(key->n);
    half_mod_size = mod_size >> 1;
#ifdef MSCRYPTO_TRACE
fprintf(stderr, "TRACE[mscrypto] mscrypto_RSA_private_to_hKey() mod_size=%d\n", (int)mod_size);
#endif

    /* allocate buffer for private key blob */
    /*blob_len = sizeof(PUBLICKEYSTRUC) + sizeof(RSAPUBKEY) + mod_size  + (mod_size/2 * 7);*/
    blob_len = sizeof(PUBLICKEYSTRUC) + sizeof(RSAPUBKEY) + (mod_size<<2)  + half_mod_size;
    key_blob = malloc(blob_len);
    if (key_blob == NULL) {
        MSCRYPTOerr(MSCRYPTO_F_RSA_PRIVATE_TO_HKEY, MSCRYPTO_R_INSUFFICIENT_MEMORY);
        goto done;
    }

    memset(key_blob, 0, blob_len);
    {
        PUBLICKEYSTRUC *key = (PUBLICKEYSTRUC *) key_blob;
        key->bType = PRIVATEKEYBLOB;
        key->bVersion = 0x02;
        /*key->reserved = 0;*/
        key->aiKeyAlg = CALG_RSA_KEYX;
    }

    /* construct private key blob from host private key */
    keybuf = ((unsigned char*)key_blob) + sizeof(PUBLICKEYSTRUC) + sizeof(RSAPUBKEY);
    bn = malloc(mod_size);
    if (bn == NULL) {
        MSCRYPTOerr(MSCRYPTO_F_RSA_PRIVATE_TO_HKEY, MSCRYPTO_R_INSUFFICIENT_MEMORY);
        goto done;
    }

    BN_bn2bin(key->n, bn);
    /* change big endian to little endian */
    PUT_STRING_REV(keybuf, bn, mod_size);

    BN_bn2bin(key->p, bn);
    /* change big endian to little endian */
    PUT_STRING_REV(keybuf, bn, half_mod_size);

    BN_bn2bin(key->q, bn);
    /* change big endian to little endian */
    PUT_STRING_REV(keybuf, bn, half_mod_size);

    BN_bn2bin(key->dmp1, bn);
    /* change big endian to little endian */
    PUT_STRING_REV(keybuf, bn, half_mod_size);

    BN_bn2bin(key->dmq1, bn);
    /* change big endian to little endian */
    PUT_STRING_REV(keybuf, bn, half_mod_size);

    BN_bn2bin(key->iqmp, bn);
    /* change big endian to little endian */
    PUT_STRING_REV(keybuf, bn, half_mod_size);

    BN_bn2bin(key->d, bn);
    /* change big endian to little endian */
    PUT_STRING_REV(keybuf, bn, mod_size);

    {
        RSAPUBKEY *rsa_pubkey = (RSAPUBKEY*)(((unsigned char*)key_blob) + sizeof(PUBLICKEYSTRUC));

        strcpy((char*) &rsa_pubkey->magic, "RSA2");
        rsa_pubkey->bitlen = mod_size << 3;

        keybuf = (unsigned char*) &rsa_pubkey->pubexp;
        BN_bn2bin(key->e, bn);
        PUT_STRING_REV(keybuf, bn, BN_num_bytes(key->e));
    }

    /* import private key blob into key container */
    if (!CryptImportKey(
        hProv,
        (BYTE *)key_blob,
        (DWORD) blob_len,
        (HCRYPTKEY) NULL,
        (DWORD) CRYPT_EXPORTABLE, /*dwFlags*/
        &hKey
    )) {
         MSCRYPTOerr(MSCRYPTO_F_RSA_PRIVATE_TO_HKEY, MSCRYPTO_R_W32ERR);
         MSCRYPTOw32err("CryptImportKey fail");
         goto done;
    }

done:
    if (key_blob) free(key_blob);
    if (bn) free(bn);

    return(hKey);
}


HCRYPTKEY
mscrypto_RSA_public_to_hKey(const RSA *key, HCRYPTPROV hProv) {
    HCRYPTKEY hKey = (HCRYPTKEY)NULL;

    DWORD mod_size;
    size_t blob_len;
    unsigned char *key_blob = NULL;
    unsigned char *bn = NULL;

    if (key == NULL) {
         MSCRYPTOerr(MSCRYPTO_F_RSA_PUBLIC_TO_HKEY, MSCRYPTO_R_INVALID_ARGUMENT);
         goto done;
    }
    if (hProv == (HCRYPTPROV)NULL) {
         MSCRYPTOerr(MSCRYPTO_F_RSA_PUBLIC_TO_HKEY, MSCRYPTO_R_INVALID_ARGUMENT);
         goto done;
    }

#ifdef MSCRYPTO_TRACE
fprintf(stderr, "TRACE[mscrypto] BN_num_bytes(key->n)=%d\n", BN_num_bytes(key->n));
fprintf(stderr, "TRACE[mscrypto] BN_num_bytes(key->e)=%d\n", BN_num_bytes(key->e));
#endif

    mod_size = BN_num_bytes(key->n);

    /* allocate buffer for public key blob */
    blob_len = sizeof(PUBLICKEYSTRUC) + sizeof(RSAPUBKEY) + mod_size;
    key_blob = malloc(blob_len);
    if (key_blob == NULL){
        MSCRYPTOerr(MSCRYPTO_F_RSA_PUBLIC_TO_HKEY, MSCRYPTO_R_INSUFFICIENT_MEMORY);
        goto done;
    }
    memset(key_blob, 0, blob_len);

    {
        PUBLICKEYSTRUC *key = (PUBLICKEYSTRUC *) key_blob;
        key->bType = PUBLICKEYBLOB;
        key->bVersion = 0x02;
        key->reserved = 0;
        key->aiKeyAlg = CALG_RSA_KEYX;
    }

    bn = malloc(mod_size);
    if (bn == NULL) {
        MSCRYPTOerr(MSCRYPTO_F_RSA_PUBLIC_TO_HKEY, MSCRYPTO_R_INSUFFICIENT_MEMORY);
        goto done;
    }

    {   /* construct public key blob from host public key */
        RSAPUBKEY *rsa_pubkey = (RSAPUBKEY*)(((unsigned char*)key_blob) + sizeof(PUBLICKEYSTRUC));
        unsigned char *keybuf;

        strcpy((char*) &rsa_pubkey->magic, "RSA1");
        rsa_pubkey->bitlen = mod_size << 3;

        /*set public exponent*/
        keybuf = (unsigned char*) &rsa_pubkey->pubexp;
        BN_bn2bin(key->e, bn);
        /* change big endian to little endian */
        PUT_STRING_REV(keybuf, bn, BN_num_bytes(key->e));

        /*set modulus*/
        keybuf = ((unsigned char*)key_blob) + sizeof(PUBLICKEYSTRUC) + sizeof(RSAPUBKEY);
        BN_bn2bin(key->n, bn);
        PUT_STRING_REV(keybuf, bn, mod_size);
    }

    /* import public key blob into key container */
    if (!CryptImportKey(
        hProv,
        (BYTE *) key_blob,
        (DWORD) blob_len,
        (HCRYPTKEY) NULL,
        (DWORD) 0,
        &hKey
    )) {
        MSCRYPTOerr(MSCRYPTO_F_RSA_PUBLIC_TO_HKEY, MSCRYPTO_R_W32ERR);
        MSCRYPTOw32err("CryptImportKey fail");
        goto done;
    }

done:
    if (key_blob) free(key_blob);
    if (bn) free(bn);

    return(hKey);
}
