sara
sara

Reputation: 3934

NCryptExportKey fails with NTE_NOT_SUPPORTED

I have a code for importing a private key of ECC cert (below)

The code is working fine on a cert generated by the following powershell script:

# Define certificate properties
$certProps = @{
    Subject = "CN=GSA_ECC.com"
    KeyAlgorithm = "ECDSA_P256"
    KeyLength = 256
    CertStoreLocation = "Cert:\LocalMachine\My"
    FriendlyName = "MyCertificate"
    KeyExportPolicy="Exportable" `
}

# Create a self-signed certificate
New-SelfSignedCertificate @certProps

However, when using this code for the certificate I generate and install using OpenSSl lib (code below), the code fails with NTE_NOT_SUPPORTED.

This is the code used to install the cert using openssl and windows APIs (error handling ommited here for making this shorter):

// Create a PKCS#12 container
PKCS12* p12 = PKCS12_create("Password", "Friendly Name", pkey, cert, NULL, 0, 0, 0, 0, 0);
// Convert PKCS#12 container to DER format
unsigned char* der_data = NULL;
int der_len = i2d_PKCS12(p12, &der_data);

// Prepare CRYPT_DATA_BLOB
CRYPT_DATA_BLOB pfx_blob;
pfx_blob.cbData = der_len;
pfx_blob.pbData = der_data;

// Import the PKCS#12 container into a temporary store
HCERTSTORE hTempStore = PFXImportCertStore(&pfx_blob, L"Password", CRYPT_EXPORTABLE | PKCS12_ALLOW_OVERWRITE_KEY);


// Open the target certificate store
// Open the "MY" certificate store in the Local Machine context
HCERTSTORE hStore = CertOpenStore(
    CERT_STORE_PROV_SYSTEM,
    0,
    NULL,
    CERT_SYSTEM_STORE_LOCAL_MACHINE,
    L"MY"
);
  
PCCERT_CONTEXT ctx = CertEnumCertificatesInStore(hTempStore, NULL);

auto res = CertAddCertificateContextToStore(hStore, ctx, CERT_STORE_ADD_REPLACE_EXISTING, NULL);

This is the code that attempts to load the private key:

    if (strcmp(_pCertContext->pCertInfo->SignatureAlgorithm.pszObjId, szOID_ECDSA_SHA256))
    {
        throw std::runtime_error("Unsupported signature algorithm for private key extraction");
    }

    BOOL result = CryptAcquireCertificatePrivateKey(
            _pCertContext,
            CRYPT_ACQUIRE_ALLOW_NCRYPT_KEY_FLAG,
            NULL,
            &key,
            &dwKeySpec,
            &freeKey
            );

    // error if result = 0

    if (dwKeySpec == CERT_NCRYPT_KEY_SPEC)
    {
                DWORD keySize = 0;

                **// Determine the size of the output buffer - This fails!**
                auto result = NCryptExportKey(key, NULL, BCRYPT_ECCPRIVATE_BLOB, NULL, NULL, 0, &keySize, 0);
    }

Looking at the cert using certutil I can see this:

Private key is NOT plain text exportable

Upvotes: 0

Views: 232

Answers (1)

bartonjs
bartonjs

Reputation: 33266

PFXImportCertStore treats CRYPT_EXPORTABLE as the CNG flag NCRYPT_ALLOW_EXPORT_FLAG; but exporting in the manner you have requested requires the stronger NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG.

Without the plaintext export allowed, you can only export into an encrypted form, like encrypted PKCS8. https://stackoverflow.com/a/55249105/6535399 shows how to do that using P/Invokes from C#, but it should be easily translated back down to C.

You might also find it efficacious to change the key export policy. If it's still fungible after import (I don't remember if it is, or isn't, or if it depends on what version of Windows you're on), you can grab the key handle and change the NCRYPT_EXPORT_POLICY_PROPERTY to assert both NCRYPT_ALLOW_EXPORT_FLAG and NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG. If you can do this it at all, it would only before the first call that accesses the key data (signing, verifying, encrypting, decrypting, or exporting).

Upvotes: 3

Related Questions