/**
 * Copyright 2015 Ministerio de Industria, Energía y Turismo
 *
 * Este fichero es parte de "Componentes de Firma XAdES 1.1.7".
 *
 * Licencia con arreglo a la EUPL, Versión 1.1 o –en cuanto sean aprobadas por la Comisión Europea– versiones posteriores de la EUPL (la Licencia);
 * Solo podrá usarse esta obra si se respeta la Licencia.
 *
 * Puede obtenerse una copia de la Licencia en:
 *
 * http://joinup.ec.europa.eu/software/page/eupl/licence-eupl
 *
 * Salvo cuando lo exija la legislación aplicable o se acuerde por escrito, el programa distribuido con arreglo a la Licencia se distribuye «TAL CUAL»,
 * SIN GARANTÍAS NI CONDICIONES DE NINGÚN TIPO, ni expresas ni implícitas.
 * Véase la Licencia en el idioma concreto que rige los permisos y limitaciones que establece la Licencia.
 */
//=--------------------------------------------------------------------------=
// security.cpp    by Stanley Man-Kit Ho
//=--------------------------------------------------------------------------=
//

#include <jni.h>
#include <stdlib.h>
#include <windows.h>
#include <BaseTsd.h>
#include <wincrypt.h>
#include <stdio.h>


#define OID_EKU_ANY         "2.5.29.37.0"

#define CERTIFICATE_PARSING_EXCEPTION \
                            "java/security/cert/CertificateParsingException"
#define KEY_EXCEPTION       "java/security/KeyException"
#define KEYSTORE_EXCEPTION  "java/security/KeyStoreException"
#define PROVIDER_EXCEPTION  "java/security/ProviderException"
#define SIGNATURE_EXCEPTION "java/security/SignatureException"

extern "C" {

/*
 * Throws an arbitrary Java exception.
 * The exception message is a Windows system error message.
 */
void ThrowException(JNIEnv *env, char *exceptionName, DWORD dwError)
{
    char szMessage[1024];
    szMessage[0] = '\0';

    FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, dwError, NULL, szMessage,
        1024, NULL);

    jclass exceptionClazz = env->FindClass(exceptionName);
    env->ThrowNew(exceptionClazz, szMessage);
}


/*
 * Maps the name of a hash algorithm to an algorithm identifier.
 */
ALG_ID MapHashAlgorithm(JNIEnv *env, jstring jHashAlgorithm) {

    const char* pszHashAlgorithm = NULL;
    ALG_ID algId = 0;

    pszHashAlgorithm = env->GetStringUTFChars(jHashAlgorithm, NULL);

    if ((strcmp("SHA", pszHashAlgorithm) == 0) ||
        (strcmp("SHA1", pszHashAlgorithm) == 0) ||
        (strcmp("SHA-1", pszHashAlgorithm) == 0)) {

        algId = CALG_SHA1;
    } else if (strcmp("SHA-256", pszHashAlgorithm) == 0) {
        algId = CALG_SHA_256;
    } else if (strcmp("SHA-384", pszHashAlgorithm) == 0) {
        algId = CALG_SHA_384;
    } else if (strcmp("SHA-512", pszHashAlgorithm) == 0) {
        algId = CALG_SHA_512;
    } else if (strcmp("MD5", pszHashAlgorithm) == 0) {
        algId = CALG_MD5;
    } else if (strcmp("MD2", pszHashAlgorithm) == 0) {
        algId = CALG_MD2;
    }

    if (pszHashAlgorithm)
        env->ReleaseStringUTFChars(jHashAlgorithm, pszHashAlgorithm);

   return algId;
}


/*
 * Returns a certificate chain context given a certificate context and key
 * usage identifier.
 */
bool GetCertificateChain(LPSTR lpszKeyUsageIdentifier, PCCERT_CONTEXT pCertContext, PCCERT_CHAIN_CONTEXT* ppChainContext)
{
    CERT_ENHKEY_USAGE        EnhkeyUsage;
    CERT_USAGE_MATCH         CertUsage;
    CERT_CHAIN_PARA          ChainPara;
    DWORD                    dwFlags = 0;
    LPSTR                    szUsageIdentifierArray[1];

    szUsageIdentifierArray[0] = lpszKeyUsageIdentifier;
    EnhkeyUsage.cUsageIdentifier = 1;
    EnhkeyUsage.rgpszUsageIdentifier = szUsageIdentifierArray;
    CertUsage.dwType = USAGE_MATCH_TYPE_AND;
    CertUsage.Usage  = EnhkeyUsage;
    ChainPara.cbSize = sizeof(CERT_CHAIN_PARA);
    ChainPara.RequestedUsage=CertUsage;

    // Build a chain using CertGetCertificateChain
    // and the certificate retrieved.
    return (::CertGetCertificateChain(NULL,     // use the default chain engine
                pCertContext,   // pointer to the end certificate
                NULL,           // use the default time
                NULL,           // search no additional stores
                &ChainPara,     // use AND logic and enhanced key usage
                                //  as indicated in the ChainPara
                                //  data structure
                dwFlags,
                NULL,           // currently reserved
                ppChainContext) == TRUE);       // return a pointer to the chain created
}

/*
 * Open a store by its name and its location
 */
HCERTSTORE OpenGenericStore(LPCSTR pszCertStoreName)
{

    LPCSTR lpszStoreProvider;
    DWORD dwFlags = 0;
    void *pvPara;
    
    if (strcmp("MY", pszCertStoreName) == 0) {
        lpszStoreProvider = CERT_STORE_PROV_SYSTEM;
        dwFlags = dwFlags | CERT_SYSTEM_STORE_CURRENT_USER;
        pvPara =  L"MY";
    } else if (strcmp("ROOT", pszCertStoreName) == 0) {
        lpszStoreProvider = CERT_STORE_PROV_SYSTEM;
        dwFlags = dwFlags | CERT_SYSTEM_STORE_CURRENT_USER;
        pvPara =  L"ROOT";
    } else if (strcmp("CA", pszCertStoreName) == 0) {
        lpszStoreProvider = CERT_STORE_PROV_SYSTEM;
        dwFlags = dwFlags | CERT_SYSTEM_STORE_CURRENT_USER;
        pvPara =  L"CA";
    } else if (strcmp("LocalMachine-MY", pszCertStoreName) == 0) {
        lpszStoreProvider = CERT_STORE_PROV_SYSTEM;
        dwFlags = dwFlags | CERT_SYSTEM_STORE_LOCAL_MACHINE;
        pvPara =  L"MY";
    } else if (strcmp("LocalMachine-ROOT", pszCertStoreName) == 0) {
        lpszStoreProvider = CERT_STORE_PROV_SYSTEM;
        dwFlags = dwFlags | CERT_SYSTEM_STORE_LOCAL_MACHINE;
        pvPara =  L"ROOT";
    } else if (strcmp("LocalMachine-CA", pszCertStoreName) == 0) {
        lpszStoreProvider = CERT_STORE_PROV_SYSTEM;
        dwFlags = dwFlags | CERT_SYSTEM_STORE_LOCAL_MACHINE;
        pvPara =  L"CA";
    } else {
        return NULL;
    }

    return ::CertOpenStore(lpszStoreProvider, 0, NULL, dwFlags, pvPara);
}


