登录时未通过凭据提供程序加载 KSP(密钥存储提供程序)

Posted

技术标签:

【中文标题】登录时未通过凭据提供程序加载 KSP(密钥存储提供程序)【英文标题】:KSP (Key Storage Provider) not being loaded at logon via a Credential Provider 【发布时间】:2016-07-15 14:39:59 【问题描述】:

我正在创建一个 Windows 凭据提供程序,以使用此 article 中所述的证书登录到 Windows 域。 这意味着创建一个自定义 KSP,在创建身份验证包时将由 LsaLogonUser 调用。

我设法创建了自定义 KSP,并在直接调用 LsaLogonUser 的独立应用程序中对其进行了成功测试。 基本上是创建身份验证包并将其传递给 LsaLogonUser,加载 KSP,调用一堆函数并验证用户在状态码上返回成功结果并加载用户配置文件。

但是,当我在凭据上的 GetSerialization 期间使用相同的身份验证包时,KSP 甚至不会加载,并且我得到 ReportResult(NTSTATUS ntsStatus, NTSTATUS ntsSubstatus, ... .) 在 ntsStatus 上。

这是我在测试中使用的 GetSerialization 代码:

    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;

我的问题是为什么在直接从 LsaLogonUser 调用身份验证包时加载 KSP,而不是在 Windows 登录期间从凭据提供程序调用。

InitialiseKerbCertificateLogon 代码为:

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;

【问题讨论】:

【参考方案1】:

来自微软关于KERB_CERTIFICATE_LOGON结构的文档:

UNICODE_STRING 类型的成员中存储的指针是相对的 到结构的开头并且不是绝对内存 指针。

但文档并没有告诉您CspData 指针也应该相对于结构的开头...

当您使用具有绝对内存指针的数据调用LsaLogonUser 时,它会起作用,当指针是相对的时也是如此。当数据被序列化时,只会对所有相对指针起作用。

有时(取决于 CREDENTIAL_PROVIDER_USAGE_SCENARIO)您应该使用 KERB_CERTIFICATE_UNLOCK_LOGON 而不是 KERB_CERTIFICATE_LOGON。

【讨论】:

我考虑了相对指针以及 unicode 字符串。我已经更新了原始帖子以包含 InitializeKerbCertificateLogon 函数。 你试过这个吗:pKerbCertLogon->DomainName.Buffer= (PWSTR)(pbDomainBuffer-pbAuthInfo); pKerbCertLogon->UserName.Buffer = (PWSTR)(pbUserBuffer-pbAuthInfo); pKerbCertLogon->Pin.Buffer = (PWSTR)(pbPinBuffer-pbAuthInfo); pKerbCertLogon->CspData = (PUCHAR)(pbCspData-pbAuthInfo); ? 就是这样!非常感谢!我完全错过了文档中的这一点,而且它与 LsaLogonUser 一起工作的事实让我没有质疑 auth 打包代码。

以上是关于登录时未通过凭据提供程序加载 KSP(密钥存储提供程序)的主要内容,如果未能解决你的问题,请参考以下文章

失败后停止自动登录 - 自定义凭据提供程序 Windows

凭据提供程序通过网络进行通信

在 WPF 应用程序中加密凭据

使用 Docker 时未加载 Keycloak SPI 提供程序和层

Windows 凭据提供程序远程登录

自定义凭据提供程序未加载