Reputation: 91
I am trying to import private key to Microsoft TPM Virtual Smart Card.
With legacy crypto API, private key is imported successfully. verified with certutil -scinfo. Problem with this approach is, it shows user two dialogs, one to select virtual smart card and other for PIN.
I have created TPM virtual smart card with the following tpmvscmgr command. "tpmvscmgr.exe create /name TestVSC /pin default /adminkey random /generate"
This is the code with legacy crypto API. CryptAcquireContext() shows user dialogs.
BOOL Legacy_LoadPrivateKey(LPBYTE pbBlob, DWORD cbSize, PCCERT_CONTEXT pCertContext,
CHAR szContainerName[1024])
{
DWORD dwLen = 0;
HCRYPTPROV hProv;
BOOL bRet = CryptAcquireContext(&hProv, NULL, MS_SCARD_PROV, PROV_RSA_FULL,
CRYPT_NEWKEYSET);
if (bRet)
{
const std::string pin = "12345678";
// get the container name
dwLen = 1024;
CryptGetProvParam(hProv, PP_CONTAINER, (BYTE*)szContainerName, &dwLen, 0);
HCRYPTKEY hKey;
bRet = CryptImportKey(hProv, pbBlob, cbSize, NULL, 0, &hKey);
if (bRet)
{
bRet = CryptSetKeyParam(hKey, KP_CERTIFICATE, pCertContext->pbCertEncoded, 0);
if (!bRet)
{
DWORD dwError = GetLastError();
//_tprintf(_T("Failed to import the certificate into the smart card. Error 0x%.8X\n"), dwError);
}
CryptDestroyKey(hKey);
}
else
{
DWORD dwError = GetLastError();
//_tprintf(_T("Failed to import the private key into the smart card. Error 0x%.8X\n"), dwError);
}
CryptReleaseContext(hProv, 0);
if (!bRet)
{
// delete the container because of the error
CryptAcquireContextA(&hProv, szContainerName, MS_SCARD_PROV_A, PROV_RSA_FULL, CRYPT_DELETEKEYSET);
}
}
else
{
DWORD dwError = GetLastError();
//_tprintf(_T("Failed to create a new container on the smart card. Error 0x%.8X\n"), dwError);
}
return bRet;
}
This is the code with Ncrypt API. NCryptImportKey() returns error NTE_BAD_KEY_STATE
bool Ncrypt_ImportPrivateKey(const wchar_t* keyName, BYTE* privateKeyBlob, DWORD privateKeyBlobSize) {
NCRYPT_KEY_HANDLE hKey = 0;
SECURITY_STATUS status;
// Open a handle to the Microsoft Smart Card Key Storage Provider
NCRYPT_PROV_HANDLE hProvider = 0;
SECURITY_STATUS status = NCryptOpenStorageProvider(&hProvider,
MS_SMART_CARD_KEY_STORAGE_PROVIDER, 0);
if (status != ERROR_SUCCESS) {
std::cerr << "Failed to open storage provider: " << status << std::endl;
return 1;
}
// Create a persisted key handle
status = NCryptCreatePersistedKey(hProvider, &hKey, BCRYPT_RSA_ALGORITHM, L"\\\\.\\Microsoft Virtual Smart Card 1\\MyKeyContainer", 0, 0);
if (status != ERROR_SUCCESS) {
std::cerr << "Failed to create persisted key: " << status << std::endl;
return false;
}
const wchar_t* pin = L"12345678";
//PBYTE p = (PBYTE)pin.c_str();
//auto len = (DWORD)(wcslen(pin.c_str()) + 1) * sizeof(wchar_t);
auto ret = NCryptSetProperty(hKey, NCRYPT_PIN_PROPERTY, (PBYTE)pin, 8 /*(DWORD)(wcslen(pin) + 1) * sizeof(wchar_t)*/, 0);
// Import the private key
status = NCryptImportKey(hProvider, 0, BCRYPT_RSAFULLPRIVATE_BLOB, NULL, &hKey, privateKeyBlob, privateKeyBlobSize, 0);
if (status != ERROR_SUCCESS) {
std::cerr << "Failed to import private key: " << status << std::endl;
NCryptFreeObject(hKey);
return false;
}
// Finalize the key
status = NCryptFinalizeKey(hKey, 0);
if (status != ERROR_SUCCESS) {
std::cerr << "Failed to finalize key: " << status << std::endl;
NCryptFreeObject(hKey);
return false;
}
// Clean up
if (hKey) {
NCryptFreeObject(hKey);
}
return true;
}
For both scenarios, I have established scard context by specifying PIN.
SCARDHANDLE ConnectToVirtualSmartCardWithPin(SCARDCONTEXT& hContext, const wchar_t* readerName, const char* pin) {
SCARDHANDLE hCard;
DWORD dwActiveProtocol;
LONG lResult = SCardConnect(hContext, readerName, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &hCard, &dwActiveProtocol);
if (lResult != SCARD_S_SUCCESS) {
std::wcerr << L"Failed to connect to virtual smart card: " << lResult << std::endl;
return NULL;
}
// Authenticate with the PIN
SCARD_IO_REQUEST pioSendPci = *SCARD_PCI_T1;
BYTE pbRecvBuffer[2];
DWORD dwRecvLength = sizeof(pbRecvBuffer);
// Construct the APDU for VERIFY command
BYTE pbSendBuffer[256] = { 0x00, 0x20, 0x00, 0x80 }; // VERIFY command with the PIN
size_t pinLen = strlen(pin);
pbSendBuffer[4] = static_cast<BYTE>(pinLen); // Length of the PIN
memcpy(pbSendBuffer + 5, pin, pinLen);
lResult = SCardTransmit(hCard, &pioSendPci, pbSendBuffer, 5 + pinLen, NULL, pbRecvBuffer, &dwRecvLength);
if (lResult != SCARD_S_SUCCESS) {
std::wcerr << L"Failed to authenticate with PIN: " << lResult << std::endl;
SCardDisconnect(hCard, SCARD_LEAVE_CARD);
return NULL;
}
return hCard;
}
int main() {
PCCERT_CONTEXT certContext = nullptr;
// Load certificate and private key from PFX file
std::vector<BYTE> keyData;
HCRYPTKEY privateKey;
std::vector<BYTE> certData = LoadCertificate("C:\\path_to_your_certificate.pfx", L"YourPFXPassword", certContext, privateKey, keyData);
SCARDCONTEXT hContext;
SCARDHANDLE hCard;
LONG lResult;
const wchar_t* readerName = L"Microsoft Virtual Smart Card 1";
const char* pin = "12345678";
// Initialize the SCARD context
lResult = SCardEstablishContext(SCARD_SCOPE_USER, NULL, NULL, &hContext);
if (lResult != SCARD_S_SUCCESS) {
std::wcerr << L"Failed to establish SCARD context: " << lResult << std::endl;
return 1;
}
// Connect to the virtual smart card with PIN
hCard = ConnectToVirtualSmartCardWithPin(hContext, readerName, pin);
if (hCard == NULL) {
SCardReleaseContext(hContext);
return 1;
}
CHAR szContainerName[1024] = { 0 };
Legacy_LoadPrivateKey(keyData.data(), keyData.size(), certContext, szContainerName);
SCardDisconnect(hCard, SCARD_LEAVE_CARD);
SCardReleaseContext(hContext);
return 0;
}
Is there a way to import private key to Microsoft TPM Virtual smart card without prompting dialogs to select card and PIN. is it possible to set PIN through API (legacy or Ncrypt)?
Upvotes: 0
Views: 301
Reputation: 91
Below are the steps to import private key by creating new key container. I have omitted error checks and inputs for simplicity.
// Create a persisted key handle
status = NCryptCreatePersistedKey(hProvider, &hKey, BCRYPT_RSA_ALGORITHM, L"\\\\.\\Microsoft Virtual Smart Card 1\\MyKeyContainer1", 0, 0);
const wchar_t* pin = L"12345678";
DWORD keysize = 2048;
auto ret = NCryptSetProperty(hKey, NCRYPT_PIN_PROPERTY, (PBYTE)pin, (DWORD)(wcslen(pin) + 1) * sizeof(wchar_t), 0);
ret = NCryptSetProperty(hKey, NCRYPT_LENGTH_PROPERTY, (PBYTE)&keysize, sizeof(DWORD), 0);
ret = NCryptSetProperty(
hKey,
BCRYPT_PRIVATE_KEY_BLOB,
pbBlob,
cbBlob,
NCRYPT_PERSIST_FLAG | NCRYPT_SILENT_FLAG
);
ret = NCryptSetProperty(hKey, NCRYPT_CERTIFICATE_PROPERTY, pbCert, cbCert, 0);
// Finalize the key
status = NCryptFinalizeKey(hKey, 0);
Upvotes: 0