/////////////////////////////////////////////////////////////////////////////
//

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_PRNG
 * Method:    generateSeed
 * Signature: (I[B)[B
 */
JNIEXPORT jbyteArray JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_PRNG_generateSeed
  (JNIEnv *env, jclass clazz, jint length, jbyteArray seed)
{

    HCRYPTPROV hCryptProv = NULL;
    BYTE*      pbData = NULL;
    jbyte*     reseedBytes = NULL;
    jbyte*     seedBytes = NULL;
    jbyteArray result = NULL;

    __try
    {
        //  Acquire a CSP context.
        if(::CryptAcquireContext(
           &hCryptProv,
           NULL,
           NULL,
           PROV_RSA_FULL,
           CRYPT_VERIFYCONTEXT) == FALSE)
        {
            ThrowException(env, PROVIDER_EXCEPTION, GetLastError());
            __leave;
        }

        /*
         * If length is negative then use the supplied seed to re-seed the
         * generator and return null.
         * If length is non-zero then generate a new seed according to the
         * requested length and return the new seed.
         * If length is zero then overwrite the supplied seed with a new
         * seed of the same length and return the seed.
         */
        if (length < 0) {
            length = env->GetArrayLength(seed);
            reseedBytes = env->GetByteArrayElements(seed, 0);

            if (::CryptGenRandom(
                hCryptProv,
                length,
                (BYTE *) reseedBytes) == FALSE) {

                ThrowException(env, PROVIDER_EXCEPTION, GetLastError());
                __leave;
            }

            result = NULL;

        } else if (length > 0) {

            pbData = new BYTE[length];

            if (::CryptGenRandom(
                hCryptProv,
                length,
                pbData) == FALSE) {

                ThrowException(env, PROVIDER_EXCEPTION, GetLastError());
                __leave;
            }

            result = env->NewByteArray(length);
            env->SetByteArrayRegion(result, 0, length, (jbyte*) pbData);

        } else { // length == 0

            length = env->GetArrayLength(seed);
            seedBytes = env->GetByteArrayElements(seed, 0);

            if (::CryptGenRandom(
                hCryptProv,
                length,
                (BYTE *) seedBytes) == FALSE) {

                ThrowException(env, PROVIDER_EXCEPTION, GetLastError());
                __leave;
            }

            result = seed; // seed will be updated when seedBytes gets released
        }
    }
    __finally
    {
        //--------------------------------------------------------------------
        // Clean up.

        if (reseedBytes)
            env->ReleaseByteArrayElements(seed, reseedBytes, JNI_ABORT);

        if (pbData)
            delete [] pbData;

        if (seedBytes)
            env->ReleaseByteArrayElements(seed, seedBytes, 0); // update orig

        if (hCryptProv)
            ::CryptReleaseContext(hCryptProv, 0);
    }

    return result;
}


/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_KeyStore
 * Method:    loadKeysOrCertificateChains
 * Signature: (Ljava/lang/String;Ljava/util/Collection;)V
 */
JNIEXPORT void JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_KeyStore_loadKeysOrCertificateChains
  (JNIEnv *env, jobject obj, jstring jCertStoreName, jobject jCollections)
{
    /**
     * Certificate in cert store has enhanced key usage extension
     * property (or EKU property) that is not part of the certificate itself. To determine
     * if the certificate should be returned, both the enhanced key usage in certificate
     * extension block and the extension property stored along with the certificate in
     * certificate store should be examined. Otherwise, we won't be able to determine
     * the proper key usage from the Java side because the information is not stored as
     * part of the encoded certificate.
     */

    const char* pszCertStoreName = NULL;
    HCERTSTORE hCertStore = NULL;
    PCCERT_CONTEXT pCertContext = NULL;
    char* pszNameString = NULL; // certificate's friendly name
    DWORD cchNameString = 0;


    __try
    {
        // Open a system certificate store.
        pszCertStoreName = env->GetStringUTFChars(jCertStoreName, NULL);
        if ((hCertStore = OpenGenericStore(pszCertStoreName)) == NULL) {
            ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
            __leave;
        }

        // Determine clazz and method ID to generate certificate
        jclass clazzArrayList = env->FindClass("java/util/ArrayList");

        jmethodID mNewArrayList = env->GetMethodID(clazzArrayList, "<init>", "()V");

        jmethodID mGenCert = env->GetMethodID(env->GetObjectClass(obj),
                                              "generateCertificate",
                                              "([BLjava/util/Collection;)V");

        // Determine method ID to generate certificate chain
        jmethodID mGenCertChain = env->GetMethodID(env->GetObjectClass(obj),
                                                   "generateCertificateChain",
                                                   "(Ljava/lang/String;Ljava/util/Collection;Ljava/util/Collection;)V");

        // Determine method ID to generate RSA certificate chain
        jmethodID mGenRSAKeyAndCertChain = env->GetMethodID(env->GetObjectClass(obj),
                                                   "generateRSAKeyAndCertificateChain",
                                                   "(Ljava/lang/String;JJILjava/util/Collection;Ljava/util/Collection;)V");

        // Use CertEnumCertificatesInStore to get the certificates
        // from the open store. pCertContext must be reset to
        // NULL to retrieve the first certificate in the store.
        while (pCertContext = ::CertEnumCertificatesInStore(hCertStore, pCertContext))
        {
            // Check if private key available - client authentication certificate
            // must have private key available.
            HCRYPTPROV hCryptProv = NULL;
            DWORD dwKeySpec = 0;
            HCRYPTKEY hUserKey = NULL;
            BOOL bCallerFreeProv = FALSE;
            BOOL bHasNoPrivateKey = FALSE;
            DWORD dwPublicKeyLength = 0;

            if (::CryptAcquireCertificatePrivateKey(pCertContext, NULL, NULL,
                                                    &hCryptProv, &dwKeySpec, &bCallerFreeProv) == FALSE)
            {
                bHasNoPrivateKey = TRUE;

            } else {
                // Private key is available

            BOOL bGetUserKey = ::CryptGetUserKey(hCryptProv, dwKeySpec, &hUserKey);

            // Skip certificate if cannot find private key
            if (bGetUserKey == FALSE)
            {
                if (bCallerFreeProv)
                    ::CryptReleaseContext(hCryptProv, NULL);

                continue;
            }

            // Set cipher mode to ECB
            DWORD dwCipherMode = CRYPT_MODE_ECB;
            ::CryptSetKeyParam(hUserKey, KP_MODE, (BYTE*)&dwCipherMode, NULL);


            // If the private key is present in smart card, we may not be able to
            // determine the key length by using the private key handle. However,
            // since public/private key pairs must have the same length, we could
            // determine the key length of the private key by using the public key
            // in the certificate.
            dwPublicKeyLength = ::CertGetPublicKeyLength(X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
                                                               &(pCertContext->pCertInfo->SubjectPublicKeyInfo));

}
            PCCERT_CHAIN_CONTEXT pCertChainContext = NULL;

            // Build certificate chain by using system certificate store.
            // Add cert chain into collection for any key usage.
            //
            if (GetCertificateChain(OID_EKU_ANY, pCertContext,
                &pCertChainContext))
            {

                for (unsigned int i=0; i < pCertChainContext->cChain; i++)
                {
                    // Found cert chain
                    PCERT_SIMPLE_CHAIN rgpChain =
                        pCertChainContext->rgpChain[i];

                    // Create ArrayList to store certs in each chain
                    jobject jArrayList =
                        env->NewObject(clazzArrayList, mNewArrayList);

                    for (unsigned int j=0; j < rgpChain->cElement; j++)
                    {
                        PCERT_CHAIN_ELEMENT rgpElement =
                            rgpChain->rgpElement[j];
                        PCCERT_CONTEXT pc = rgpElement->pCertContext;

                        // Retrieve the friendly name of the first certificate
                        // in the chain
                        if (j == 0) {

                            // If the cert's name cannot be retrieved then
                            // pszNameString remains set to NULL.
                            // (An alias name will be generated automatically
                            // when storing this cert in the keystore.)

                            // Get length of friendly name
                            if ((cchNameString = CertGetNameString(pc,
                                CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL,
                                NULL, 0)) > 1) {

                                // Found friendly name
                                pszNameString = new char[cchNameString];
                                CertGetNameString(pc,
                                    CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL,
                                    pszNameString, cchNameString);
                            }
                        }

                        BYTE* pbCertEncoded = pc->pbCertEncoded;
                        DWORD cbCertEncoded = pc->cbCertEncoded;

                        // Allocate and populate byte array
                        jbyteArray byteArray = env->NewByteArray(cbCertEncoded);
                        env->SetByteArrayRegion(byteArray, 0, cbCertEncoded,
                            (jbyte*) pbCertEncoded);

                        // Generate certificate from byte array and store into
                        // cert collection
                        env->CallVoidMethod(obj, mGenCert, byteArray, jArrayList);
                    }
                    if (bHasNoPrivateKey)
                    {
                        // Generate certificate chain and store into cert chain
                        // collection
                        env->CallVoidMethod(obj, mGenCertChain,
                            env->NewStringUTF(pszNameString),
                            jArrayList, jCollections);
                    }
                    else
                    {
                    // Determine key type: RSA or DSA
                    DWORD dwData = CALG_RSA_KEYX;
                    DWORD dwSize = sizeof(DWORD);
                    ::CryptGetKeyParam(hUserKey, KP_ALGID, (BYTE*)&dwData,
                        &dwSize, NULL);

                    if ((dwData & ALG_TYPE_RSA) == ALG_TYPE_RSA)
                    {
                        // Generate RSA certificate chain and store into cert
                        // chain collection
                        env->CallVoidMethod(obj, mGenRSAKeyAndCertChain,
                            env->NewStringUTF(pszNameString),
                            (jlong) hCryptProv, (jlong) hUserKey,
                            dwPublicKeyLength, jArrayList, jCollections);
                    }
}
                }

                // Free cert chain
                if (pCertChainContext)
                    ::CertFreeCertificateChain(pCertChainContext);
            }
        }
    }
    __finally
    {
        if (hCertStore)
            ::CertCloseStore(hCertStore, 0);

        if (pszCertStoreName)
            env->ReleaseStringUTFChars(jCertStoreName, pszCertStoreName);

        if (pszNameString)
            delete [] pszNameString;
    }
}


/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_Key
 * Method:    cleanUp
 * Signature: (JJ)V
 */
JNIEXPORT void JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_Key_cleanUp
  (JNIEnv *env, jclass clazz, jlong hCryptProv, jlong hCryptKey)
{
    if (hCryptKey != NULL)
        ::CryptDestroyKey((HCRYPTKEY) hCryptKey);

    if (hCryptProv != NULL)
        ::CryptReleaseContext((HCRYPTPROV) hCryptProv, NULL);
}


/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_RSASignature
 * Method:    signHash
 * Signature: ([BILjava/lang/String;JJ)[B
 */
JNIEXPORT jbyteArray JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_RSASignature_signHash
  (JNIEnv *env, jclass clazz, jbyteArray jHash, jint jHashSize,
        jstring jHashAlgorithm, jlong hCryptProv, jlong hCryptKey)
{
    HCRYPTHASH hHash = NULL;
    jbyte* pHashBuffer = NULL;
    jbyte* pSignedHashBuffer = NULL;
    jbyteArray jSignedHash = NULL;

    __try
    {
        // Map hash algorithm
        ALG_ID algId = MapHashAlgorithm(env, jHashAlgorithm);

        // Acquire a hash object handle.
        if (::CryptCreateHash(HCRYPTPROV(hCryptProv), algId, 0, 0, &hHash) == FALSE)
        {
            ThrowException(env, SIGNATURE_EXCEPTION, GetLastError());
            __leave;
        }

        // Copy hash from Java to native buffer
        pHashBuffer = new jbyte[jHashSize];
        env->GetByteArrayRegion(jHash, 0, jHashSize, pHashBuffer);

        // Set hash value in the hash object
        if (::CryptSetHashParam(hHash, HP_HASHVAL, (BYTE*)pHashBuffer, NULL) == FALSE)
        {
            ThrowException(env, SIGNATURE_EXCEPTION, GetLastError());
            __leave;
        }

        // Determine key spec.
        DWORD dwKeySpec = AT_SIGNATURE;
        ALG_ID dwAlgId;
        DWORD dwAlgIdLen = sizeof(ALG_ID);

        if (! ::CryptGetKeyParam((HCRYPTKEY) hCryptKey, KP_ALGID, (BYTE*)&dwAlgId, &dwAlgIdLen, 0)) {
            ThrowException(env, SIGNATURE_EXCEPTION, GetLastError());
            __leave;

        }
        if (CALG_RSA_KEYX == dwAlgId) {
            dwKeySpec = AT_KEYEXCHANGE;
        }

        // Determine size of buffer
        DWORD dwBufLen = 0;
        if (::CryptSignHash(hHash, dwKeySpec, NULL, NULL, NULL, &dwBufLen) == FALSE)
        {
            ThrowException(env, SIGNATURE_EXCEPTION, GetLastError());
            __leave;
        }

        pSignedHashBuffer = new jbyte[dwBufLen];
        if (::CryptSignHash(hHash, dwKeySpec, NULL, NULL, (BYTE*)pSignedHashBuffer, &dwBufLen) == FALSE)
        {
            ThrowException(env, SIGNATURE_EXCEPTION, GetLastError());
            __leave;
        }

        // Create new byte array
        jbyteArray temp = env->NewByteArray(dwBufLen);

        // Copy data from native buffer
        env->SetByteArrayRegion(temp, 0, dwBufLen, pSignedHashBuffer);

        jSignedHash = temp;
    }
    __finally
    {
        if (pSignedHashBuffer)
            delete [] pSignedHashBuffer;

        if (pHashBuffer)
            delete [] pHashBuffer;

        if (hHash)
            ::CryptDestroyHash(hHash);
    }

    return jSignedHash;
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_RSASignature
 * Method:    verifySignedHash
 * Signature: ([BIL/java/lang/String;[BIJJ)Z
 */
JNIEXPORT jboolean JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_RSASignature_verifySignedHash
  (JNIEnv *env, jclass clazz, jbyteArray jHash, jint jHashSize,
        jstring jHashAlgorithm, jbyteArray jSignedHash, jint jSignedHashSize,
        jlong hCryptProv, jlong hCryptKey)
{
    HCRYPTHASH hHash = NULL;
    jbyte* pHashBuffer = NULL;
    jbyte* pSignedHashBuffer = NULL;
    DWORD dwSignedHashBufferLen = jSignedHashSize;
    jboolean result = JNI_FALSE;

    __try
    {
        // Map hash algorithm
        ALG_ID algId = MapHashAlgorithm(env, jHashAlgorithm);

        // Acquire a hash object handle.
        if (::CryptCreateHash(HCRYPTPROV(hCryptProv), algId, 0, 0, &hHash)
            == FALSE)
        {
            ThrowException(env, SIGNATURE_EXCEPTION, GetLastError());
            __leave;
        }

        // Copy hash and signedHash from Java to native buffer
        pHashBuffer = new jbyte[jHashSize];
        env->GetByteArrayRegion(jHash, 0, jHashSize, pHashBuffer);
        pSignedHashBuffer = new jbyte[jSignedHashSize];
        env->GetByteArrayRegion(jSignedHash, 0, jSignedHashSize,
            pSignedHashBuffer);

        // Set hash value in the hash object
        if (::CryptSetHashParam(hHash, HP_HASHVAL, (BYTE*) pHashBuffer, NULL)
            == FALSE)
        {
            ThrowException(env, SIGNATURE_EXCEPTION, GetLastError());
            __leave;
        }

        // For RSA, the hash encryption algorithm is normally the same as the
        // public key algorithm, so AT_SIGNATURE is used.

        // Verify the signature
        if (::CryptVerifySignatureA(hHash, (BYTE *) pSignedHashBuffer,
            dwSignedHashBufferLen, (HCRYPTKEY) hCryptKey, NULL, 0) == TRUE)
        {
            result = JNI_TRUE;
        }
    }

    __finally
    {
        if (pSignedHashBuffer)
            delete [] pSignedHashBuffer;

        if (pHashBuffer)
            delete [] pHashBuffer;

        if (hHash)
            ::CryptDestroyHash(hHash);
    }

    return result;
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_RSAKeyPairGenerator
 * Method:    generateRSAKeyPair
 * Signature: (ILjava/lang/String;)Les/mityc/javasign/pkstore/mscapi/mityc/RSAKeyPair;
 */
JNIEXPORT jobject JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_RSAKeyPairGenerator_generateRSAKeyPair
  (JNIEnv *env, jclass clazz, jint keySize, jstring keyContainerName)
{
    HCRYPTPROV hCryptProv = NULL;
    HCRYPTKEY hKeyPair;
    DWORD dwFlags = (keySize << 16) | CRYPT_EXPORTABLE;
    jobject keypair;
    const char* pszKeyContainerName = NULL; // UUID

    __try
    {
        pszKeyContainerName = env->GetStringUTFChars(keyContainerName, NULL);

        // Acquire a CSP context (create a new key container).
        if (::CryptAcquireContext(
            &hCryptProv,
            pszKeyContainerName,
            NULL,
            PROV_RSA_FULL,
            CRYPT_NEWKEYSET) == FALSE)
        {
            ThrowException(env, KEY_EXCEPTION, GetLastError());
            __leave;
        }

        // Generate an RSA keypair
        if(::CryptGenKey(
           hCryptProv,
           AT_KEYEXCHANGE,
           dwFlags,
           &hKeyPair) == FALSE)
        {
            ThrowException(env, KEY_EXCEPTION, GetLastError());
            __leave;
        }

        // Get the method ID for the RSAKeyPair constructor
        jclass clazzRSAKeyPair =
            env->FindClass("es/mityc/javasign/pkstore/mscapi/mityc/RSAKeyPair");

        jmethodID mNewRSAKeyPair =
            env->GetMethodID(clazzRSAKeyPair, "<init>", "(JJI)V");

        // Create a new RSA keypair
        keypair = env->NewObject(clazzRSAKeyPair, mNewRSAKeyPair,
            (jlong) hCryptProv, (jlong) hKeyPair, keySize);

    }
    __finally
    {
        //--------------------------------------------------------------------
        // Clean up.

        if (pszKeyContainerName)
            env->ReleaseStringUTFChars(keyContainerName, pszKeyContainerName);
    }

    return keypair;
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_Key
 * Method:    getContainerName
 * Signature: (J)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_Key_getContainerName
  (JNIEnv *env, jclass jclazz, jlong hCryptProv)
{
    DWORD cbData = 256;
    BYTE pbData[256];
    pbData[0] = '\0';

    ::CryptGetProvParam(
        (HCRYPTPROV)hCryptProv,
        PP_CONTAINER,
        (BYTE *)pbData,
        &cbData,
        0);

    return env->NewStringUTF((const char*)pbData);
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_Key
 * Method:    getKeyType
 * Signature: (J)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_Key_getKeyType
  (JNIEnv *env, jclass jclazz, jlong hCryptKey)
{
    ALG_ID dwAlgId;
    DWORD dwAlgIdLen = sizeof(ALG_ID);

    if (::CryptGetKeyParam((HCRYPTKEY) hCryptKey, KP_ALGID, (BYTE*)&dwAlgId, &dwAlgIdLen, 0)) {

        if (CALG_RSA_SIGN == dwAlgId) {
            return env->NewStringUTF("Signature");

        } else if (CALG_RSA_KEYX == dwAlgId) {
            return env->NewStringUTF("Exchange");

        } else {
            char buffer[64];
            if (sprintf(buffer, "%lu", dwAlgId)) {
                return env->NewStringUTF(buffer);
            }
        }
    }

    return env->NewStringUTF("<Unknown>");
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_KeyStore
 * Method:    storeCertificate
 * Signature: (Ljava/lang/String;Ljava/lang/String;[BIJJ)V
 */
JNIEXPORT void JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_KeyStore_storeCertificate
  (JNIEnv *env, jobject obj, jstring jCertStoreName, jstring jCertAliasName,
        jbyteArray jCertEncoding, jint jCertEncodingSize, jlong hCryptProv,
        jlong hCryptKey)
{
    const char* pszCertStoreName = NULL;
    HCERTSTORE hCertStore = NULL;
    PCCERT_CONTEXT pCertContext = NULL;
    PWCHAR pszCertAliasName = NULL;
    jbyte* pbCertEncoding = NULL;
    const jchar* jCertAliasChars = NULL;
    const char* pszContainerName = NULL;
    const char* pszProviderName = NULL;
    WCHAR * pwszContainerName = NULL;
    WCHAR * pwszProviderName = NULL;

    __try
    {
        // Open a system certificate store.
        pszCertStoreName = env->GetStringUTFChars(jCertStoreName, NULL);
        if ((hCertStore = OpenGenericStore(pszCertStoreName)) == NULL) {
            ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
            __leave;
        }

        // Copy encoding from Java to native buffer
        pbCertEncoding = new jbyte[jCertEncodingSize];
        env->GetByteArrayRegion(jCertEncoding, 0, jCertEncodingSize, pbCertEncoding);

        // Create a certificate context from the encoded cert
        if (!(pCertContext = ::CertCreateCertificateContext(X509_ASN_ENCODING,
            (BYTE*) pbCertEncoding, jCertEncodingSize))) {

            ThrowException(env, CERTIFICATE_PARSING_EXCEPTION, GetLastError());
            __leave;
        }

        // Set the certificate's friendly name
        int size = env->GetStringLength(jCertAliasName);
        pszCertAliasName = new WCHAR[size + 1];

        jCertAliasChars = env->GetStringChars(jCertAliasName, NULL);
        memcpy(pszCertAliasName, jCertAliasChars, size * sizeof(WCHAR));
        pszCertAliasName[size] = 0; // append the string terminator

        CRYPT_DATA_BLOB friendlyName = {
            sizeof(WCHAR) * (size + 1),
            (BYTE *) pszCertAliasName
        };

        env->ReleaseStringChars(jCertAliasName, jCertAliasChars);

        if (! ::CertSetCertificateContextProperty(pCertContext,
            CERT_FRIENDLY_NAME_PROP_ID, 0, &friendlyName)) {

            ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
            __leave;
        }

        // Attach the certificate's private key (if supplied)
        if (hCryptProv != 0 && hCryptKey != 0) {

            CRYPT_KEY_PROV_INFO keyProviderInfo;
            DWORD dwDataLen;

            // Get the name of the key container
            if (! ::CryptGetProvParam(
                (HCRYPTPROV) hCryptProv,
                PP_CONTAINER,
                NULL,
                &dwDataLen,
                0)) {

                ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
                __leave;
            }

            pszContainerName = new char[dwDataLen];

            if (! ::CryptGetProvParam(
                (HCRYPTPROV) hCryptProv,
                PP_CONTAINER,
                (BYTE *) pszContainerName,
                &dwDataLen,
                0)) {

                ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
                __leave;
            }

            // Convert to a wide char string
            pwszContainerName = new WCHAR[dwDataLen];

            if (mbstowcs(pwszContainerName, pszContainerName, dwDataLen) == 0) {
                ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
                __leave;
            }

            // Set the name of the key container
            keyProviderInfo.pwszContainerName = pwszContainerName;


            // Get the name of the provider
            if (! ::CryptGetProvParam(
                (HCRYPTPROV) hCryptProv,
                PP_NAME,
                NULL,
                &dwDataLen,
                0)) {

                ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
                __leave;
            }

            pszProviderName = new char[dwDataLen];

            if (! ::CryptGetProvParam(
                (HCRYPTPROV) hCryptProv,
                PP_NAME,
                (BYTE *) pszProviderName,
                &dwDataLen,
                0)) {

                ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
                __leave;
            }

            // Convert to a wide char string
            pwszProviderName = new WCHAR[dwDataLen];

            if (mbstowcs(pwszProviderName, pszProviderName, dwDataLen) == 0) {
                ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
                __leave;
            }

            // Set the name of the provider
            keyProviderInfo.pwszProvName = pwszProviderName;

            // Get and set the type of the provider
            if (! ::CryptGetProvParam(
                (HCRYPTPROV) hCryptProv,
                PP_PROVTYPE,
                (LPBYTE) &keyProviderInfo.dwProvType,
                &dwDataLen,
                0)) {

                ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
                __leave;
            }

            // Set no provider flags
            keyProviderInfo.dwFlags = 0;

            // Set no provider parameters
            keyProviderInfo.cProvParam = 0;
            keyProviderInfo.rgProvParam = NULL;

            // Get the key's algorithm ID
            if (! ::CryptGetKeyParam(
                (HCRYPTKEY) hCryptKey,
                KP_ALGID,
                (LPBYTE) &keyProviderInfo.dwKeySpec,
                &dwDataLen,
                0)) {

                ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
                __leave;
            }
            // Set the key spec (using the algorithm ID).
            switch (keyProviderInfo.dwKeySpec) {
            case CALG_RSA_KEYX:
            case CALG_DH_SF:
                keyProviderInfo.dwKeySpec = AT_KEYEXCHANGE;
                break;

            case CALG_RSA_SIGN:
            case CALG_DSS_SIGN:
                keyProviderInfo.dwKeySpec = AT_SIGNATURE;
                break;

            default:
                ThrowException(env, KEYSTORE_EXCEPTION, NTE_BAD_ALGID);
                __leave;
            }

            if (! ::CertSetCertificateContextProperty(pCertContext,
                CERT_KEY_PROV_INFO_PROP_ID, 0, &keyProviderInfo)) {

                ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
                __leave;
            }
        }

        // Import encoded certificate
        if (!::CertAddCertificateContextToStore(hCertStore, pCertContext,
            CERT_STORE_ADD_REPLACE_EXISTING, NULL))
        {
            ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
            __leave;
        }

    }
    __finally
    {
        //--------------------------------------------------------------------
        // Clean up.

        if (hCertStore)
            ::CertCloseStore(hCertStore, 0);

        if (pszCertStoreName)
            env->ReleaseStringUTFChars(jCertStoreName, pszCertStoreName);

        if (pbCertEncoding)
            delete [] pbCertEncoding;

        if (pszCertAliasName)
            delete [] pszCertAliasName;

        if (pszContainerName)
            delete [] pszContainerName;

        if (pwszContainerName)
            delete [] pwszContainerName;

        if (pszProviderName)
            delete [] pszProviderName;

        if (pwszProviderName)
            delete [] pwszProviderName;

        if (pCertContext)
            ::CertFreeCertificateContext(pCertContext);
    }
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_KeyStore
 * Method:    removeCertificate
 * Signature: (Ljava/lang/String;Ljava/lang/String;[BI)V
 */
JNIEXPORT void JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_KeyStore_removeCertificate
  (JNIEnv *env, jobject obj, jstring jCertStoreName, jstring jCertAliasName,
  jbyteArray jCertEncoding, jint jCertEncodingSize) {

    const char* pszCertStoreName = NULL;
    const char* pszCertAliasName = NULL;
    HCERTSTORE hCertStore = NULL;
    PCCERT_CONTEXT pCertContext = NULL;
    PCCERT_CONTEXT pTBDCertContext = NULL;
    jbyte* pbCertEncoding = NULL;
    DWORD cchNameString = 0;
    char* pszNameString = NULL; // certificate's friendly name
    BOOL bDeleteAttempted = FALSE;

    __try
    {
        // Open a system certificate store.
        pszCertStoreName = env->GetStringUTFChars(jCertStoreName, NULL);
        if ((hCertStore = OpenGenericStore(pszCertStoreName)) == NULL) {
            ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
            __leave;
        }

        // Copy encoding from Java to native buffer
        pbCertEncoding = new jbyte[jCertEncodingSize];
        env->GetByteArrayRegion(jCertEncoding, 0, jCertEncodingSize, pbCertEncoding);

        // Create a certificate context from the encoded cert
        if (!(pCertContext = ::CertCreateCertificateContext(X509_ASN_ENCODING,
            (BYTE*) pbCertEncoding, jCertEncodingSize))) {

            ThrowException(env, CERTIFICATE_PARSING_EXCEPTION, GetLastError());
            __leave;
        }

        // Find the certificate to be deleted
        if (!(pTBDCertContext = ::CertFindCertificateInStore(hCertStore,
            X509_ASN_ENCODING, 0, CERT_FIND_EXISTING, pCertContext, NULL))) {

            ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
            __leave;
        }

        // Check that its friendly name matches the supplied alias
        if ((cchNameString = ::CertGetNameString(pTBDCertContext,
                CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, NULL, 0)) > 1) {

            pszNameString = new char[cchNameString];

            ::CertGetNameString(pTBDCertContext,
                CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, pszNameString,
                cchNameString);

            // Compare the certificate's friendly name with supplied alias name
            pszCertAliasName = env->GetStringUTFChars(jCertAliasName, NULL);
            if (strcmp(pszCertAliasName, pszNameString) == 0) {

                // Only delete the certificate if the alias names matches
                if (! ::CertDeleteCertificateFromStore(pTBDCertContext)) {

                    // pTBDCertContext is always freed by the
                    //  CertDeleteCertificateFromStore method
                    bDeleteAttempted = TRUE;

                    ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
                    __leave;
                }
            }
        }

    }
    __finally
    {
        //--------------------------------------------------------------------
        // Clean up.

        if (hCertStore)
            ::CertCloseStore(hCertStore, 0);

        if (pszCertStoreName)
            env->ReleaseStringUTFChars(jCertStoreName, pszCertStoreName);

        if (pszCertAliasName)
            env->ReleaseStringUTFChars(jCertAliasName, pszCertAliasName);

        if (pbCertEncoding)
            delete [] pbCertEncoding;

        if (pszNameString)
            delete [] pszNameString;

        if (pCertContext)
            ::CertFreeCertificateContext(pCertContext);

        if (bDeleteAttempted && pTBDCertContext)
            ::CertFreeCertificateContext(pTBDCertContext);
    }
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_KeyStore
 * Method:    destroyKeyContainer
 * Signature: (Ljava/lang/String;)V
 */
JNIEXPORT void JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_KeyStore_destroyKeyContainer
  (JNIEnv *env, jclass clazz, jstring keyContainerName)
{
    HCRYPTPROV hCryptProv = NULL;
    const char* pszKeyContainerName = NULL;

    __try
    {
        pszKeyContainerName = env->GetStringUTFChars(keyContainerName, NULL);

        // Destroying the default key container is not permitted
        // (because it may contain more one keypair).
        if (pszKeyContainerName == NULL) {

            ThrowException(env, KEYSTORE_EXCEPTION, NTE_BAD_KEYSET_PARAM);
            __leave;
        }

        // Acquire a CSP context (to the key container).
        if (::CryptAcquireContext(
            &hCryptProv,
            pszKeyContainerName,
            NULL,
            PROV_RSA_FULL,
            CRYPT_DELETEKEYSET) == FALSE)
        {
            ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
            __leave;
        }

    }
    __finally
    {
        //--------------------------------------------------------------------
        // Clean up.

        if (pszKeyContainerName)
            env->ReleaseStringUTFChars(keyContainerName, pszKeyContainerName);
    }
}




/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_RSACipher
 * Method:    findCertificateUsingAlias
 * Signature: (Ljava/lang/String;Ljava/lang/String;)J
 */
JNIEXPORT jlong JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_RSACipher_findCertificateUsingAlias
  (JNIEnv *env, jobject obj, jstring jCertStoreName, jstring jCertAliasName)
{
    const char* pszCertStoreName = NULL;
    const char* pszCertAliasName = NULL;
    HCERTSTORE hCertStore = NULL;
    PCCERT_CONTEXT pCertContext = NULL;
    char* pszNameString = NULL; // certificate's friendly name
    DWORD cchNameString = 0;

    __try
    {
        pszCertStoreName = env->GetStringUTFChars(jCertStoreName, NULL);
        pszCertAliasName = env->GetStringUTFChars(jCertAliasName, NULL);

        // Open a system certificate store.
        if ((hCertStore = OpenGenericStore(pszCertStoreName)) == NULL) {
            ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
            __leave;
        }

        // Use CertEnumCertificatesInStore to get the certificates
        // from the open store. pCertContext must be reset to
        // NULL to retrieve the first certificate in the store.
        while (pCertContext = ::CertEnumCertificatesInStore(hCertStore, pCertContext))
        {
            if ((cchNameString = ::CertGetNameString(pCertContext,
                CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, NULL, 0)) == 1) {

                continue; // not found
            }

            pszNameString = new char[cchNameString];

            if (::CertGetNameString(pCertContext,
                CERT_NAME_FRIENDLY_DISPLAY_TYPE, 0, NULL, pszNameString,
                cchNameString) == 1) {

                continue; // not found
            }

            // Compare the certificate's friendly name with supplied alias name
            if (strcmp(pszCertAliasName, pszNameString) == 0) {
                delete [] pszNameString;
                break;

            } else {
                delete [] pszNameString;
            }
        }
    }
    __finally
    {
        if (hCertStore)
            ::CertCloseStore(hCertStore, 0);

        if (pszCertStoreName)
            env->ReleaseStringUTFChars(jCertStoreName, pszCertStoreName);

        if (pszCertAliasName)
            env->ReleaseStringUTFChars(jCertAliasName, pszCertAliasName);
    }

    return (jlong) pCertContext;
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_RSACipher
 * Method:    getKeyFromCert
 * Signature: (JZ)J
 */
JNIEXPORT jlong JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_RSACipher_getKeyFromCert
  (JNIEnv *env, jobject obj, jlong pCertContext, jboolean usePrivateKey)
{
    HCRYPTPROV hCryptProv = NULL;
    HCRYPTKEY hKey = NULL;
    DWORD dwKeySpec;

    __try
    {
        if (usePrivateKey == JNI_TRUE) {
            // Locate the key container for the certificate's private key
            if (!(::CryptAcquireCertificatePrivateKey(
                (PCCERT_CONTEXT) pCertContext, 0, NULL, &hCryptProv,
                &dwKeySpec, NULL))) {

                ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
                __leave;
            }

            // Get a handle to the private key
            if (!(::CryptGetUserKey(hCryptProv, dwKeySpec, &hKey))) {
                ThrowException(env, KEY_EXCEPTION, GetLastError());
                __leave;
            }

        } else { // use public key

            //  Acquire a CSP context.
            if(::CryptAcquireContext(
               &hCryptProv,
               "J2SE",
               NULL,
               PROV_RSA_FULL,
               0) == FALSE)
            {
                // If CSP context hasn't been created, create one.
                //
                if (::CryptAcquireContext(
                    &hCryptProv,
                    "J2SE",
                    NULL,
                    PROV_RSA_FULL,
                    CRYPT_NEWKEYSET) == FALSE)
                {
                    ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
                    __leave;
                }
            }

            // Import the certificate's public key into the key container
            if (!(::CryptImportPublicKeyInfo(hCryptProv, X509_ASN_ENCODING,
                &(((PCCERT_CONTEXT) pCertContext)->pCertInfo->SubjectPublicKeyInfo),
                &hKey))) {

                ThrowException(env, KEY_EXCEPTION, GetLastError());
                __leave;
            }
        }
    }
    __finally
    {
        //--------------------------------------------------------------------
        // Clean up.

        if (hCryptProv)
            ::CryptReleaseContext(hCryptProv, 0);
    }

    return hKey;        // TODO - when finished with this key, call
                        //              CryptDestroyKey(hKey)
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_KeyStore
 * Method:    getKeyLength
 * Signature: (J)I
 */
JNIEXPORT jint JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_KeyStore_getKeyLength
  (JNIEnv *env, jobject obj, jlong hKey)
{
    DWORD dwDataLen = sizeof(DWORD);
    BYTE pbData[sizeof(DWORD)];
    DWORD length = 0;

    __try
    {
        // Get key length (in bits)
        //TODO - may need to use KP_BLOCKLEN instead?
        if (!(::CryptGetKeyParam((HCRYPTKEY) hKey, KP_KEYLEN, (BYTE *)pbData, &dwDataLen,
            0))) {

            ThrowException(env, KEY_EXCEPTION, GetLastError());
            __leave;
        }
        length = (DWORD) pbData;
    }
    __finally
    {
        // no cleanup required
    }

    return (jint) length;
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_RSACipher
 * Method:    encryptDecrypt
 * Signature: ([BIJZ)[B
 */
JNIEXPORT jbyteArray JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_RSACipher_encryptDecrypt
  (JNIEnv *env, jclass clazz, jbyteArray jData, jint jDataSize, jlong hKey,
   jboolean doEncrypt)
{
    jbyteArray result = NULL;
    jbyte* pData = NULL;
    DWORD dwDataLen = jDataSize;
    DWORD dwBufLen = env->GetArrayLength(jData);
    DWORD i;
    BYTE tmp;

    __try
    {
        // Copy data from Java buffer to native buffer
        pData = new jbyte[dwBufLen];
        env->GetByteArrayRegion(jData, 0, dwBufLen, pData);

        if (doEncrypt == JNI_TRUE) {
            // encrypt
            if (! ::CryptEncrypt((HCRYPTKEY) hKey, 0, TRUE, 0, (BYTE *)pData,
                &dwDataLen, dwBufLen)) {

                ThrowException(env, KEY_EXCEPTION, GetLastError());
                __leave;
            }
            dwBufLen = dwDataLen;

            // convert from little-endian
            for (i = 0; i < dwBufLen / 2; i++) {
                tmp = pData[i];
                pData[i] = pData[dwBufLen - i -1];
                pData[dwBufLen - i - 1] = tmp;
            }
        } else {
            // convert to little-endian
            for (i = 0; i < dwBufLen / 2; i++) {
                tmp = pData[i];
                pData[i] = pData[dwBufLen - i -1];
                pData[dwBufLen - i - 1] = tmp;
            }

            // decrypt
            if (! ::CryptDecrypt((HCRYPTKEY) hKey, 0, TRUE, 0, (BYTE *)pData,
                &dwBufLen)) {

                ThrowException(env, KEY_EXCEPTION, GetLastError());
                __leave;
            }
        }

        // Create new byte array
        result = env->NewByteArray(dwBufLen);

        // Copy data from native buffer to Java buffer
        env->SetByteArrayRegion(result, 0, dwBufLen, (jbyte*) pData);
    }
    __finally
    {
        if (pData)
            delete [] pData;
    }

    return result;
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_RSAPublicKey
 * Method:    getPublicKeyBlob
 * Signature: (J)[B
 */
JNIEXPORT jbyteArray JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_RSAPublicKey_getPublicKeyBlob
    (JNIEnv *env, jclass clazz, jlong hCryptKey) {

    jbyteArray blob = NULL;
    DWORD dwBlobLen;
    BYTE* pbKeyBlob;

    __try
    {

        // Determine the size of the blob
        if (! ::CryptExportKey((HCRYPTKEY) hCryptKey, 0, PUBLICKEYBLOB, 0, NULL,
            &dwBlobLen)) {

            ThrowException(env, KEY_EXCEPTION, GetLastError());
            __leave;
        }

        pbKeyBlob = new BYTE[dwBlobLen];

        // Generate key blob
        if (! ::CryptExportKey((HCRYPTKEY) hCryptKey, 0, PUBLICKEYBLOB, 0,
            pbKeyBlob, &dwBlobLen)) {

            ThrowException(env, KEY_EXCEPTION, GetLastError());
            __leave;
        }

        // Create new byte array
        blob = env->NewByteArray(dwBlobLen);

        // Copy data from native buffer to Java buffer
        env->SetByteArrayRegion(blob, 0, dwBlobLen, (jbyte*) pbKeyBlob);
    }
    __finally
    {
        if (pbKeyBlob)
            delete [] pbKeyBlob;
    }

    return blob;
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_RSAPublicKey
 * Method:    getExponent
 * Signature: ([B)[B
 */
JNIEXPORT jbyteArray JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_RSAPublicKey_getExponent
    (JNIEnv *env, jclass clazz, jbyteArray jKeyBlob) {

    jbyteArray exponent = NULL;
    jbyte*     exponentBytes = NULL;
    jbyte*     keyBlob = NULL;

    __try {

        jsize length = env->GetArrayLength(jKeyBlob);
        keyBlob = env->GetByteArrayElements(jKeyBlob, 0);

        PUBLICKEYSTRUC* pPublicKeyStruc = (PUBLICKEYSTRUC *) keyBlob;

        // Check BLOB type
        if (pPublicKeyStruc->bType != PUBLICKEYBLOB) {
            ThrowException(env, KEY_EXCEPTION, NTE_BAD_TYPE);
            __leave;
        }

        RSAPUBKEY* pRsaPubKey =
            (RSAPUBKEY *) (keyBlob + sizeof(PUBLICKEYSTRUC));
        int len = sizeof(pRsaPubKey->pubexp);
        exponentBytes = new jbyte[len];

        // convert from little-endian while copying from blob
        for (int i = 0, j = len - 1; i < len; i++, j--) {
            exponentBytes[i] = ((BYTE*) &pRsaPubKey->pubexp)[j];
        }

        exponent = env->NewByteArray(len);
        env->SetByteArrayRegion(exponent, 0, len, exponentBytes);
    }
    __finally
    {
        if (keyBlob)
            env->ReleaseByteArrayElements(jKeyBlob, keyBlob, JNI_ABORT);

        if (exponentBytes)
            delete [] exponentBytes;
    }

    return exponent;
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_RSAPublicKey
 * Method:    getModulus
 * Signature: ([B)[B
 */
JNIEXPORT jbyteArray JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_RSAPublicKey_getModulus
    (JNIEnv *env, jclass clazz, jbyteArray jKeyBlob) {

    jbyteArray modulus = NULL;
    jbyte*     modulusBytes = NULL;
    jbyte*     keyBlob = NULL;

    __try {

        jsize length = env->GetArrayLength(jKeyBlob);
        keyBlob = env->GetByteArrayElements(jKeyBlob, 0);

        PUBLICKEYSTRUC* pPublicKeyStruc = (PUBLICKEYSTRUC *) keyBlob;

        // Check BLOB type
        if (pPublicKeyStruc->bType != PUBLICKEYBLOB) {
            ThrowException(env, KEY_EXCEPTION, NTE_BAD_TYPE);
            __leave;
        }

        RSAPUBKEY* pRsaPubKey =
            (RSAPUBKEY *) (keyBlob + sizeof(PUBLICKEYSTRUC));
        int len = pRsaPubKey->bitlen / 8;

        modulusBytes = new jbyte[len];
        BYTE * pbModulus =
            (BYTE *) (keyBlob + sizeof(PUBLICKEYSTRUC) + sizeof(RSAPUBKEY));

        // convert from little-endian while copying from blob
        for (int i = 0, j = len - 1; i < len; i++, j--) {
            modulusBytes[i] = pbModulus[j];
        }

        modulus = env->NewByteArray(len);
        env->SetByteArrayRegion(modulus, 0, len, modulusBytes);
    }
    __finally
    {
        if (keyBlob)
            env->ReleaseByteArrayElements(jKeyBlob, keyBlob, JNI_ABORT);

        if (modulusBytes)
            delete [] modulusBytes;
    }

    return modulus;
}

/*
 * Convert an array in big-endian byte order into little-endian byte order.
 */
int convertToLittleEndian(JNIEnv *env, jbyteArray source, jbyte* destination,
    int destinationLength) {

    int count = 0;
    int sourceLength = env->GetArrayLength(source);

    if (sourceLength < destinationLength) {
        return -1;
    }

    jbyte* sourceBytes = env->GetByteArrayElements(source, 0);

    // Copy bytes from the end of the source array to the beginning of the
    // destination array (until the destination array is full).
    // This ensures that the sign byte from the source array will be excluded.
    for (int i = 0; i < destinationLength; i++) {
        destination[i] = sourceBytes[sourceLength - i - 1];
        count++;
    }
    if (sourceBytes)
        env->ReleaseByteArrayElements(source, sourceBytes, JNI_ABORT);

    return count;
}

/*
 * The Microsoft Base Cryptographic Provider supports public-key BLOBs
 * that have the following format:
 *
 *     PUBLICKEYSTRUC publickeystruc;
 *     RSAPUBKEY rsapubkey;
 *     BYTE modulus[rsapubkey.bitlen/8];
 *
 * and private-key BLOBs that have the following format:
 *
 *     PUBLICKEYSTRUC publickeystruc;
 *     RSAPUBKEY rsapubkey;
 *     BYTE modulus[rsapubkey.bitlen/8];
 *     BYTE prime1[rsapubkey.bitlen/16];
 *     BYTE prime2[rsapubkey.bitlen/16];
 *     BYTE exponent1[rsapubkey.bitlen/16];
 *     BYTE exponent2[rsapubkey.bitlen/16];
 *     BYTE coefficient[rsapubkey.bitlen/16];
 *     BYTE privateExponent[rsapubkey.bitlen/8];
 *
 * This method generates such BLOBs from the key elements supplied.
 */
jbyteArray generateKeyBlob(
        JNIEnv *env,
        jint jKeyBitLength,
        jbyteArray jModulus,
        jbyteArray jPublicExponent,
        jbyteArray jPrivateExponent,
        jbyteArray jPrimeP,
        jbyteArray jPrimeQ,
        jbyteArray jExponentP,
        jbyteArray jExponentQ,
        jbyteArray jCrtCoefficient)
{
    jsize jKeyByteLength = jKeyBitLength / 8;
    jsize jBlobLength;
    BOOL bGeneratePrivateKeyBlob;

    // Determine whether to generate a public-key or a private-key BLOB
    if (jPrivateExponent != NULL &&
        jPrimeP != NULL &&
        jPrimeQ != NULL &&
        jExponentP != NULL &&
        jExponentQ != NULL &&
        jCrtCoefficient != NULL) {

        bGeneratePrivateKeyBlob = TRUE;
        jBlobLength = sizeof(BLOBHEADER) +
                        sizeof(RSAPUBKEY) +
                        ((jKeyBitLength / 8) * 4) +
                        (jKeyBitLength / 16);

    } else {
        bGeneratePrivateKeyBlob = FALSE;
        jBlobLength = sizeof(BLOBHEADER) +
                        sizeof(RSAPUBKEY) +
                        (jKeyBitLength / 8);
    }

    jbyte* jBlobBytes = new jbyte[jBlobLength];
    jbyte* jBlobElement;
    jbyteArray jBlob = NULL;
    jsize  jElementLength;

    __try {

        BLOBHEADER *pBlobHeader = (BLOBHEADER *) jBlobBytes;
        if (bGeneratePrivateKeyBlob) {
            pBlobHeader->bType = PRIVATEKEYBLOB;  // 0x07
        } else {
            pBlobHeader->bType = PUBLICKEYBLOB;   // 0x06
        }
        pBlobHeader->bVersion = CUR_BLOB_VERSION; // 0x02
        pBlobHeader->reserved = 0;                // 0x0000
        pBlobHeader->aiKeyAlg = CALG_RSA_KEYX;    // 0x0000a400

        RSAPUBKEY *pRsaPubKey =
            (RSAPUBKEY *) (jBlobBytes + sizeof(PUBLICKEYSTRUC));
        if (bGeneratePrivateKeyBlob) {
            pRsaPubKey->magic = 0x32415352;       // "RSA2"
        } else {
            pRsaPubKey->magic = 0x31415352;       // "RSA1"
        }
        pRsaPubKey->bitlen = jKeyBitLength;
        pRsaPubKey->pubexp = 0; // init

        // Sanity check
        jsize jPublicExponentLength = env->GetArrayLength(jPublicExponent);
        if (jPublicExponentLength > sizeof(pRsaPubKey->pubexp)) {
            ThrowException(env, KEY_EXCEPTION, NTE_BAD_TYPE);
            __leave;
        }
        // The length argument must be the smaller of jPublicExponentLength
        // and sizeof(pRsaPubKey->pubkey)
        convertToLittleEndian(env, jPublicExponent,
            (jbyte *) &(pRsaPubKey->pubexp), jPublicExponentLength);

        // Modulus n
        jBlobElement =
            (jbyte *) (jBlobBytes + sizeof(PUBLICKEYSTRUC) + sizeof(RSAPUBKEY));
        jElementLength = convertToLittleEndian(env, jModulus, jBlobElement,
            jKeyByteLength);

        if (bGeneratePrivateKeyBlob) {
            // Prime p
            jBlobElement += jElementLength;
            jElementLength = convertToLittleEndian(env, jPrimeP, jBlobElement,
                jKeyByteLength / 2);

            // Prime q
            jBlobElement += jElementLength;
            jElementLength = convertToLittleEndian(env, jPrimeQ, jBlobElement,
                jKeyByteLength / 2);

            // Prime exponent p
            jBlobElement += jElementLength;
            jElementLength = convertToLittleEndian(env, jExponentP,
                jBlobElement, jKeyByteLength / 2);

            // Prime exponent q
            jBlobElement += jElementLength;
            jElementLength = convertToLittleEndian(env, jExponentQ,
                jBlobElement, jKeyByteLength / 2);

            // CRT coefficient
            jBlobElement += jElementLength;
            jElementLength = convertToLittleEndian(env, jCrtCoefficient,
                jBlobElement, jKeyByteLength / 2);

            // Private exponent
            jBlobElement += jElementLength;
            convertToLittleEndian(env, jPrivateExponent, jBlobElement,
                jKeyByteLength);
        }

        jBlob = env->NewByteArray(jBlobLength);
        env->SetByteArrayRegion(jBlob, 0, jBlobLength, jBlobBytes);

    }
    __finally
    {
        if (jBlobBytes)
            delete [] jBlobBytes;
    }

    return jBlob;
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_KeyStore
 * Method:    generatePrivateKeyBlob
 * Signature: (I[B[B[B[B[B[B[B[B)[B
 */
JNIEXPORT jbyteArray JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_KeyStore_generatePrivateKeyBlob
    (JNIEnv *env, jclass clazz,
        jint jKeyBitLength,
        jbyteArray jModulus,
        jbyteArray jPublicExponent,
        jbyteArray jPrivateExponent,
        jbyteArray jPrimeP,
        jbyteArray jPrimeQ,
        jbyteArray jExponentP,
        jbyteArray jExponentQ,
        jbyteArray jCrtCoefficient)
{
    return generateKeyBlob(env, jKeyBitLength, jModulus, jPublicExponent,
        jPrivateExponent, jPrimeP, jPrimeQ, jExponentP, jExponentQ,
        jCrtCoefficient);
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_RSASignature
 * Method:    generatePublicKeyBlob
 * Signature: (I[B[B)[B
 */
JNIEXPORT jbyteArray JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_RSASignature_generatePublicKeyBlob
    (JNIEnv *env, jclass clazz,
        jint jKeyBitLength,
        jbyteArray jModulus,
        jbyteArray jPublicExponent)
{
    return generateKeyBlob(env, jKeyBitLength, jModulus, jPublicExponent,
        NULL, NULL, NULL, NULL, NULL, NULL);
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_KeyStore
 * Method:    storePrivateKey
 * Signature: ([BLjava/lang/String;I)Les/mityc/javasign/pkstore/mscapi/mityc/RSAPrivateKey;
 */
JNIEXPORT jobject JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_KeyStore_storePrivateKey
    (JNIEnv *env, jclass clazz, jbyteArray keyBlob, jstring keyContainerName,
     jint keySize)
{
    HCRYPTPROV hCryptProv = NULL;
    HCRYPTKEY hKey = NULL;
    DWORD dwBlobLen;
    BYTE * pbKeyBlob = NULL;
    const char* pszKeyContainerName = NULL; // UUID
    jobject privateKey = NULL;

    __try
    {
        pszKeyContainerName = env->GetStringUTFChars(keyContainerName, NULL);
        dwBlobLen = env->GetArrayLength(keyBlob);
        pbKeyBlob = (BYTE *) env->GetByteArrayElements(keyBlob, 0);

        // Acquire a CSP context (create a new key container).
        if (::CryptAcquireContext(
            &hCryptProv,
            pszKeyContainerName,
            NULL,
            PROV_RSA_FULL,
            CRYPT_NEWKEYSET) == FALSE)
        {
            ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
            __leave;
        }

        // Import the private key
        if (::CryptImportKey(
            hCryptProv,
            pbKeyBlob,
            dwBlobLen,
            0,
            CRYPT_EXPORTABLE,
            &hKey) == FALSE)
        {
            ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
            __leave;
        }

        // Get the method ID for the RSAPrivateKey constructor
        jclass clazzRSAPrivateKey =
            env->FindClass("es/mityc/javasign/pkstore/mscapi/mityc/RSAPrivateKey");

        jmethodID mNewRSAPrivateKey =
            env->GetMethodID(clazzRSAPrivateKey, "<init>", "(JJI)V");

        // Create a new RSA private key
        privateKey = env->NewObject(clazzRSAPrivateKey, mNewRSAPrivateKey,
            (jlong) hCryptProv, (jlong) hKey, keySize);

    }
    __finally
    {
        //--------------------------------------------------------------------
        // Clean up.

        if (pszKeyContainerName)
            env->ReleaseStringUTFChars(keyContainerName, pszKeyContainerName);

        if (pbKeyBlob)
            env->ReleaseByteArrayElements(keyBlob, (jbyte *) pbKeyBlob,
                JNI_ABORT);
    }

    return privateKey;
}

/*
 * Class:     es_mityc_javasign_pkstore_mscapi_mityc_RSASignature
 * Method:    importPublicKey
 * Signature: ([BI)Les/mityc/javasign/pkstore/mscapi/mityc/RSAPublicKey;
 */
JNIEXPORT jobject JNICALL Java_es_mityc_javasign_pkstore_mscapi_mityc_RSASignature_importPublicKey
    (JNIEnv *env, jclass clazz, jbyteArray keyBlob, jint keySize)
{
    HCRYPTPROV hCryptProv = NULL;
    HCRYPTKEY hKey = NULL;
    DWORD dwBlobLen;
    BYTE * pbKeyBlob = NULL;
    jobject publicKey = NULL;

    __try
    {
        dwBlobLen = env->GetArrayLength(keyBlob);
        pbKeyBlob = (BYTE *) env->GetByteArrayElements(keyBlob, 0);

        // Acquire a CSP context (create a new key container).
        if (::CryptAcquireContext(
            &hCryptProv,
            NULL,
            NULL,
            PROV_RSA_FULL,
            CRYPT_VERIFYCONTEXT) == FALSE)
        {
            ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
            __leave;
        }

        // Import the public key
        if (::CryptImportKey(
            hCryptProv,
            pbKeyBlob,
            dwBlobLen,
            0,
            CRYPT_EXPORTABLE,
            &hKey) == FALSE)
        {
            ThrowException(env, KEYSTORE_EXCEPTION, GetLastError());
            __leave;
        }

        // Get the method ID for the RSAPublicKey constructor
        jclass clazzRSAPublicKey =
            env->FindClass("es/mityc/javasign/pkstore/mscapi/mityc/RSAPublicKey");

        jmethodID mNewRSAPublicKey =
            env->GetMethodID(clazzRSAPublicKey, "<init>", "(JJI)V");

        // Create a new RSA public key
        publicKey = env->NewObject(clazzRSAPublicKey, mNewRSAPublicKey,
            (jlong) hCryptProv, (jlong) hKey, keySize);

    }
    __finally
    {
        //--------------------------------------------------------------------
        // Clean up.

        if (pbKeyBlob)
            env->ReleaseByteArrayElements(keyBlob, (jbyte *) pbKeyBlob,
                JNI_ABORT);
    }

    return publicKey;
}

} /* extern "C" */
