windows credential provider using PKINIT and implicit mapping

Peter Buus 0 Reputation points
2025-12-08T11:18:59.3233333+00:00

I am developing a windows credential provider aiming at supporting CPUS_LOGON/CPUS_UNLOCK_WORKSTATION using certificate PKINIT and implicit mapping.

The certificate used for login is a short-term certificate stored in LocalMachine/My CNG store.

My GetSerialization is as follows, but I cant get any other answer from Security Event Log than

EventID 4625 - Status 0xC000000D bad format

I tried numerous ways to get more information - but I only get the same event

How can I achieve more information on what is wrong?

//////////////////////////////////////////////////////////////////////////////////////

Dec 8 12:05:31: [CP] Opened LocalMachine\MY store: 000001CCDE2DD0C0

Dec 8 12:05:31: [CP] Found cert context: 000001CCDC8CDA70

Dec 8 12:05:31: [CP] Using ABSOLUTE pointer for CspData. Ptr=000001CCD6FF8DE8

Dec 8 12:05:31: [CP] Kerberos package id = 2

Dec 8 12:05:31: [CP] ===== GetSerialization SUCCESS =====

Dec 8 12:05:31: [CP] ===== GetSerialization EXIT hr=0x00000000 =====

//////////////////////////////////////////////////////////////////////////////////////

HRESULT SoloidpCertificateCredential::GetSerialization(

CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE* pcpgsr,

CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs,

PWSTR* ppwszOptionalStatusText,

CREDENTIAL_PROVIDER_STATUS_ICON* pcpsiOptionalStatusIcon)

{

*pcpgsr = CPGSR_NO_CREDENTIAL_NOT_FINISHED;

*ppwszOptionalStatusText = nullptr;

*pcpsiOptionalStatusIcon = CPSI_NONE;

ZeroMemory(pcpcs, sizeof(*pcpcs));

HRESULT hr = S_OK;

HCERTSTORE hStore = nullptr;

PCCERT_CONTEXT pCert = nullptr;

BYTE* pbSerialization = nullptr;

std::wstring thumbprint;

std::vector<BYTE> hashBytes;

CRYPT_HASH_BLOB hashBlob = {};

LSA_STRING lsaPackageName = {};

LSA_HANDLE hLsa = nullptr;

NTSTATUS nts = 0;

auto HexToBytes = [&](LPCWSTR hex, std::vector<BYTE>& out) -> bool {

    if (!hex) return false;

    size_t len = wcslen(hex);

    if (len % 2 != 0) return false;

    out.clear();

    out.reserve(len / 2);

    for (size_t i = 0; i < len; i += 2)

    {

        unsigned int byte = 0;

        if (swscanf_s(hex + i, L"%02X", &byte) != 1)

        {

            out.clear();

            return false;

        }

        out.push_back(static_cast<BYTE>(byte));

    }

    return !out.empty();

};

{

    CibaUserCertificate cibaUserCert;

    hr = cibaUserCert.LoadCibaUserCertificate(&thumbprint);

	// Issues short term certificate and publish to LocalMachine\My

    if (FAILED(hr)) goto cleanup;

}

hStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, 0, 0, CERT_SYSTEM_STORE_LOCAL_MACHINE, L"MY");

if (!hStore) {

    hr = HRESULT_FROM_WIN32(GetLastError());

    debugMsg("[CP] CertOpenStore failed hr=0x%08X", hr);

    goto cleanup;

}

// --- Step 3: Convert thumbprint -> bytes ---

if (!HexToBytes(thumbprint.c_str(), hashBytes)) {

    debugMsg("[CP] HexToBytes failed for thumbprint=%ls", thumbprint.c_str());

    hr = E_FAIL;

    goto cleanup;

}

hashBlob.cbData = (DWORD)hashBytes.size();

hashBlob.pbData = hashBytes.data();

// --- Step 4: Find cert by SHA1 ---

pCert = CertFindCertificateInStore(hStore,

    X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 0,

    CERT_FIND_SHA1_HASH, &hashBlob, nullptr);

if (!pCert) {

    hr = HRESULT_FROM_WIN32(GetLastError());

    debugMsg("[CP] CertFindCertificateInStore failed hr=0x%08X", hr);

    goto cleanup;

}

debugMsg("[CP] Found cert context: %p", pCert);

DWORD cbCert = pCert->cbCertEncoded;

DWORD cbTotal = sizeof(KERB_CERTIFICATE_LOGON) + cbCert;

pbSerialization = (BYTE*)CoTaskMemAlloc(cbTotal);

if (!pbSerialization) {

    hr = E_OUTOFMEMORY;

    debugMsg("[CP] CoTaskMemAlloc failed for %u bytes", cbTotal);

    goto cleanup;

}

ZeroMemory(pbSerialization, cbTotal);

const bool useOffsets = false; // tried both - same result

KERB_CERTIFICATE_LOGON* pLogon =

    reinterpret_cast<KERB_CERTIFICATE_LOGON*>(pbSerialization);

pLogon->MessageType = KerbCertificateLogon;

pLogon->Flags = 0;

pLogon->CspDataLength = cbCert;

BYTE* certDest = pbSerialization + sizeof(KERB_CERTIFICATE_LOGON);

if (useOffsets) {

    pLogon->CspData = (PUCHAR)(ULONG_PTR)(certDest - pbSerialization);

} else {

    pLogon->CspData = (PUCHAR)certDest;

}

memcpy(certDest, pCert->pbCertEncoded, cbCert);

lsaPackageName.Buffer = const_cast<PCHAR>("Kerberos");

lsaPackageName.Length = (USHORT)strlen(lsaPackageName.Buffer);

lsaPackageName.MaximumLength = lsaPackageName.Length;

nts = LsaConnectUntrusted(&hLsa);

if (!NT_SUCCESS(nts)) {

    hr = HRESULT_FROM_NT(nts);

    debugMsg("[CP] LsaConnectUntrusted failed nts=0x%08X hr=0x%08X", nts, hr);

    goto cleanup;

}

nts = LsaLookupAuthenticationPackage(hLsa, &lsaPackageName,

                                     &pcpcs->ulAuthenticationPackage);

LsaClose(hLsa);

if (!NT_SUCCESS(nts)) {

    hr = HRESULT_FROM_NT(nts);

    goto cleanup;

}

pcpcs->clsidCredentialProvider = CLSID_SoloidpCredentialProvider;

pcpcs->cbSerialization = cbTotal;

pcpcs->rgbSerialization = pbSerialization;

*pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED;

pbSerialization = nullptr;

debugMsg("[CP] ===== GetSerialization SUCCESS =====");

cleanup:

if (pbSerialization) CoTaskMemFree(pbSerialization);

if (pCert) CertFreeCertificateContext(pCert);

if (hStore) CertCloseStore(hStore, 0);

if (hLsa) LsaClose(hLsa);

debugMsg("[CP] ===== GetSerialization EXIT hr=0x%08X =====", hr);

return hr;

}

Developer technologies | C++
Developer technologies | C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
{count} votes

1 answer

Sort by: Most helpful
  1. Michael Le (WICLOUD CORPORATION) 10,570 Reputation points Microsoft External Staff Moderator
    2026-01-28T06:53:44.0366667+00:00

    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):

    1. Certificate bytes (you have this)
    2. Provider name (missing - e.g., "Microsoft Software Key Storage Provider")
    3. 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.

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.