Hello @Peter Buus ,
Sorry for the late response.
For details about the error code, you could check here: 0xC000000D.
It seems like in your CspData - you're only copying the certificate bytes, but LSA needs the complete CSP data structure.
For CNG certificates, CspData must contain three components (per the smart card sign-in flow documentation):
- Certificate bytes (you have this)
- Provider name (missing - e.g., "Microsoft Software Key Storage Provider")
- Container name (missing - the unique key identifier)
Also, CspData must always be an offset, not an absolute pointer.
You could check out this implementation.
First, add a function to extract the CNG key information:
HRESULT GetCngKeyInfo(PCCERT_CONTEXT pCert, std::wstring& providerName, std::wstring& containerName)
{
NCRYPT_KEY_HANDLE hKey = 0;
BOOL fCallerFree = FALSE;
DWORD dwKeySpec = 0;
if (!CryptAcquireCertificatePrivateKey(pCert,
CRYPT_ACQUIRE_ONLY_NCRYPT_KEY_FLAG | CRYPT_ACQUIRE_SILENT_FLAG,
nullptr, &hKey, &dwKeySpec, &fCallerFree))
{
return HRESULT_FROM_WIN32(GetLastError());
}
HRESULT hr = S_OK;
DWORD cbData = 0;
// Get provider handle
NCRYPT_PROV_HANDLE hProv = 0;
if (NCryptGetProperty(hKey, NCRYPT_PROVIDER_HANDLE_PROPERTY,
(BYTE*)&hProv, sizeof(hProv), &cbData, 0) == ERROR_SUCCESS)
{
// Get provider name
cbData = 0;
NCryptGetProperty(hProv, NCRYPT_NAME_PROPERTY, nullptr, 0, &cbData, 0);
std::vector<BYTE> nameBuffer(cbData);
if (NCryptGetProperty(hProv, NCRYPT_NAME_PROPERTY,
nameBuffer.data(), cbData, &cbData, 0) == ERROR_SUCCESS)
{
providerName = (LPCWSTR)nameBuffer.data();
}
}
// Get container name
cbData = 0;
if (NCryptGetProperty(hKey, NCRYPT_UNIQUE_NAME_PROPERTY,
nullptr, 0, &cbData, 0) == ERROR_SUCCESS)
{
std::vector<BYTE> containerBuffer(cbData);
if (NCryptGetProperty(hKey, NCRYPT_UNIQUE_NAME_PROPERTY,
containerBuffer.data(), cbData, &cbData, 0) == ERROR_SUCCESS)
{
containerName = (LPCWSTR)containerBuffer.data();
}
}
if (fCallerFree) NCryptFreeObject(hKey);
return (providerName.empty() || containerName.empty()) ? E_FAIL : S_OK;
}
Then replace your buffer building code with:
// Step 1: Get CNG provider and container info
std::wstring providerName, containerName;
hr = GetCngKeyInfo(pCert, providerName, containerName);
if (FAILED(hr)) {
debugMsg("[CP] GetCngKeyInfo failed hr=0x%08X", hr);
goto cleanup;
}
debugMsg("[CP] Provider: %ls, Container: %ls", providerName.c_str(), containerName.c_str());
// Step 2: Calculate complete CSP data size
DWORD cbCert = pCert->cbCertEncoded;
DWORD cbProviderName = (DWORD)((providerName.length() + 1) * sizeof(WCHAR));
DWORD cbContainerName = (DWORD)((containerName.length() + 1) * sizeof(WCHAR));
DWORD cbCspData = cbCert + cbProviderName + cbContainerName;
DWORD cbTotal = sizeof(KERB_CERTIFICATE_LOGON) + cbCspData;
pbSerialization = (BYTE*)CoTaskMemAlloc(cbTotal);
if (!pbSerialization) {
hr = E_OUTOFMEMORY;
goto cleanup;
}
ZeroMemory(pbSerialization, cbTotal);
// Step 3: Fill structure
KERB_CERTIFICATE_LOGON* pLogon = (KERB_CERTIFICATE_LOGON*)pbSerialization;
pLogon->MessageType = KerbCertificateLogon;
pLogon->Flags = 0;
pLogon->CspDataLength = cbCspData;
// CspData MUST be offset, not pointer!
pLogon->CspData = (PUCHAR)(ULONG_PTR)sizeof(KERB_CERTIFICATE_LOGON);
// Step 4: Copy all three components
BYTE* pCurrent = pbSerialization + sizeof(KERB_CERTIFICATE_LOGON);
// Copy certificate
memcpy(pCurrent, pCert->pbCertEncoded, cbCert);
pCurrent += cbCert;
// Copy provider name
memcpy(pCurrent, providerName.c_str(), cbProviderName);
pCurrent += cbProviderName;
// Copy container name
memcpy(pCurrent, containerName.c_str(), cbContainerName);
debugMsg("[CP] Built CSP data: cert=%d, provider=%d, container=%d bytes",
cbCert, cbProviderName, cbContainerName);
Let me know the result after considering these changes.