How to encrypt data using a certificate? - public-key-encryption

How do i encrypt data using a "certificate" in the Microsoft Crypto API?
i know how to encrypt data with the Microsoft Crypto API using AES encryption:
keyBlob.hdr.bType := PLAINTEXTKEYBLOB;
keyBlob.hdr.bVersion := CUR_BLOB_VERSION;
keyBlob.hdr.reserved := 0;
keyBlob.hdr.aiKeyAlg := CALG_AES_128;
keyBlob.cbKeySize := 16;
Move(data[0], keyBlob.key[0], 16);
/*
Set ProviderName to either
providerName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
providerName = "Microsoft Enhanced RSA and AES Cryptographic Provider (Prototype)" //Windows XP and earlier
*/
MS_ENH_RSA_AES_PROV_W: WideString = 'Microsoft Enhanced RSA and AES Cryptographic Provider';
providerName := MS_ENH_RSA_AES_PROV_W;
CryptAcquireContextW(provider, nil, PWideChar(providerName), PROV_RSA_AES, CRYPT_VERIFYCONTEXT);
CryptImportKey(provider, PByte(#keyBlob), sizeof(keyBlob), 0, 0, importedKey);
mode := CRYPT_MODE_CBC;
CryptSetKeyParam(importedKey, KP_MODE, #mode, 0);
//CryptEncrypt encrypts in-place. Copy stuff to be encrypted into new byte buffer
utf8PlainText := TCrypt.WideStringToUTF8(szPlainText);
dataLen := Length(utf8PlainText);
bufferLen := dataLen+16; //allocate a buffer larger than we need to hold the data we want to encrypt
SetLength(data, bufferLen);
Move(utf8PlainText[1], data[0], dataLen);
if not CryptEncrypt(importedKey, 0, True, 0, #data[0], {var}dataLen, bufferLen) then
begin
le := GetLastError;
if le = ERROR_MORE_DATA then
begin
/*
If the buffer allocated for pbData is not large enough to hold the encrypted data,
GetLastError returns ERROR_MORE_DATA and stores the required buffer size,
in bytes, in the DWORD value pointed to by pdwDataLen.
*/
bufferLen := dataLen;
SetLength(data, bufferLen);
CryptEncrypt(importedKey, 0, True, 0, #data[0], {var}dataLen, bufferLen);
end;
CryptDestroyKey(importedKey);
CryptReleaseContext(provider, 0);
end;
Now i need to do the same thing, except rather than symmetric encryption i need to use a public-key to encrypt, and a private key to decrypt.
Note: It took 3 days to come up with those 15 lines of code for symmetric encryption. i'm hoping someone can same me from a week of banging my head against a wall, and i end up going down the wrong path thinking that i have to install OpenSSL. Even worse, if i try to call COM Objects from native code
Note: i only included the code example as a way to fill-up the question with irrelavent junk. Some people vote to close a question if it only contains one line.

Microsoft Crypto API contain high-level functions for asymmetric encryption and decryption with certificates. Look at CryptEncryptessage and CryptDecryptMessage.
In decryption case your CERT_CONTEXT must have a CERT_KEY_PROV_INFO_PROP_ID property.
I can give you an examples of usage:
const wchar_t message[] = L"This is a simple test message.";
PCCERT_CONTEXT hCert = NULL;
HCERTSTORE hStore = NULL;
static bool openCertStoreMY(CDialog *parent)
{
if(!hStore)
{
hStore = CertOpenSystemStore(NULL, L"MY");
if(!hStore)
{
parent->MessageBox(L"Cannot open \"MY\"", L"Error", MB_ICONERROR);
return false;
}
}
return true;
}
void CTestDlg::OnEncryptClicked()
{
if(!hCert)
{
if(!openCertStoreMY(this))
return;
hCert = CryptUIDlgSelectCertificateFromStore(hStore, GetSafeHwnd(), NULL, NULL, 0, 0, 0);
if(!hCert)
return;
}
CRYPT_ENCRYPT_MESSAGE_PARA params;
memset(&params, 0, sizeof(CRYPT_ENCRYPT_MESSAGE_PARA));
params.cbSize = sizeof(CRYPT_ENCRYPT_MESSAGE_PARA);
params.dwMsgEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
params.ContentEncryptionAlgorithm.pszObjId = "2.16.840.1.101.3.4.1.2"; //AES128
DWORD msz;
DWORD cbMsg = sizeof(message);
const BYTE *pbMsg = (PBYTE)message;
if(!CryptEncryptMessage(&params, 1, &hCert, pbMsg, cbMsg, NULL, &msz))
return;
PBYTE outBuf = new BYTE[msz];
if(CryptEncryptMessage(&params, 1, &hCert, pbMsg, cbMsg, outBuf, &msz))
{
FILE *fil = _wfopen(filename, L"wb");
if(fil)
{
fwrite(outBuf, 1, msz, fil);
fclose(fil);
MessageBox(L"Complete");
}
else
MessageBox(L"Cannot open file", L"Error", MB_ICONERROR);
}
delete [] outBuf;
}
void CTestDlg::OnDecryptClicked()
{
if(!openCertStoreMY(this))
return;
CRYPT_DECRYPT_MESSAGE_PARA params;
params.cbSize = sizeof(CRYPT_DECRYPT_MESSAGE_PARA);
params.dwMsgAndCertEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
params.cCertStore = 1;
params.rghCertStore = &hStore;
params.dwFlags = 0;
DWORD cbMsg;
PBYTE pbMsg;
FILE *fil = _wfopen(filename, L"rb");
if(fil)
{
fseek(fil, 0 ,2);
cbMsg = ftell(fil);
fseek(fil, 0, 0);
pbMsg = new BYTE[cbMsg];
fread(pbMsg, 1, cbMsg, fil);
fclose(fil);
} else {
MessageBox(L"Cannot open file", L"Error", MB_ICONERROR);
return;
}
DWORD msz;
if(!CryptDecryptMessage(&params, pbMsg, cbMsg, NULL, &msz, NULL))
{
delete [] pbMsg;
return;
}
PBYTE outBuf = new BYTE[msz];
if(CryptDecryptMessage(&params, pbMsg, cbMsg, outBuf, &msz, NULL))
MessageBox((LPCWSTR)outBuf);
delete [] pbMsg;
delete [] outBuf;
}

Related

Self-signed cert generated through CryptAPI and added to a remote certificate store does not have private key

I have a scenario wherein an application needs to create a self-signed certificate and add it to the certificate store on a remote machine. I've tried both CryptAPI and CNG (though CNG still appears to use CryptAPI for creating the self-signed certificate and adding it to the remote certificate store), but the behavior I see occurs in both.
Environment:
Two machines on the same domain. One is Windows Server 2016 Standard and the other is Windows Server 2019 Datacenter. Same domain user with admin privileges used to log into both machines. Run the application on the 2016 machine indicating the hostname of the other.
The code uses MFC/ATL for CString, includes wincrypt.h, and links against crypt32.lib. Tested against both VS2019's toolset and VS2005's toolset.
What I see:
The self-signed certificate is created and added to the remote store. If I view the certificate through the MMC it indicates that it has a private key attached. However, when I try to click "Manage Private Keys..." an error dialog says:
"No keys found for certificate!"
Similarly the Certificate Export Wizard says:
"Note: The associated private key cannot be found. Only the certificate can be exported."
And the "Yes, export the private key" option is greyed out.
My theory is that the private key isn't being properly attached to the certificate context (or otherwise not being transmitted to the remote machine when I add the key to the certificate store).
Here's what my code looks like:
#include <afx.h>
#include <wincrypt.h>
#pragma comment(lib, "crypt32.lib")
CString GetCertificateThumbprintString(PCCERT_CONTEXT pCertContext)
{
DWORD cbSize;
if (!CryptHashCertificate(0, 0, 0, pCertContext->pbCertEncoded, pCertContext->cbCertEncoded, NULL, &cbSize)) {
return "";
}
LPSTR pszString;
if (!(pszString = (LPSTR)malloc(cbSize * sizeof(TCHAR)))) {
return "";
}
if (!CryptHashCertificate(0, 0, 0, pCertContext->pbCertEncoded, pCertContext->cbCertEncoded, (BYTE*)pszString, &cbSize)) {
free(pszString);
return "";
}
else {
pszString[cbSize] = NULL;
LPTSTR lpszTP = NULL;
if (!(lpszTP = (LPTSTR)malloc((cbSize + 1) * sizeof(TCHAR) * 2))) {
free(pszString);
return "";
}
for (int i = 0; i < (int)cbSize; ++i) {
_stprintf_s(&lpszTP[i * 2], sizeof(TCHAR) + 1, _T("%.2X"), pszString[i] & 0xff);
}
CString result = lpszTP;
free(lpszTP);
free(pszString);
return result;
}
}
CString CreateCertificate(CString hostName)
{
wchar_t subjectName [MAX_PATH] = L"";
wchar_t store [MAX_PATH] = L"";
wsprintfW(store, L"\\\\%s\\MY", hostName);
wsprintfW(subjectName, L"CN=%s", hostName);
HCERTSTORE certStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, NULL, CERT_SYSTEM_STORE_LOCAL_MACHINE, store);
if (NULL == certStore) {
_tprintf(_T("Failed to open Personal certificate store of %s."), hostName);
return "";
}
DWORD encodedSubjectSize = 0;
if (!CertStrToName(X509_ASN_ENCODING, subjectName, CERT_X500_NAME_STR, NULL, NULL, &encodedSubjectSize, NULL)) {
_tprintf(_T("Invalid certificate subject name. Error %d\n"), GetLastError());
return "";
}
BYTE* encodedSubject = (BYTE*)malloc(encodedSubjectSize);
if (NULL == encodedSubject) {
_tprintf(_T("malloc() failed: %d "), GetLastError());
return "";
}
if (!CertStrToName(X509_ASN_ENCODING, subjectName, CERT_X500_NAME_STR, NULL, encodedSubject, &encodedSubjectSize, NULL)) {
_tprintf(_T("Invalid certificate subject name. Error %d\n"), GetLastError());
free(encodedSubject);
return "";
}
// Acquire key container
HCRYPTPROV cryptProvider;
const wchar_t* pszKeyContainerName = L"TESTKEYCONTAINERTEST";
if (!CryptAcquireContext(&cryptProvider, pszKeyContainerName, NULL, PROV_RSA_FULL, CRYPT_NEWKEYSET | CRYPT_MACHINE_KEYSET)) {
if (GetLastError() == NTE_EXISTS)
{
if (!CryptAcquireContext(&cryptProvider, pszKeyContainerName, NULL, PROV_RSA_FULL, CRYPT_MACHINE_KEYSET))
{
_tprintf(_T("Can't get a crypto provider. Error %d\n"), GetLastError());
free(encodedSubject);
return "";
}
}
}
// Generate new key pair
HCRYPTKEY key;
if (!CryptGenKey(cryptProvider, AT_SIGNATURE, 0x08000000 /* RSA-2048-BIT_KEY */ | CRYPT_EXPORTABLE, &key)) {
_tprintf(_T("Can't generate a key pair. Error %d\n"), GetLastError());
CryptReleaseContext(cryptProvider, 0);
CertCloseStore(certStore, 0);
free(encodedSubject);
return "";
}
// Prepare key provider structure for self-signed certificate
CRYPT_KEY_PROV_INFO keyProviderInfo;
ZeroMemory(&keyProviderInfo, sizeof(keyProviderInfo));
keyProviderInfo.pwszContainerName = (LPWSTR)pszKeyContainerName;
keyProviderInfo.dwProvType = PROV_RSA_FULL;
keyProviderInfo.dwFlags = CRYPT_MACHINE_KEYSET;
keyProviderInfo.dwKeySpec = AT_SIGNATURE;
// Prepare algorithm structure for self-signed certificate
CRYPT_ALGORITHM_IDENTIFIER algorithm;
memset(&algorithm, 0, sizeof(algorithm));
algorithm.pszObjId = (LPSTR)szOID_RSA_SHA256RSA;
// Prepare certificate Subject for self-signed certificate
CERT_NAME_BLOB subjectBlob;
ZeroMemory(&subjectBlob, sizeof(subjectBlob));
subjectBlob.cbData = encodedSubjectSize;
subjectBlob.pbData = encodedSubject;
PCCERT_CONTEXT certContext = CertCreateSelfSignCertificate(NULL, &subjectBlob, 0, &keyProviderInfo, &algorithm, NULL, NULL, NULL);
if (!certContext) {
_tprintf(_T("Can't create a self-signed certificate. Error %d\n"), GetLastError());
CryptDestroyKey(key);
CryptReleaseContext(cryptProvider, 0);
CertCloseStore(certStore, 0);
free(encodedSubject);
return "";
}
if (!CertSetCertificateContextProperty(certContext, CERT_KEY_PROV_INFO_PROP_ID, 0, &keyProviderInfo)) {
_tprintf(_T("Unable to set key provider info property on certificate context. Error %d\n"), GetLastError());
CertFreeCertificateContext(certContext);
CryptDestroyKey(key);
CryptReleaseContext(cryptProvider, 0);
CertCloseStore(certStore, 0);
free(encodedSubject);
return "";
}
// add certificate to store
if (!CertAddCertificateContextToStore(certStore, certContext, CERT_STORE_ADD_ALWAYS, nullptr))
{
CertFreeCertificateContext(certContext);
CryptDestroyKey(key);
CryptReleaseContext(cryptProvider, 0);
CertCloseStore(certStore, 0);
free(encodedSubject);
return "";
}
CString result = GetCertificateThumbprintString(certContext);
CertFreeCertificateContext(certContext);
CryptDestroyKey(key);
CryptReleaseContext(cryptProvider, 0);
CertCloseStore(certStore, 0);
free(encodedSubject);
return result;
}
int main(int argc, char* argv[])
{
if (argc < 2) {
printf("Need arg.");
return -1;
}
auto index = 1;
if (strcmp(argv[1], "dbg") == 0) {
while (!IsDebuggerPresent()) Sleep(100);
index = 2;
}
CString strThumbprint = CreateCertificate(argv[index]);
_tprintf(strThumbprint);
return 0;
}
I found a similar issue here which is what gave me the idea to call CertSetCertificateContextProperty in the first place. But it doesn't resolve the issue.
What am I missing here?
Edit: This same code works when the certificate store opened is on the local machine. This issue only arises when the certificate store is on a remote machine.
Edit 2: At RbMm's suggestion, I investigated exporting the certificate into a PFX. It was unclear to me how exporting the certificates into PFX's would change anything except that perhaps the generated PFX would do some magic to allow the key pair to be passed along when I insert it into the remote store.
In that investigation I found this which helped me change my code to utilize PFXExportCertStoreEx/PFXImportCertStore. What you see below is what I added (replacing the CertSetCertificateContextProperty call). However, it should be noted that this did not work either. So I'm again at a loss.
// Create temporary store to shove the self-signed certificate into.
HCERTSTORE initialTempStore = CertOpenStore(CERT_STORE_PROV_MEMORY, PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, NULL, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"MY");
if (NULL == initialTempStore) {
_tprintf(_T("Failed to open local Personal certificate store."));
CertFreeCertificateContext(certContext);
CryptDestroyKey(key);
CryptReleaseContext(cryptProvider, 0);
free(encodedSubject);
return "";
}
// Add the certificate to the self-signed store.
if (!CertAddCertificateContextToStore(initialTempStore, certContext, CERT_STORE_ADD_REPLACE_EXISTING, nullptr)) {
_tprintf(_T("Failed to add cert to local store."));
CertCloseStore(initialTempStore, 0);
CertFreeCertificateContext(certContext);
CryptDestroyKey(key);
CryptReleaseContext(cryptProvider, 0);
free(encodedSubject);
return "";
}
// Export the certificate store into a PFX that packages the certificates with the private keys.
CRYPT_DATA_BLOB pfx;
ZeroMemory(&pfx, sizeof(CRYPT_DATA_BLOB));
LPCTSTR password = L"hello5";
if (!PFXExportCertStoreEx(initialTempStore, &pfx, password, NULL, EXPORT_PRIVATE_KEYS)) {
_tprintf(_T("Unable to export PFX.\n"));
CertCloseStore(initialTempStore, 0);
CertDeleteCertificateFromStore(certContext);
CertFreeCertificateContext(certContext);
CryptDestroyKey(key);
CryptReleaseContext(cryptProvider, 0);
free(encodedSubject);
return "";
}
pfx.pbData = (BYTE*)malloc(pfx.cbData);
if (!PFXExportCertStoreEx(initialTempStore, &pfx, password, NULL, EXPORT_PRIVATE_KEYS)) {
_tprintf(_T("Unable to export PFX.\n"));
CertDeleteCertificateFromStore(certContext);
CertFreeCertificateContext(certContext);
CryptDestroyKey(key);
CryptReleaseContext(cryptProvider, 0);
free(encodedSubject);
free(pfx.pbData);
return "";
}
// Now we don't need anything we had before because the PFX contains it all.
CertFreeCertificateContext(certContext);
CertCloseStore(initialTempStore, 0);
CryptDestroyKey(key);
CryptReleaseContext(cryptProvider, 0);
free(encodedSubject);
// Import the cert into a temporary store marking the keys as exportable.
HCERTSTORE tempStoreWithKeys = PFXImportCertStore(&pfx, password, CRYPT_EXPORTABLE | CRYPT_MACHINE_KEYSET);
if (tempStoreWithKeys == NULL) {
_tprintf(_T("Unable to import PFX.\n"));
PrintError((LPTSTR)_T("PFXImportCertStore"));
free(encodedSubject);
free(pfx.pbData);
return "";
}
// Search through the temporary store to find the cert we want.
PCCERT_CONTEXT certWithPrivateKey = CertEnumCertificatesInStore(tempStoreWithKeys, nullptr);
if (certWithPrivateKey == NULL) {
_tprintf(_T("Unable to enumerate temporary store. Error %d\n"), GetLastError());
free(encodedSubject);
free(pfx.pbData);
return "";
}
while (certWithPrivateKey) {
DWORD requiredSize = CertNameToStr(X509_ASN_ENCODING, &certWithPrivateKey->pCertInfo->Issuer, CERT_X500_NAME_STR, NULL, NULL);
LPTSTR decodedSubject = (LPTSTR)malloc(requiredSize * sizeof(TCHAR));
if (NULL == decodedSubject) {
_tprintf(_T("malloc() failed: %d "), GetLastError());
free(pfx.pbData);
return "";
}
if (!CertNameToStr(X509_ASN_ENCODING, &certWithPrivateKey->pCertInfo->Issuer, CERT_X500_NAME_STR, decodedSubject, requiredSize)) {
_tprintf(_T("Invalid certificate subject name. Error %d\n"), GetLastError());
CertFreeCertificateContext(certWithPrivateKey);
CertCloseStore(tempStoreWithKeys, 0);
free(decodedSubject);
free(pfx.pbData);
return "";
}
if (_tcsncmp(subjectName, decodedSubject, requiredSize) != 0) {
free(decodedSubject);
certWithPrivateKey = CertEnumCertificatesInStore(tempStoreWithKeys, certWithPrivateKey);
continue;
}
free(decodedSubject);
break;
}
if (!certWithPrivateKey) {
_tprintf(_T("No matching cert found in store\n."));
CertFreeCertificateContext(certWithPrivateKey);
CertCloseStore(tempStoreWithKeys, 0);
free(pfx.pbData);
return "";
}
Edit 3: After further discussions I've tried the following as well. After creating the temporary in-memory store and adding the certificate to it, I set the CERT_KEY_CONTEXT property on the resultant add operation as follows:
PCCERT_CONTEXT newCertContext;
// Add the certificate to the self-signed store.
if (!CertAddCertificateContextToStore(initialTempStore, certContext, CERT_STORE_ADD_REPLACE_EXISTING, &newCertContext)) {
_tprintf(_T("Failed to add cert to local store."));
CertCloseStore(initialTempStore, 0);
CertFreeCertificateContext(certContext);
CryptDestroyKey(key);
CryptReleaseContext(cryptProvider, 0);
free(encodedSubject);
return "";
}
CERT_KEY_CONTEXT keyContext;
keyContext.cbSize = sizeof(CERT_KEY_CONTEXT);
keyContext.dwKeySpec = AT_SIGNATURE;
keyContext.hCryptProv = cryptProvider;
if (!CertSetCertificateContextProperty(newCertContext, CERT_KEY_CONTEXT_PROP_ID, 0, &keyContext)) {
_tprintf(_T("Unable to set key context property on certificate context. Error %d\n"), GetLastError());
CertFreeCertificateContext(certContext);
CertFreeCertificateContext(newCertContext);
CryptDestroyKey(key);
CryptReleaseContext(cryptProvider, 0);
CertCloseStore(initialTempStore, 0);
free(encodedSubject);
return "";
}
This does not resolve the issue either.
private key never stored in certificate. it stored in crypto provider container.
function CertSetCertificateContextProperty set property to certificate context (which is not equal to encoded certificate) and this is only link to crypto provider container. this link have no sense on another comp, because there not exist such container. and certificate context exist in memory only or stored in certificate store, which again not exist on another comp
so we need store certificate + private key in PFX container. this can be done in next steps
Acquire Crypto Provider handle (CryptAcquireContextW). note that we
can use in memory only context (use pszContainer = 0, dwFlags =
CRYPT_VERIFYCONTEXT)
generates public/private key pair for container (CryptGenKey ), if it
yet not exist. if we use in memory only container - need do this
always
after certificate is created , create new in memory only store (
CertOpenStore(CERT_STORE_PROV_MEMORY)) and add certificate ( CertAddCertificateContextToStore ) to this store. this need only because PFXExportCertStoreEx accept only cert store as input but
not standalone certificate
set Certificate Context Property to certificate in new store, not
for original
*after this we can call PFXExportCertStoreEx for create PFX container*
this PFX can be moved already to another comps and installed here - during this process, new crypto container will be created, here will be stored private key from PFX, certificate will be put to some cert store and in this store will be link to container, where private key stored
minimal code example:
#define Get_Err(err, fn) err = (fn ? NOERROR : GetLastError())
#define No_Err(err, fn) NOERROR == (Get_Err(err, fn))
#define No_Err_V(err, q, p, fn) NOERROR == (err = ((p = fn) == q ? GetLastError() : NOERROR))
ULONG CreatePfx(PCWSTR szFileName,
PCWSTR szPassword,
PCWSTR SomeName = L"CN=SomeName",
ULONG dwKeySpec = AT_SIGNATURE,
PCWSTR szProvider = MS_ENHANCED_PROV_W,
ULONG dwProvType = PROV_RSA_FULL)
{
ULONG dwError;
HCRYPTPROV hProv;
if (No_Err(dwError, CryptAcquireContextW(&hProv, 0, szProvider, dwProvType, CRYPT_VERIFYCONTEXT)))
{
HCRYPTKEY hKey;
if (No_Err(dwError, CryptGenKey(hProv, dwKeySpec, CRYPT_EXPORTABLE, &hKey)))
{
CryptDestroyKey(hKey);
CRYPT_KEY_PROV_INFO KeyProvInfo = {
0, const_cast<PWSTR>(szProvider), dwProvType, 0, 0, 0, dwKeySpec
};
CERT_NAME_BLOB cnb {};
while (No_Err(dwError, CertStrToNameW(X509_ASN_ENCODING, SomeName, CERT_X500_NAME_STR, 0, cnb.pbData, &cnb.cbData, 0)))
{
if (cnb.pbData)
{
PCCERT_CONTEXT pCertContext, pNewCertContext;
if (No_Err_V(dwError, NULL, pCertContext, CertCreateSelfSignCertificate(hProv, &cnb, 0, &KeyProvInfo,0, 0, 0, 0)))
{
HCERTSTORE hMemStore;
if (No_Err_V(dwError, NULL, hMemStore, CertOpenStore(CERT_STORE_PROV_MEMORY, 0, 0, CERT_STORE_CREATE_NEW_FLAG, 0 )))
{
if (No_Err(dwError, CertAddCertificateContextToStore(hMemStore, pCertContext, CERT_STORE_ADD_NEW, &pNewCertContext)))
{
CERT_KEY_CONTEXT ckc = { sizeof(ckc), { hProv }, dwKeySpec };
CertSetCertificateContextProperty(pNewCertContext, CERT_KEY_CONTEXT_PROP_ID, 0, &ckc);
CertFreeCertificateContext(pNewCertContext);
DATA_BLOB db{};
while (No_Err(dwError, PFXExportCertStoreEx(hMemStore, &db,
szPassword, 0, EXPORT_PRIVATE_KEYS|
REPORT_NO_PRIVATE_KEY|
REPORT_NOT_ABLE_TO_EXPORT_PRIVATE_KEY)))
{
if (db.pbData)
{
// on remote comp call
// PFXImportCertStore(&db, szPassword, CRYPT_EXPORTABLE);
HANDLE hFile;
if (No_Err_V(dwError, INVALID_HANDLE_VALUE, hFile, CreateFileW(
szFileName, FILE_APPEND_DATA, 0, 0, CREATE_ALWAYS, 0, 0)))
{
Get_Err(dwError, WriteFile(hFile, db.pbData, db.cbData, &db.cbData, 0));
CloseHandle(hFile);
}
break;
}
db.pbData = (PBYTE)alloca(db.cbData);
}
}
CertCloseStore(hMemStore, 0);
}
CertFreeCertificateContext(pCertContext);
}
break;
}
cnb.pbData = (PUCHAR)alloca(cnb.cbData);
}
}
CryptReleaseContext(hProv, 0);
}
return dwError;
}
another way:
export private key from crypto provider (
CryptExportKey(PRIVATEKEYBLOB) )
pack it with certificate
and send to remote comp
on remote comp create new crypto container
import private key ( CryptImportKey )
put certificate to the some store
bind certificate to private key container
(CertSetCertificateContextProperty)
#define Get_Err(err, fn) err = (fn ? NOERROR : GetLastError())
#define No_Err(err, fn) NOERROR == (Get_Err(err, fn))
#define No_Err_V(err, q, p, fn) NOERROR == ((p = fn) == q ? GetLastError() : NOERROR)
ULONG ExportCertAndKey(_Out_ PDATA_BLOB pdb,
_Out_ PULONG pcbPrivateKey,
_In_ PCWSTR SomeName = L"CN=SomeName",
_In_ ULONG dwKeySpec = AT_KEYEXCHANGE,
_In_ PCWSTR szProvider = MS_ENHANCED_PROV_W,
_In_ ULONG dwProvType = PROV_RSA_FULL)
{
ULONG dwError;
HCRYPTPROV hProv;
if (No_Err(dwError, CryptAcquireContextW(&hProv, 0, szProvider, dwProvType, CRYPT_VERIFYCONTEXT)))
{
HCRYPTKEY hKey;
if (No_Err(dwError, CryptGenKey(hProv, dwKeySpec, CRYPT_EXPORTABLE, &hKey)))
{
DATA_BLOB db{};
while (No_Err(dwError, CryptExportKey(hKey, 0, PRIVATEKEYBLOB, 0, db.pbData, &db.cbData)))
{
if (db.pbData)
{
break;
}
db.pbData = (PBYTE)alloca(db.cbData);
}
CryptDestroyKey(hKey);
if (NOERROR == dwError)
{
CRYPT_KEY_PROV_INFO KeyProvInfo = {
0, const_cast<PWSTR>(szProvider), dwProvType, 0, 0, 0, dwKeySpec
};
CERT_NAME_BLOB cnb {};
while (No_Err(dwError, CertStrToNameW(X509_ASN_ENCODING, SomeName, CERT_X500_NAME_STR, 0, cnb.pbData, &cnb.cbData, 0)))
{
if (cnb.pbData)
{
PCCERT_CONTEXT pCertContext;
if (No_Err_V(dwError, NULL, pCertContext, CertCreateSelfSignCertificate(hProv, &cnb, 0, &KeyProvInfo,0, 0, 0, 0)))
{
if (No_Err_V(dwError, NULL, pdb->pbData, new UCHAR[pdb->cbData = db.cbData + pCertContext->cbCertEncoded]))
{
*pcbPrivateKey = db.cbData;
memcpy(pdb->pbData, db.pbData, db.cbData);
memcpy(pdb->pbData + db.cbData, pCertContext->pbCertEncoded, pCertContext->cbCertEncoded);
}
CertFreeCertificateContext(pCertContext);
}
break;
}
cnb.pbData = (PUCHAR)alloca(cnb.cbData);
}
}
}
CryptReleaseContext(hProv, 0);
}
return dwError;
}
ULONG ImportCertAndKey(_In_ PDATA_BLOB pdb,
_In_ ULONG cbPrivateKey,
_In_ PCWSTR szContainer,
_In_ ULONG dwKeySpec = AT_KEYEXCHANGE,
_In_ PCWSTR szProvider = MS_ENHANCED_PROV_W,
_In_ ULONG dwProvType = PROV_RSA_FULL)
{
ULONG dwError;
HCRYPTPROV hProv;
if (No_Err(dwError, CryptAcquireContextW(&hProv, szContainer, szProvider, dwProvType, CRYPT_NEWKEYSET)))
{
HCRYPTKEY hKey;
if (No_Err(dwError, CryptImportKey(hProv, pdb->pbData, cbPrivateKey, 0, CRYPT_EXPORTABLE, &hKey)))
{
CryptDestroyKey(hKey);
PCCERT_CONTEXT pCertContext, pNewCertContext;
if (No_Err_V(dwError, NULL, pCertContext, CertCreateCertificateContext(
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
pdb->pbData + cbPrivateKey, pdb->cbData - cbPrivateKey)))
{
HCERTSTORE hStore;
if (No_Err_V(dwError, NULL, hStore, CertOpenSystemStoreW(0, L"MY")))
{
if (No_Err(dwError, CertAddCertificateContextToStore(hStore, pCertContext, CERT_STORE_ADD_NEW, &pNewCertContext)))
{
CRYPT_KEY_PROV_INFO KeyProvInfo = {
const_cast<PWSTR>(szContainer), const_cast<PWSTR>(szProvider), dwProvType, 0, 0, 0, dwKeySpec
};
Get_Err(dwError, CertSetCertificateContextProperty(pNewCertContext, CERT_KEY_PROV_INFO_PROP_ID, 0, &KeyProvInfo));
CertFreeCertificateContext(pNewCertContext);
}
CertCloseStore(hStore, 0);
}
CertFreeCertificateContext(pCertContext);
}
}
CryptReleaseContext(hProv, 0);
}
return dwError;
}
DATA_BLOB db;
ULONG cbPrivKey;
if (NOERROR == ExportCertAndKey(&db, &cbPrivKey))
{
ImportCertAndKey(&db, cbPrivKey, L"TestContainer");
delete [] db.pbData;
}

How to eject USB drive on Windows 10 (IOCTL_STORAGE_EJECT_MEDIA no longer enough)

Convention wisdom to eject a USB drive on Windows is the following sequence:
CreateFile (drive letter, with read/write rights, file share read and write)
DeviceIoControl(FSCTL_LOCK_VOLUME)
DeviceIoControl(FSCTL_DISMOUNT_VOLUME)
DeviceIoControl(IOCTL_STORAGE_MEDIA_REMOVAL) PreventMediaRemoval = FALSE
DeviceIoControl(IOCTL_STORAGE_EJECT_MEDIA)
This worked fine until a recent change in Windows 10 (not sure when). Now the drive is still properly ejected, but then Windows immediately remounts the drive.
What needs to be done to eject the drive until the user removes it and puts it in again?
Using CM_Request_Device_EjectW API works for me. You can have a try.
The following is the complete code I tested and it from "How to Prepare a USB Drive for Safe Removal" at codeproject.
(Here the "F" is my USB drive letter. Replace it using your own one.)
#include <stdio.h>
#include <windows.h>
#include <Setupapi.h>
#include <winioctl.h>
#include <winioctl.h>
#include <cfgmgr32.h>
//-------------------------------------------------
DEVINST GetDrivesDevInstByDeviceNumber(long DeviceNumber, UINT DriveType, char* szDosDeviceName);
//-------------------------------------------------
//-------------------------------------------------
int main()
{
char DriveLetter = 'F';
DriveLetter &= ~0x20; // uppercase
if (DriveLetter < 'A' || DriveLetter > 'Z') {
return 1;
}
char szRootPath[] = "F:\\"; // "X:\" -> for GetDriveType
szRootPath[0] = DriveLetter;
char szDevicePath[] = "F:"; // "X:" -> for QueryDosDevice
szDevicePath[0] = DriveLetter;
char szVolumeAccessPath[] = "\\\\.\\F:"; // "\\.\X:" -> to open the volume
szVolumeAccessPath[4] = DriveLetter;
long DeviceNumber = -1;
// open the storage volume
HANDLE hVolume = CreateFile(szVolumeAccessPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
if (hVolume == INVALID_HANDLE_VALUE) {
return 1;
}
// get the volume's device number
STORAGE_DEVICE_NUMBER sdn;
DWORD dwBytesReturned = 0;
long res = DeviceIoControl(hVolume, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL);
if (res) {
DeviceNumber = sdn.DeviceNumber;
}
CloseHandle(hVolume);
if (DeviceNumber == -1) {
return 1;
}
// get the drive type which is required to match the device numbers correctely
UINT DriveType = GetDriveType(szRootPath);
// get the dos device name (like \device\floppy0) to decide if it's a floppy or not - who knows a better way?
char szDosDeviceName[MAX_PATH];
res = QueryDosDevice(szDevicePath, szDosDeviceName, MAX_PATH);
if (!res) {
return 1;
}
// get the device instance handle of the storage volume by means of a SetupDi enum and matching the device number
DEVINST DevInst = GetDrivesDevInstByDeviceNumber(DeviceNumber, DriveType, szDosDeviceName);
if (DevInst == 0) {
return 1;
}
PNP_VETO_TYPE VetoType = PNP_VetoTypeUnknown;
WCHAR VetoNameW[MAX_PATH];
VetoNameW[0] = 0;
bool bSuccess = false;
// get drives's parent, e.g. the USB bridge, the SATA port, an IDE channel with two drives!
DEVINST DevInstParent = 0;
res = CM_Get_Parent(&DevInstParent, DevInst, 0);
for (long tries = 1; tries <= 3; tries++) { // sometimes we need some tries...
VetoNameW[0] = 0;
// CM_Query_And_Remove_SubTree doesn't work for restricted users
//res = CM_Query_And_Remove_SubTreeW(DevInstParent, &VetoType, VetoNameW, MAX_PATH, CM_REMOVE_NO_RESTART); // CM_Query_And_Remove_SubTreeA is not implemented under W2K!
//res = CM_Query_And_Remove_SubTreeW(DevInstParent, NULL, NULL, 0, CM_REMOVE_NO_RESTART); // with messagebox (W2K, Vista) or balloon (XP)
res = CM_Request_Device_EjectW(DevInstParent, &VetoType, VetoNameW, MAX_PATH, 0);
//res = CM_Request_Device_EjectW(DevInstParent, NULL, NULL, 0, 0); // with messagebox (W2K, Vista) or balloon (XP)
bSuccess = (res == CR_SUCCESS && VetoType == PNP_VetoTypeUnknown);
if (bSuccess) {
break;
}
Sleep(500); // required to give the next tries a chance!
}
if (bSuccess) {
printf("Success\n\n");
return 0;
}
printf("failed\n");
printf("Result=0x%2X\n", res);
if (VetoNameW[0]) {
printf("VetoName=%ws)\n\n", VetoNameW);
}
return 1;
}
//-----------------------------------------------------------
//----------------------------------------------------------------------
// returns the device instance handle of a storage volume or 0 on error
//----------------------------------------------------------------------
DEVINST GetDrivesDevInstByDeviceNumber(long DeviceNumber, UINT DriveType, char* szDosDeviceName)
{
bool IsFloppy = (strstr(szDosDeviceName, "\\Floppy") != NULL); // who knows a better way?
GUID* guid;
switch (DriveType) {
case DRIVE_REMOVABLE:
if (IsFloppy) {
guid = (GUID*)&GUID_DEVINTERFACE_FLOPPY;
}
else {
guid = (GUID*)&GUID_DEVINTERFACE_DISK;
}
break;
case DRIVE_FIXED:
guid = (GUID*)&GUID_DEVINTERFACE_DISK;
break;
case DRIVE_CDROM:
guid = (GUID*)&GUID_DEVINTERFACE_CDROM;
break;
default:
return 0;
}
// Get device interface info set handle for all devices attached to system
HDEVINFO hDevInfo = SetupDiGetClassDevs(guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (hDevInfo == INVALID_HANDLE_VALUE) {
return 0;
}
// Retrieve a context structure for a device interface of a device information set
DWORD dwIndex = 0;
long res;
BYTE Buf[1024];
PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf;
SP_DEVICE_INTERFACE_DATA spdid;
SP_DEVINFO_DATA spdd;
DWORD dwSize;
spdid.cbSize = sizeof(spdid);
while (true) {
res = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, guid, dwIndex, &spdid);
if (!res) {
break;
}
dwSize = 0;
SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL); // check the buffer size
if (dwSize != 0 && dwSize <= sizeof(Buf)) {
pspdidd->cbSize = sizeof(*pspdidd); // 5 Bytes!
ZeroMemory(&spdd, sizeof(spdd));
spdd.cbSize = sizeof(spdd);
long res = SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd, dwSize, &dwSize, &spdd);
if (res) {
// in case you are interested in the USB serial number:
// the device id string contains the serial number if the device has one,
// otherwise a generated id that contains the '&' char...
/*
DEVINST DevInstParent = 0;
CM_Get_Parent(&DevInstParent, spdd.DevInst, 0);
char szDeviceIdString[MAX_PATH];
CM_Get_Device_ID(DevInstParent, szDeviceIdString, MAX_PATH, 0);
printf("DeviceId=%s\n", szDeviceIdString);
*/
// open the disk or cdrom or floppy
HANDLE hDrive = CreateFile(pspdidd->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hDrive != INVALID_HANDLE_VALUE) {
// get its device number
STORAGE_DEVICE_NUMBER sdn;
DWORD dwBytesReturned = 0;
res = DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL);
if (res) {
if (DeviceNumber == (long)sdn.DeviceNumber) { // match the given device number with the one of the current device
CloseHandle(hDrive);
SetupDiDestroyDeviceInfoList(hDevInfo);
return spdd.DevInst;
}
}
CloseHandle(hDrive);
}
}
}
dwIndex++;
}
SetupDiDestroyDeviceInfoList(hDevInfo);
return 0;
}

SetupDiGetClassDevs - Get name shown in USB devices from deviceinstanceid?

I have a printer connected that doesn't have a driver and doesn't show up under printers, but it shows up under "Start->Settings->Bluetooth & other devices" with name "SRP300".
I can send data to the printer via the following routine (found here : https://www.levelextreme.com/ViewPageGenericLogin.aspx?LoadContainer=1&NoThread=1157607 ) where it gets the Device Instance ID, and Guid - but I'm simply not able to figure out where I am to get the name from "SP300".
What would I need to call as soon as I've found the GUID of it? The best would be if I could search for the name to start with and if SP300 is found then get the instance id/guid, but I've tried different approaches enumerating to get that name that is shown but nothing seem to produce it.
If I inspect the registry I can see that it's grouped under USB and then under a folder called USBPRINT and then a folder 00000001 and in there there is the name, but wonder how I'm able to retrieve this with Win api calls?
int test2()
{
int MemberIndex = 0;
LONG Result = 0;
DWORD Length = 0;
HANDLE hDevInfo;
ULONG Required;
HANDLE m_hComm=NULL;
PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = NULL;
SP_DEVICE_INTERFACE_DATA devInfoData;
hDevInfo = SetupDiGetClassDevs((LPGUID)&(USB_PRINT), NULL, NULL, DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
if (hDevInfo == INVALID_HANDLE_VALUE)
{
printf("No hardware device");
return 0;
}
devInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
//Step through the available devices looking for the one we want.
do
{
//[1]
Result = SetupDiEnumDeviceInterfaces(hDevInfo, 0, (LPGUID)&(USB_PRINT), MemberIndex, &devInfoData);
if (Result != 0)
{
SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInfoData, NULL, 0, &Length, NULL);
//Allocate memory for the hDevInfo structure, using the returned Length.
detailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)new BYTE[Length * 4];
//Set cbSize in the detailData structure.
detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
//Call the function again, this time passing it the returned buffer size.
if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &devInfoData, detailData, Length, &Required, NULL) == TRUE)
{
m_hComm = CreateFile(detailData->DevicePath,
GENERIC_READ | GENERIC_WRITE,
NULL,
NULL,
OPEN_EXISTING, 0, NULL);
if (m_hComm != INVALID_HANDLE_VALUE)
{
//Result = 0;
printf("USB port Available");
}
CloseHandle(m_hComm);
}
delete(detailData);
}
MemberIndex = MemberIndex + 1;
} while (Result != 0);
SetupDiDestroyDeviceInfoList(hDevInfo);
printf("%u\r\n", MemberIndex);
;
return 0;
}
If an enumeration parameter value is not used to select devices, set Enumerator to NULL and when Enumerator is NULL, SetupDiGetClassDevs returns devices for all PnP enumerators. You could set this parameter either be the value's globally unique identifier (GUID) or symbolic name.
For more information, you could refer to this document below.
https://learn.microsoft.com/en-us/windows/desktop/api/setupapi/nf-setupapi-setupdigetclassdevsw
Best Regards,
Baron Bi

