【问题标题】:How to write a KSP to hook up into KERB_CERTIFICATE_LOGON如何编写 KSP 以连接到 KERB_CERTIFICATE_LOGON
【发布时间】:2017-06-11 16:18:24
【问题描述】:

大家好,我编写了一个自定义凭据提供程序,在使用用户名/密码作为凭据时可以正常工作,密码通过蓝牙传输。 毕竟这并不难,因为文档会告诉您要实现哪些接口。

现在我想更改凭据以改用证书。我看到我应该为此使用 KERB_CERTIFICATE_LOGON 结构。深入研究该主题,我发现我应该实现一个自定义密钥存储提供程序,如this article by microsoft 中所述。这就是我迷失的地方。也许我愚蠢地搜索正确的文档,但我只是找不到我必须实现哪些接口才能拥有可以在 KERB_CERTIFICATE_LOGON 结构的 CspData 字段中引用的 KSP。我只是找到了一堆方法,然后快速搜索 NCRYPT_CERTIFICATE_PROPERTY(在上面链接的文章中提到)在 google 中发现了惊人的 两个 结果。

我找到了this SO-question,它可以帮助我在拥有 credentialprovider 和 KSP 时连接它,但它没有解释如何编写 KSP。 :-(

谁能指导我在哪里找到信息或显示在类似场景中使用的 KSP 的简短示例(只是方法声明,以及如何在对 KERB_CERTIFICATE_LOGON 的调用中使用生成的 KSP)?

【问题讨论】:

    标签: c++ kerberos winlogon credential-providers cng


    【解决方案1】:

    这个Microsoft Cryptographic Provider Development Kit 有一个很好的 KSP 样本。

    除了这个示例之外,您唯一需要做的就是实现SmartCardKeyCertificate 属性。 代码将是这样的:

    SECURITY_STATUS
    WINAPI
    KSPGetKeyProperty(
    __in    NCRYPT_PROV_HANDLE hProvider,
    __in    NCRYPT_KEY_HANDLE hKey,
    __in    LPCWSTR pszProperty,
    __out_bcount_part_opt(cbOutput, *pcbResult) PBYTE pbOutput,
    __in    DWORD   cbOutput,
    __out   DWORD * pcbResult,
    __in    DWORD   dwFlags)
    {
    
        ...
    
        if (wcscmp(pszProperty, NCRYPT_CERTIFICATE_PROPERTY) == 0)
        {
            dwProperty = SAMPLEKSP_CERTIFICATE_PROPERTY;
            cbResult = GetCertificateSize(); //the size of the certificate that is associated with the key
        }
    
        *pcbResult = cbResult;
        if(pbOutput == NULL)
        {
            Status = ERROR_SUCCESS;
            goto cleanup;
        }
    
        if(cbOutput < *pcbResult)
        {
             Status = NTE_BUFFER_TOO_SMALL;
             goto cleanup;
        }
    
        if (wcscmp(pszProperty, NCRYPT_CERTIFICATE_PROPERTY) == 0)
        {
            CopyMemory(pbOutput, crt, sizeof(crt));
        }
    
    
        switch(dwProperty)
        {
        case SAMPLEKSP_CERTIFICATE_PROPERTY:
            CopyMemory(pbOutput, GetCertificate(), cbResult); //Copy to pbOutput the certificate in binary form 
            break;
    
        ...
    
        }
    
        ...
    
    }
    

    实现 KSP 并注册后,您的 CredentialProvider 可以与之交互:

    HRESULT MyCredential::GetSerialization(
        CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE* pcpgsr,
        CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs,
        PWSTR* ppwszOptionalStatusText,
        CREDENTIAL_PROVIDER_STATUS_ICON* pcpsiOptionalStatusIcon
        )
    {
    
        ...
    
        ULONG ulAuthPackage;
        HRESULT hr = RetrieveNegotiateAuthPackage(&ulAuthPackage);
        ConstructAuthInfo(&pcpcs->rgbSerialization, &pcpcs->cbSerialization);
    
        if (SUCCEEDED(hr))
        {
            pcpcs->ulAuthenticationPackage = ulAuthPackage;
            pcpcs->clsidCredentialProvider = CLSID_MyCredentialProvider;
    
            // At this point the credential has created the serialized credential used for logon
            // By setting this to CPGSR_RETURN_CREDENTIAL_FINISHED we are letting logonUI know
            // that we have all the information we need and it should attempt to submit the 
            // serialized credential.
            *pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED;
        }
    
        return hr;
    }
    
    HRESULT RetrieveNegotiateAuthPackage(ULONG * pulAuthPackage)
    {
        HRESULT hr = S_OK;
        HANDLE hLsa = NULL;
    
        NTSTATUS status = LsaConnectUntrusted(&hLsa);
        if (SUCCEEDED(HRESULT_FROM_NT(status)))
        {
    
            ULONG ulAuthPackage;
            LSA_STRING lsaszKerberosName;
            LsaInitString(&lsaszKerberosName, MICROSOFT_KERBEROS_NAME_A);
            status = LsaLookupAuthenticationPackage(hLsa, &lsaszKerberosName, &ulAuthPackage);
            if (SUCCEEDED(HRESULT_FROM_NT(status)))
            {
                *pulAuthPackage = ulAuthPackage;
                hr = S_OK;
            }
            else
            {
                hr = HRESULT_FROM_NT(status);
            }
            LsaDeregisterLogonProcess(hLsa);
        }
        else
        {
            hr = HRESULT_FROM_NT(status);
        }
    
        return hr;
    }
    
    void ConstructAuthInfo(LPBYTE* ppbAuthInfo, ULONG *pulAuthInfoLen)
    {
        WCHAR szCardName[] = L""; // no card name specified but you can put one if you want
        WCHAR szContainerName[] = L"my_key_name";
        WCHAR szReaderName[] = L"";
        WCHAR szCspName[] = L"My Key Storage Provider";
        WCHAR szPin[] = L"11111111";
        ULONG ulPinByteLen = wcslen(szPin) * sizeof(WCHAR);
        WCHAR szUserName[] = L"user";
        ULONG ulUserByteLen = wcslen(szUserName) * sizeof(WCHAR);
        WCHAR szDomainName[] = L"testdomain.com";
        ULONG ulDomainByteLen = 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 = 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-pbAuthInfo);
        pKerbCertLogon->UserName.Length = (USHORT)ulUserByteLen;
        pKerbCertLogon->UserName.MaximumLength = (USHORT)(ulUserByteLen + sizeof(WCHAR));
        pKerbCertLogon->UserName.Buffer = (PWSTR)(pbUserBuffer-pbAuthInfo);
        pKerbCertLogon->Pin.Length = (USHORT)ulPinByteLen;
        pKerbCertLogon->Pin.MaximumLength = (USHORT)(ulPinByteLen + sizeof(WCHAR));
        pKerbCertLogon->Pin.Buffer = (PWSTR)(pbPinBuffer-pbAuthInfo);
    
        pKerbCertLogon->CspDataLength = ulCspDataLen;
        pKerbCertLogon->CspData = (PUCHAR)(pbCspData-pbAuthInfo);
    
        pKerbCspInfo = (KERB_SMARTCARD_CSP_INFO*)pbCspData;
        pKerbCspInfo->dwCspInfoLen = ulCspDataLen;
        pKerbCspInfo->MessageType = 1;
        pKerbCspInfo->KeySpec = CERT_NCRYPT_KEY_SPEC;
    
        pKerbCspInfo->nCardNameOffset = 0;
        pKerbCspInfo->nReaderNameOffset = pKerbCspInfo->nCardNameOffset + wcslen(szCardName) + 1;
        pKerbCspInfo->nContainerNameOffset = pKerbCspInfo->nReaderNameOffset + wcslen(szReaderName) + 1;
        pKerbCspInfo->nCSPNameOffset = pKerbCspInfo->nContainerNameOffset + 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));
    
        *ppbAuthInfo = pbAuthInfo;
        *pulAuthInfoLen = ulAuthInfoLen;
    }
    

    这段代码主要有两点:

    1. 为了与 KSP 交互,pKerbCspInfo->KeySpec 必须是 CERT_NCRYPT_KEY_SPEC。
    2. 为了正确的结构序列化,根据this回答:

      pKerbCertLogon->DomainName.Buffer = (PWSTR)(pbDomainBuffer-pbAuthInfo); pKerbCertLogon->UserName.Buffer = (PWSTR)(pbUserBuffer-pbAuthInfo); pKerbCertLogon->Pin.Buffer = (PWSTR)(pbPinBuffer-pbAuthInfo); pKerbCertLogon->CspData = (PUCHAR)(pbCspData-pbAuthInfo);

    域控制器也应该使用“Kerberos 身份验证”模板颁发证书。

    【讨论】:

    • 非常感谢!与此同时,我终于在 CNG-Kit 中找到了样本。但是我无法让它工作。按照您的建议将 KeySpec 更改为 CERT_NCRYPT_KEY_SPEC 就可以了。
    • 还有一个问题:是否可以将提供程序移植到 c++?我试过了,KSP 的注册确实有效。但是,在登录过程中似乎没有调用 c++ 版本。我认为这与 GetKeyStorageInterface 的导出有关,但我不知道我必须进行哪些更改才能使其正常工作。
    • @Frank 我不认为使用 C 或 C ++ 有区别。究竟是什么不起作用?您的 c++ 提供程序是否在其他场景中工作?例如:NCryptOpenStorageProvider(...); NCryptOpenKey(...);
    • 这是我的错,而且是一个非常愚蠢的错误。我没有注意到在 VisualStudio 中选择了错误的平台(WIN32)。将其更改为 x64 即可正常工作。 :-)
    猜你喜欢
    • 2022-08-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-19
    • 2014-07-29
    • 1970-01-01
    相关资源
    最近更新 更多