locked
Why does NCryptExportKey return NTE_NOT_SUPPORTED ? [CryptoAPI & Crypto NG question] RRS feed

  • Question

  • Anybody able to extract an elliptic curve key from a .p12?

    I get 0x80090029L (NTE_NOT_SUPPORTED ) returned from NCryptExportKey whose documentation  https://msdn.microsoft.com/en-us/library/windows/desktop/aa376263(v=vs.85).aspx does not help.

    I don't understand why.  The most likely would be that it is forbidden to export a private key, but I originally called PFXImportCertStore with CRYPT_EXPORTABLE to avoid that issue.  (And that worked with CryptoAPI.  Now we are trying NCRYPT because of the elliptic curve key. )

    retContext is a certificate which was obtained from a .p12 created by openSSL with an elliptic curve private key.

    CryptAcquireCertificatePrivateKey  works and sets hKeyhandle to 0x005520e8

    HCRYPTPROV_OR_NCRYPT_KEY_HANDLE hKeyhandle;
    DWORD dwKeySpec;	BOOL callerFreeProv;
    
    if (::CryptAcquireCertificatePrivateKey(retContext, CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG, NULL, &hKeyhandle, &dwKeySpec, &callerFreeProv)) {
      if (CERT_NCRYPT_KEY_SPEC == dwKeySpec) {
      DWORD dwFlags = 0;
      auto pszBlobType = BCRYPT_PRIVATE_KEY_BLOB; //  BCRYPT_ECCPRIVATE_BLOB;
      DWORD cbBlob= 0;
      auto lastSecStatus = ::NCryptExportKey(hKeyhandle, NULL, pszBlobType, NULL,
    	NULL,		// Ask for the 
    	0,		// correct size
    	&cbBlob,	// to be retuned here.
    	dwFlags);
    if (0 != lastSecStatus) winerr_category::throwWinErr(lastSecStatus, "NCryptExportKey");

    I'm using Windows 7.

    This is how I made the .p12:

    SET ossl=\Openssl-Win32\bin\openssl.exe
    %ossl% ecparam -name prime256v1 -genkey -out ec.key -text
    %ossl% req -new -x509 -nodes -subject -days 3650 -key ec.key -out ec.cer 
    %ossl% pkcs12 -keypbe PBE-SHA1-3DES -certpbe PBE-SHA1-3DES -export -in ec.cer -inkey ec.key -out ec.p12 -name "Example"

    Thanks in advance...

    I also get a NTE_NOT_SUPPORTED on NCryptGetProperty for NCRYPT_BLOCK_LENGTH_PROPERTY.

    I get a 1 for NCryptGetProperty using

    NCRYPT_EXPORT_POLICY_PROPERTY

    I guess that means I'm not allowed to export in plaintext.

    NCRYPT_ALLOW_EXPORT_FLAG 0x00000001 The private key can be exported.
    NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG 0x00000002 The private key can be exported in plaintext form.





    • Edited by Andrew7Webb Thursday, June 25, 2015 1:13 AM more info
    • Moved by Shu 2017 Friday, June 26, 2015 2:15 AM better forum
    Wednesday, June 24, 2015 9:10 PM

Answers

  • I made some progress using a different approach in fetchKeyAndCertFromP12. I'm not sure it works on all possible P12s. I did test on a RSA and ECC P12.

    // Use a Window system call to display a Windows specific error std::string errMessage(int win32Err) { std::stringstream errmsg; errmsg << " " << std::hex << win32Err << std::dec << ": "; return errmsg.str(); static HMODULE hModuleNTDll = LoadLibrary(L"NTDLL.DLL"); LPVOID lpMsgBuf; int tchars = ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, hModuleNTDll, win32Err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); #ifdef UNICODE BOOL UsedDefaultChar; int numBytes = ::WideCharToMultiByte(CP_UTF8, 0, (LPCWCH)lpMsgBuf, tchars, // in (LPSTR)lpMsgBuf, tchars + 1, // out 0, &UsedDefaultChar); #endif if (lpMsgBuf == 0) errmsg << "::FormatMessage failed"; else errmsg << (char *)lpMsgBuf; ::LocalFree(lpMsgBuf); return errmsg.str(); } bool report(char * label, SECURITY_STATUS retv) { std::cout << label; if (ERROR_SUCCESS == retv) std::cout << " ok" << std::endl; else std::cout << " reported error = " << errMessage(retv) << std::endl; return (ERROR_SUCCESS == retv); } bool report(char * label, bool retv) { std::cout << label; if (retv) std::cout << " ok" << std::endl; else std::cout << " failed." << std::endl; return (retv); } void report(char * label, BYTE keyBlob[], DWORD len ) { std::cout << label << std::hex << std::setfill('0'); for (unsigned b = 0; b < len; b++) std::cout << std::setw(2) << (int)keyBlob[b] << " "; std::cout << std::dec << std::endl; } // Read all the bytes from a file and put it into a std::vector<unsigned char> // bool fileToVect(const std::string & filePath, // IN: The pathname of the file. std::vector<unsigned char> & dat // OUT: The contents of the file. ) { dat.clear(); if (0 == filePath.size() != 0) return(false); std::ifstream file(filePath.c_str(), std::ios_base::binary); if (file.bad()) return false; file.seekg(0, std::ios::end); // Determine the file size std::streamoff tellglength = file.tellg(); if (-1 == tellglength) return false; file.seekg(0, std::ios::beg); if (0 != tellglength) { dat.resize((std::vector<unsigned char>::size_type) tellglength); // Allocate space in the std::vector file.read((char *)&dat[0], tellglength); // Read all the bytes into the std::vector. } file.close(); return(true); } bool fetchKeyAndCertFromP12( const std::string & p12Filename, const std::wstring & p12Password, NCRYPT_KEY_HANDLE & hKey, PCCERT_CONTEXT & pcontext) { std::vector<unsigned char> datP12; if (!report("File to vector ", fileToVect(p12Filename, datP12))) return false; CRYPT_DATA_BLOB pfx; pfx.pbData = (BYTE *)datP12.data(); pfx.cbData = datP12.size(); if (!report("PFXIsPFXBlob", TRUE == ::PFXIsPFXBlob(&pfx))) return false; if (!report("PFXVerifyPassword", TRUE == ::PFXVerifyPassword(&pfx, p12Password.c_str(), 0))) return false; HCERTSTORE hStore = ::PFXImportCertStore(&pfx, p12Password.c_str(), CRYPT_EXPORTABLE | PKCS12_NO_PERSIST_KEY | PKCS12_ALWAYS_CNG_KSP); bool importCertStoreOk = (NULL != hStore); if (!report("PFXImportCertStore", importCertStoreOk)) return false; while (pcontext = ::CertEnumCertificatesInStore(hStore, pcontext)) { // Certs with private keys have a CERT_KEY_CONTEXT_PROP_ID const int buffsize = 4999; DWORD len = buffsize; char buff[buffsize]; // Need more than just the structure space... len = buffsize; if (::CertGetCertificateContextProperty(pcontext, CERT_KEY_CONTEXT_PROP_ID, buff, &len)) { auto ckc = (CERT_KEY_CONTEXT *)buff; hKey = ckc->hNCryptKey; // Found the private key handle ! break; // Found the desired certificate. } } if (NULL == hKey) { return report("No certs with private keys in this certStore", false); } report("CertEnumCertificatesInStore", true); bool closeCertStoreOk = TRUE == CertCloseStore(hStore, 0); if (!report("CertCloseStore", closeCertStoreOk)) return false; return true; } bool exportPrivateKeyBlob(NCRYPT_KEY_HANDLE hKey, WCHAR * ngBlobType, std::vector< unsigned char> & keyblob) { DWORD policy = NCRYPT_ALLOW_PLAINTEXT_ARCHIVING_FLAG | NCRYPT_ALLOW_ARCHIVING_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG | NCRYPT_ALLOW_EXPORT_FLAG; if (!report("NCryptSetProperty( allow plaintext export )", ::NCryptSetProperty(hKey, NCRYPT_EXPORT_POLICY_PROPERTY, (PBYTE)&policy, sizeof(DWORD), 0))) return false; const int buffsize = 1024; keyblob.resize(buffsize); DWORD keylen = buffsize; if (!report("NCryptExportKey", ::NCryptExportKey(hKey, NULL, ngBlobType, NULL, keyblob.data(), buffsize, &keylen, 0))) return false; keyblob.resize(keylen); report("BLOB= ", keyblob.data(), keylen); return true; } // NCryptExportKey can use BCRYPT_ECCPRIVATE_BLOB, BCRYPT_PRIVATE_KEY_BLOB, BCRYPT_RSAFULLPRIVATE_BLOB // WCHAR * determineBlobType(PCCERT_CONTEXT cert) { std::cout << "Cert alg is "; std::string certOid{ cert->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId }; if (certOid == szOID_ECC_PUBLIC_KEY) { std::cout << "ECC" << std::endl; return BCRYPT_ECCPRIVATE_BLOB; // 1.2.840.10045.2.1 } if (certOid == szOID_RSA_RSA) { std::cout << "RSA" << std::endl; return LEGACY_RSAPRIVATE_BLOB; // 1.2.840.113549.1.1.1 // return BCRYPT_RSAFULLPRIVATE_BLOB; // 1.2.840.113549.1.1.1 } std::cout << "unexpected " << certOid << std::endl; return BCRYPT_PRIVATE_KEY_BLOB; }


    • Proposed as answer by Euclid Friday, July 6, 2018 2:50 AM
    • Marked as answer by Andrew7Webb Wednesday, July 18, 2018 1:52 PM
    Sunday, April 22, 2018 3:30 AM

All replies

  • Hi Andriw7Webb,

    I move this to Application Security for Windows Desktop forum for better support. people in that forum is more familiar with security issue.

    Shu


    We are trying to better understand customer views on social support experience, so your participation in this interview project would be greatly appreciated if you have time. Thanks for helping make community forums a great place.
    Click HERE to participate the survey.

    Friday, June 26, 2015 2:17 AM
  • I just want to say that I spent countless hours in the past 3 years trying to use CNG functions to export EC private keys from PFX files but with no luck. The conclusion of my investigation was that Microsoft didn't implement EC support in NCryptExportKey, at least for the standard key storage provider.

    Their might be some other undocumented way but I was not able to reverse engineer any.

    For the time being, it is easier and quicker to write code that uses OpenSSL for parsing PFX files and then formatting the data in CNG format.


    Mounir IDRASSI - IDRIX - http://www.idrix.fr

    Tuesday, July 14, 2015 11:26 PM
  • Did you find an answer to your question?

    I have no problem exporting my EC P256 private key with NCryptExportKey, but I created the PKCS12 file without PBE. However, as soon as I switch to P384, I have the same issue as you describe here.

    Tuesday, August 25, 2015 3:56 AM
  • Hi Andrew, I just read your comment in a question (related to this thread subject) a made today.

    I found a solution to my case, I guess it may work for you. My problem was an issue with the export of the certificate in a p12 container and a condition ( the certificate had to be installed in the windows certificate store).

    These are the commands I executed (in a ubuntu machine) to generate the certificate (the important one for me was the export).

    openssl ecparam -out ./eckeyparam.pem -name "prime256v1" -param_enc named_curve -conv_form uncompressed
    
    openssl req -new -newkey ec:./eckeyparam.pem -sha256 -keyout "${DIR}/${NAME}.key" -passout pass:"${MyPassword}" -subj "${MySubjectString}" -out "${DIR}/${NAME}.csr" -config ${SSLConfigFile}
    
    openssl ca -in "${DIR}/${NAME}.csr" -enddate ${ENDDATE} -passin pass:"${MyCAKeyPassword}" -out "${DIR}/${NAME}.crt -notext -batch -extension section_name -config ${SSLConfigFile}
    
    openssl pkcs12 -export -in "${DIR}/${NAME}.crt" -passin pass:"${MyP12Password}" -inkey "${DIR}/${NAME}.key" -name ${NAME} -CSP "Microsoft Software Key Service Provider" -keysign -cerfile "${CAROOTANDINTERMEDIATE}" -out "${DIR}/${NAME}.p12"

    I hope it may help.


    Fred G C

    Saturday, April 16, 2016 12:05 AM
  • The same issue exist in my case also. It would be helpful if you could share how you solved the above mentioned issue.

    • Edited by Barkho Wednesday, April 18, 2018 2:09 PM
    Wednesday, April 18, 2018 2:09 PM
  • I made some progress using a different approach in fetchKeyAndCertFromP12. I'm not sure it works on all possible P12s. I did test on a RSA and ECC P12.

    // Use a Window system call to display a Windows specific error std::string errMessage(int win32Err) { std::stringstream errmsg; errmsg << " " << std::hex << win32Err << std::dec << ": "; return errmsg.str(); static HMODULE hModuleNTDll = LoadLibrary(L"NTDLL.DLL"); LPVOID lpMsgBuf; int tchars = ::FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_IGNORE_INSERTS, hModuleNTDll, win32Err, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, 0, NULL); #ifdef UNICODE BOOL UsedDefaultChar; int numBytes = ::WideCharToMultiByte(CP_UTF8, 0, (LPCWCH)lpMsgBuf, tchars, // in (LPSTR)lpMsgBuf, tchars + 1, // out 0, &UsedDefaultChar); #endif if (lpMsgBuf == 0) errmsg << "::FormatMessage failed"; else errmsg << (char *)lpMsgBuf; ::LocalFree(lpMsgBuf); return errmsg.str(); } bool report(char * label, SECURITY_STATUS retv) { std::cout << label; if (ERROR_SUCCESS == retv) std::cout << " ok" << std::endl; else std::cout << " reported error = " << errMessage(retv) << std::endl; return (ERROR_SUCCESS == retv); } bool report(char * label, bool retv) { std::cout << label; if (retv) std::cout << " ok" << std::endl; else std::cout << " failed." << std::endl; return (retv); } void report(char * label, BYTE keyBlob[], DWORD len ) { std::cout << label << std::hex << std::setfill('0'); for (unsigned b = 0; b < len; b++) std::cout << std::setw(2) << (int)keyBlob[b] << " "; std::cout << std::dec << std::endl; } // Read all the bytes from a file and put it into a std::vector<unsigned char> // bool fileToVect(const std::string & filePath, // IN: The pathname of the file. std::vector<unsigned char> & dat // OUT: The contents of the file. ) { dat.clear(); if (0 == filePath.size() != 0) return(false); std::ifstream file(filePath.c_str(), std::ios_base::binary); if (file.bad()) return false; file.seekg(0, std::ios::end); // Determine the file size std::streamoff tellglength = file.tellg(); if (-1 == tellglength) return false; file.seekg(0, std::ios::beg); if (0 != tellglength) { dat.resize((std::vector<unsigned char>::size_type) tellglength); // Allocate space in the std::vector file.read((char *)&dat[0], tellglength); // Read all the bytes into the std::vector. } file.close(); return(true); } bool fetchKeyAndCertFromP12( const std::string & p12Filename, const std::wstring & p12Password, NCRYPT_KEY_HANDLE & hKey, PCCERT_CONTEXT & pcontext) { std::vector<unsigned char> datP12; if (!report("File to vector ", fileToVect(p12Filename, datP12))) return false; CRYPT_DATA_BLOB pfx; pfx.pbData = (BYTE *)datP12.data(); pfx.cbData = datP12.size(); if (!report("PFXIsPFXBlob", TRUE == ::PFXIsPFXBlob(&pfx))) return false; if (!report("PFXVerifyPassword", TRUE == ::PFXVerifyPassword(&pfx, p12Password.c_str(), 0))) return false; HCERTSTORE hStore = ::PFXImportCertStore(&pfx, p12Password.c_str(), CRYPT_EXPORTABLE | PKCS12_NO_PERSIST_KEY | PKCS12_ALWAYS_CNG_KSP); bool importCertStoreOk = (NULL != hStore); if (!report("PFXImportCertStore", importCertStoreOk)) return false; while (pcontext = ::CertEnumCertificatesInStore(hStore, pcontext)) { // Certs with private keys have a CERT_KEY_CONTEXT_PROP_ID const int buffsize = 4999; DWORD len = buffsize; char buff[buffsize]; // Need more than just the structure space... len = buffsize; if (::CertGetCertificateContextProperty(pcontext, CERT_KEY_CONTEXT_PROP_ID, buff, &len)) { auto ckc = (CERT_KEY_CONTEXT *)buff; hKey = ckc->hNCryptKey; // Found the private key handle ! break; // Found the desired certificate. } } if (NULL == hKey) { return report("No certs with private keys in this certStore", false); } report("CertEnumCertificatesInStore", true); bool closeCertStoreOk = TRUE == CertCloseStore(hStore, 0); if (!report("CertCloseStore", closeCertStoreOk)) return false; return true; } bool exportPrivateKeyBlob(NCRYPT_KEY_HANDLE hKey, WCHAR * ngBlobType, std::vector< unsigned char> & keyblob) { DWORD policy = NCRYPT_ALLOW_PLAINTEXT_ARCHIVING_FLAG | NCRYPT_ALLOW_ARCHIVING_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG | NCRYPT_ALLOW_EXPORT_FLAG; if (!report("NCryptSetProperty( allow plaintext export )", ::NCryptSetProperty(hKey, NCRYPT_EXPORT_POLICY_PROPERTY, (PBYTE)&policy, sizeof(DWORD), 0))) return false; const int buffsize = 1024; keyblob.resize(buffsize); DWORD keylen = buffsize; if (!report("NCryptExportKey", ::NCryptExportKey(hKey, NULL, ngBlobType, NULL, keyblob.data(), buffsize, &keylen, 0))) return false; keyblob.resize(keylen); report("BLOB= ", keyblob.data(), keylen); return true; } // NCryptExportKey can use BCRYPT_ECCPRIVATE_BLOB, BCRYPT_PRIVATE_KEY_BLOB, BCRYPT_RSAFULLPRIVATE_BLOB // WCHAR * determineBlobType(PCCERT_CONTEXT cert) { std::cout << "Cert alg is "; std::string certOid{ cert->pCertInfo->SubjectPublicKeyInfo.Algorithm.pszObjId }; if (certOid == szOID_ECC_PUBLIC_KEY) { std::cout << "ECC" << std::endl; return BCRYPT_ECCPRIVATE_BLOB; // 1.2.840.10045.2.1 } if (certOid == szOID_RSA_RSA) { std::cout << "RSA" << std::endl; return LEGACY_RSAPRIVATE_BLOB; // 1.2.840.113549.1.1.1 // return BCRYPT_RSAFULLPRIVATE_BLOB; // 1.2.840.113549.1.1.1 } std::cout << "unexpected " << certOid << std::endl; return BCRYPT_PRIVATE_KEY_BLOB; }


    • Proposed as answer by Euclid Friday, July 6, 2018 2:50 AM
    • Marked as answer by Andrew7Webb Wednesday, July 18, 2018 1:52 PM
    Sunday, April 22, 2018 3:30 AM
  • Thank you. This works for me as well. The trick was to set the policy:

    DWORD policy = NCRYPT_ALLOW_PLAINTEXT_ARCHIVING_FLAG | NCRYPT_ALLOW_ARCHIVING_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG | NCRYPT_ALLOW_EXPORT_FLAG;
    ::NCryptSetProperty(hKey, NCRYPT_EXPORT_POLICY_PROPERTY, (PBYTE)&policy, sizeof(DWORD), 0);

    Friday, July 6, 2018 2:51 AM
  • I think it is a dual trick
       1) CertGetCertificateContextProperty with CERT_KEY_CONTEXT_PROP_ID  
       2) SetProperty to NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG

    2 without 1 failed when I tried CryptAcquireCertificatePrivateKey.
    But something about 1 allows you to do 2.
    Glad it helped.

    Friday, July 6, 2018 10:26 AM