I am creating a Windows Credential Provider to logon into a Windows domain using certificates as described on this article. This implies creating a custom KSP that will be called by LsaLogonUser when creating an authentication package.
I manage to create the custom KSP and tested it successfully in a standalone app that calls LsaLogonUser directly. Basically creating the authentication package and pass it to LsaLogonUser, loaded the KSP, called a bunch of functions and authenticated the user returning a success result on the status code and the user profile loaded.
However, when I use the same authentication package during the GetSerialization on a credential, the KSP doesn't even loads and I get a 0xc000000d (The parameter is incorrect) reported by ReportResult(NTSTATUS ntsStatus, NTSTATUS ntsSubstatus, ....) on the ntsStatus.
This is the code for GetSerialization I am using on my testing:
HRESULT AOCredential::GetSerialization(
CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE *pcpgsr,
CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcs,
PWSTR *ppwszOptionalStatusText,
CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon
)
{
UNREFERENCED_PARAMETER(ppwszOptionalStatusText);
UNREFERENCED_PARAMETER(pcpsiOptionalStatusIcon);
HRESULT hr;
ULONG ulAuthPackage;
hr = RetrieveKerberosAuthPackage(&ulAuthPackage);
if (SUCCEEDED(hr))
{
InitialiseKerbCertificateLogon(&pcpcs->rgbSerialization, &pcpcs->cbSerialization); // this package actually worked when calling LsaLogonUser function directly (the KSP gets loaded and the authentication succeeds)
pcpcs->ulAuthenticationPackage = ulAuthPackage;
pcpcs->clsidCredentialProvider = CLSID_CallsignProvider;
*pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED;
}
return hr;
}
My question is why the KSP is getting loaded when the authentication package is called from LsaLogonUser directly, but not from the Credential Provider during the windows logon.
The InitialiseKerbCertificateLogon code is:
void InitialiseKerbCertificateLogon(PWSTR domain, PWSTR username, LPBYTE* authInfo, ULONG *authInfoLength)
{
WCHAR szCardName[] = L"";
WCHAR szContainerName[] = L"Default";
WCHAR szReaderName[] = L"";
WCHAR szCspName[] = CS_KSP_NAME;
WCHAR szPin[] = CS_TEST_PIN;
ULONG ulPinByteLen = (ULONG)(wcslen(szPin) * sizeof(WCHAR));
WCHAR szUserName[] = CS_TEST_USERNAME;
ULONG ulUserByteLen = (ULONG)(wcslen(szUserName) * sizeof(WCHAR));
WCHAR szDomainName[] = CS_TEST_DOMAIN;
ULONG ulDomainByteLen = (ULONG)(wcslen(szDomainName) * sizeof(WCHAR));
LPBYTE pbAuthInfo = NULL;
ULONG ulAuthInfoLen = 0;
KERB_CERTIFICATE_LOGON *pKerbCertLogon;
KERB_SMARTCARD_CSP_INFO *pKerbCspInfo;
LPBYTE pbDomainBuffer, pbUserBuffer, pbPinBuffer;
LPBYTE pbCspData;
LPBYTE pbCspDataContent;
ULONG ulCspDataLen = (ULONG)(sizeof(KERB_SMARTCARD_CSP_INFO) - sizeof(TCHAR) +
(wcslen(szCardName) + 1) * sizeof(WCHAR) +
(wcslen(szCspName) + 1) * sizeof(WCHAR) +
(wcslen(szContainerName) + 1) * sizeof(WCHAR) +
(wcslen(szReaderName) + 1) * sizeof(WCHAR));
ulAuthInfoLen = sizeof(KERB_CERTIFICATE_LOGON) +
ulDomainByteLen + sizeof(WCHAR) +
ulUserByteLen + sizeof(WCHAR) +
ulPinByteLen + sizeof(WCHAR) +
ulCspDataLen;
pbAuthInfo = (LPBYTE)CoTaskMemAlloc(ulAuthInfoLen);
ZeroMemory(pbAuthInfo, ulAuthInfoLen);
pbDomainBuffer = pbAuthInfo + sizeof(KERB_CERTIFICATE_LOGON);
pbUserBuffer = pbDomainBuffer + ulDomainByteLen + sizeof(WCHAR);
pbPinBuffer = pbUserBuffer + ulUserByteLen + sizeof(WCHAR);
pbCspData = pbPinBuffer + ulPinByteLen + sizeof(WCHAR);
memcpy(pbDomainBuffer, szDomainName, ulDomainByteLen);
memcpy(pbUserBuffer, szUserName, ulUserByteLen);
memcpy(pbPinBuffer, szPin, ulPinByteLen);
pKerbCertLogon = (KERB_CERTIFICATE_LOGON*)pbAuthInfo;
pKerbCertLogon->MessageType = KerbCertificateLogon;
pKerbCertLogon->DomainName.Length = (USHORT)ulDomainByteLen;
pKerbCertLogon->DomainName.MaximumLength = (USHORT)(ulDomainByteLen + sizeof(WCHAR));
pKerbCertLogon->DomainName.Buffer = (PWSTR)pbDomainBuffer;
pKerbCertLogon->UserName.Length = (USHORT)ulUserByteLen;
pKerbCertLogon->UserName.MaximumLength = (USHORT)(ulUserByteLen + sizeof(WCHAR));
pKerbCertLogon->UserName.Buffer = (PWSTR)pbUserBuffer;
pKerbCertLogon->Pin.Length = (USHORT)ulPinByteLen;
pKerbCertLogon->Pin.MaximumLength = (USHORT)(ulPinByteLen + sizeof(WCHAR));
pKerbCertLogon->Pin.Buffer = (PWSTR)pbPinBuffer;
pKerbCertLogon->CspDataLength = ulCspDataLen;
pKerbCertLogon->CspData = pbCspData;
pKerbCspInfo = (KERB_SMARTCARD_CSP_INFO*)pbCspData;
pKerbCspInfo->dwCspInfoLen = ulCspDataLen;
pKerbCspInfo->MessageType = 1;
pKerbCspInfo->KeySpec = AT_KEYEXCHANGE;
pKerbCspInfo->nCardNameOffset = 0;
pKerbCspInfo->nReaderNameOffset = pKerbCspInfo->nCardNameOffset + (ULONG)wcslen(szCardName) + 1;
pKerbCspInfo->nContainerNameOffset = pKerbCspInfo->nReaderNameOffset + (ULONG)wcslen(szReaderName) + 1;
pKerbCspInfo->nCSPNameOffset = pKerbCspInfo->nContainerNameOffset + (ULONG)wcslen(szContainerName) + 1;
pbCspDataContent = pbCspData + sizeof(KERB_SMARTCARD_CSP_INFO) - sizeof(TCHAR);
memcpy(pbCspDataContent + (pKerbCspInfo->nCardNameOffset * sizeof(WCHAR)), szCardName, wcslen(szCardName) * sizeof(WCHAR));
memcpy(pbCspDataContent + (pKerbCspInfo->nReaderNameOffset * sizeof(WCHAR)), szReaderName, wcslen(szReaderName) * sizeof(WCHAR));
memcpy(pbCspDataContent + (pKerbCspInfo->nContainerNameOffset * sizeof(WCHAR)), szContainerName, wcslen(szContainerName) * sizeof(WCHAR));
memcpy(pbCspDataContent + (pKerbCspInfo->nCSPNameOffset * sizeof(WCHAR)), szCspName, wcslen(szCspName) * sizeof(WCHAR));
*authInfo = pbAuthInfo;
*authInfoLength = ulAuthInfoLen;
}
From Microsoft's docs about KERB_CERTIFICATE_LOGON structure:
The pointers stored in the members of UNICODE_STRING type are relative to the beginning of the structure and are not absolute memory pointers.
But documentation does not tell you that CspData
pointer should also be relative to the beginning of the structure...
When you call LsaLogonUser
with data with absolute memory pointers it will work, also when pointers are relative. When data is serialized will only work with all pointers relative.
Also sometimes (depending on CREDENTIAL_PROVIDER_USAGE_SCENARIO) you should use KERB_CERTIFICATE_UNLOCK_LOGON instead of KERB_CERTIFICATE_LOGON.