Structure of the certificate pointed to by NCRYPT_KEY_HANDLE

I've written a credential provider and a key storage provider to logon to windows via certificate. As the documentation in this points is quite vague I used different samples from Microsoft to get things working.
I think I'm nearly there, but the logon behaves unpredictably. Sometimes I get through to the kerberos server (which complains about the certificate), sometimes the process fails with 0x80090029 without any information and sometimes windows crashes. As these crashes all have to do with access violations or null pointers and happen to occur in various places (kerberos.dll, Windows.UI.Logon.dll, ...) I think it has something to do with my key structure that i point the given NCRYT_KEY_HANDLE to in my OpenKey-implementation.
The KeyStorageProviderSample in the CNG-Kit has an example, but relies on a RSA-key stored in %AppData%. I don't have the private key available as it is stored in secure hardware, I just have the public part (i.e. the public certificate), that I read from another device and import via the following code:
SECURITY_STATUS WINAPI KeyHandler::ReadPemCert(__inout KSP_KEY *keyHandle)
{
LOG_FUNCTION;
CERT_CONTEXT certContext = {};
DWORD readLength = 0;
LOG("Fetch certificate");
const int maxSizeInBytes = 4096;
char pemCertificateAsBytes[maxSizeInBytes];
BluetoothClient bluetoothClient = BluetoothClient();
bluetoothClient.getCertificate((PBYTE)pemCertificateAsBytes, readLength);
DWORD certAsDerLen = readLength;
BYTE* certAsDer = new BYTE[certAsDerLen];
LOG("convert PEM to DER");
if (!CryptStringToBinaryA(pemCertificateAsBytes, 0, CRYPT_STRING_BASE64, certAsDer, &certAsDerLen, NULL, NULL))
{
LOG_LAST_ERROR("CryptStringToBinary failed. Err:");
}
LOG_BYTES_AS_HEX("DER-Zertifikat", certAsDer, certAsDerLen);
PCCERT_CONTEXT pcCertContext = CertCreateCertificateContext(X509_ASN_ENCODING, certAsDer, certAsDerLen);
certContext->pCertInfo = pcCertContext->pCertInfo;
certContext->cbCertEncoded = pcCertContext->cbCertEncoded;
certContext->pbCertEncoded = pcCertContext->pbCertEncoded;
certContext->dwCertEncodingType = pcCertContext->dwCertEncodingType;
CERT_INFO *certInfo;
certInfo = certContext.pCertInfo;
CERT_PUBLIC_KEY_INFO pubKeyInfo = certInfo->SubjectPublicKeyInfo;
LOG("Aquire cryptocontext");
HCRYPTPROV hProv = 0;
if (!CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT))
{
{
LOG_LAST_ERROR("CryptAcquireContext failed. Err:");
return -1;
}
}
LOG("Importing public key");
NCRYPT_KEY_HANDLE publicKeyHandle = NULL;
if (!CryptImportPublicKeyInfo(hProv, X509_ASN_ENCODING, &pubKeyInfo, &publicKeyHandle))
{
LOG_LAST_ERROR("CryptImportPublicKeyInfo failed. Err:");
return -1;
}
keyHandle->fFinished = TRUE;
keyHandle->hPublicKey = (BCRYPT_KEY_HANDLE)publicKeyHandle;
keyHandle->pszKeyBlobType = BCRYPT_RSAPUBLIC_BLOB;
LocalFree(certInfo);
return ERROR_SUCCESS;
}
The key structure is initialized this way:
SECURITY_STATUS
WINAPI
KeyHandler::CreateNewKeyObject(
__in_opt LPCWSTR pszKeyName,
__deref_out KSP_KEY **ppKey)
{
LOG_FUNCTION;
KSP_KEY *pKey = NULL;
DWORD cbKeyName = 0;
SECURITY_STATUS Status = NTE_INTERNAL_ERROR;
NTSTATUS ntStatus = STATUS_INTERNAL_ERROR;
pKey = (KSP_KEY *)HeapAlloc(GetProcessHeap(), 0, sizeof(KSP_KEY));
if (pKey == NULL)
{
return NTE_NO_MEMORY;
}
pKey->cbLength = sizeof(KSP_KEY);
pKey->dwMagic = KSP_KEY_MAGIC;
pKey->dwAlgID = KSP_RSA_ALGID;
pKey->pszKeyFilePath = NULL;
pKey->pszKeyBlobType = NULL;
pKey->dwKeyBitLength = 0;
pKey->fFinished = FALSE;
//Copy the keyname into the key struct.
if (pszKeyName != NULL)
{
cbKeyName = (DWORD)(wcslen(pszKeyName) + 1) * sizeof(WCHAR);
pKey->pszKeyName = (LPWSTR)HeapAlloc(
GetProcessHeap(),
0,
cbKeyName + sizeof(WCHAR));
if (pKey->pszKeyName == NULL)
{
return NTE_NO_MEMORY;
}
CopyMemory(pKey->pszKeyName, pszKeyName, cbKeyName);
pKey->pszKeyName[cbKeyName / sizeof(WCHAR)] = L'\0';
}
else
{
pKey->pszKeyName = NULL;
}
if (globalRSAProviderHandle == NULL)
{
ntStatus = BCryptOpenAlgorithmProvider(
&globalRSAProviderHandle,
BCRYPT_RSA_ALGORITHM,
NULL,
0);
if (!NT_SUCCESS(ntStatus))
{
return NormalizeNteStatus(ntStatus);
}
}
pKey->hProvider = globalRSAProviderHandle;
pKey->pbKeyFile = NULL;
pKey->cbKeyFile = 0;
pKey->pbPrivateKey = NULL;
pKey->cbPrivateKey = 0;
pKey->hPublicKey = NULL;
pKey->hPrivateKey = NULL;
pKey->dwExportPolicy = NCRYPT_ALLOW_EXPORT_FLAG | NCRYPT_ALLOW_PLAINTEXT_EXPORT_FLAG;
pKey->dwKeyUsagePolicy = NCRYPT_ALLOW_DECRYPT_FLAG | NCRYPT_ALLOW_SIGNING_FLAG;
pKey->pbSecurityDescr = NULL;
pKey->cbSecurityDescr = 0;
InitializeListHead(&pKey->PropertyList);
*ppKey = pKey;
pKey = NULL;
return ERROR_SUCCESS;
}
Somewhere in there must be the mistake leading to the various memory errors. But as I'm quite new to windows programming and c/c++ I just can't spot the point and can't find any documentation about the datastructure that windows expects for the NCRYTP_KEY_HANDLE.
Does anybody know more about this structure?
NCRYPT_KEY_HANDLE is just a pointer to a structure that you defined.
Windows itself doesn't care about this structure and expect that your provider knows how to work with it.
In KeyHandler::ReadPemCert you mixed legacy CryptoAPI and CNG API. Since you are implementing KSP you should use only CNG API (CryptImportPublicKeyInfoEx2).
DWORD error = NTE_FAIL;
BCRYPT_KEY_HANDLE hKey = NULL;
...
PCCERT_CONTEXT pcCertContext = CertCreateCertificateContext(X509_ASN_ENCODING, certAsDer, certAsDerLen);
if(!pcCertContext)
{
goto Exit;
}
if (!CryptImportPublicKeyInfoEx2(X509_ASN_ENCODING, &pcCertContext->pCertInfo->SubjectPublicKeyInfo, 0, nullptr, &hKey))
{
goto Exit;
}
/* Also you can export key and print out the result to make sure everything works
DWORD temp = 0;
status = BCryptExportKey(hKey, 0, BCRYPT_RSAPUBLIC_BLOB, nullptr, 0, &temp, 0);
if (status != ERROR_SUCCESS)
{
goto Exit;
}
std::vector<BYTE> key(temp);
status = BCryptExportKey(hKey, 0, BCRYPT_RSAPUBLIC_BLOB, key.data(), key.size(), &temp, 0);
if (status != ERROR_SUCCESS)
{
goto Exit;
}
for (auto const& i : key)
{
std::cout << std::hex << (int)i;
}
}
*/
keyHandle->fFinished = TRUE;
keyHandle->hPublicKey = hKey;
keyHandle->pszKeyBlobType = BCRYPT_RSAPUBLIC_BLOB;
erro = ERROR_SUCCESS;
Exit:
if(pcCertContext)
{
CertFreeCertificateContext(pcCertContext);
}
return error;

SecItemExport fails when exporting private key

I would like to export a private ECDSA key using the new 10.7 Security.framework APIs but the operation keeps failing with error code -26260:
errSecPassphraseRequired = -25260, /* Passphrase is required for import/export. */
I would also like for the keys I generate not to be added to the keychain because they are for use on other machines and I will handle the storage myself. Here is code that doesn't work- the first export succeeds, but the same key data once imported can't be exported again without a passphrase. I haven't set a passphrase for this key. I've tried many combinations of export types, included wrapped OpenSSL and PEM armoring, but it doesn't seem to make a difference. I have working code that does this already using OpenSSL, but since it is deprecated in Lion I wanted to see what the new APIs could do.
// Set up the parameters for 256-bit ECDSA
CFMutableDictionaryRef parameters = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(parameters, kSecAttrKeyType, kSecAttrKeyTypeECDSA);
CFDictionarySetValue(parameters, kSecAttrKeySizeInBits, CFSTR("256")); // kSecp256r1
CFDictionarySetValue(parameters, kSecAttrIsPermanent, kCFBooleanFalse);
// Issue #1:
// SecKeyGeneratePair() adds the keys to the keychain, which I don't want to do
// The docs say that kSecAttrIsPermanent should change this behavior
SecKeyRef publicKey, privateKey;
OSStatus result = SecKeyGeneratePair((CFDictionaryRef)parameters, &publicKey, &privateKey);
CFRelease(parameters), parameters = nullptr;
if(noErr == result) {
CFDataRef privateKeyData = nullptr;
result = SecItemExport(privateKey, kSecFormatUnknown, 0, nullptr, &privateKeyData);
if(noErr == result) {
CFShow(privateKeyData);
uint32_t format = kSecFormatUnknown;
uint32_t itemType = kSecItemTypePrivateKey;
CFArrayRef items = nullptr;
result = SecItemImport(privateKeyData, nullptr, &format, &itemType, 0, nullptr, nullptr, &items);
CFRelease(privateKeyData), privateKeyData = nullptr;
CFRelease(privateKey), privateKey = nullptr;
if(noErr == result) {
privateKey = (SecKeyRef)CFRetain(CFArrayGetValueAtIndex(items, 0));
CFRelease(items), items = nullptr;
// Issue #2:
// SecItemExport() fails with -25260
result = SecItemExport(privateKey, kSecFormatUnknown, 0, nullptr, &privateKeyData);
if(noErr == result) {
CFShow(privateKeyData);
CFRelease(privateKeyData), privateKeyData = nullptr;
}
else
printf("SecItemExport error: %d\n", result);
}
else
puts("SecItemImport failed");
}
else
printf("SecItemExport error: %d\n", result);
}
else
printf("SecKeyGeneratePair error: %d\n", result);
Here is output from a sample run:
<CFData 0x10061b390 [0x7fff79ed3fa0]>{length = 121, capacity = 256, bytes = 0x307702010104209432679e712a4ac156 ... 219ffed31a54aff1}
SecItemExport error: -25260

Resources