KSP (поставщик хранилища ключей) не загружается при входе в систему через поставщика учетных данных

Я создаю Windows Credential Provider для входа в домен Windows с использованием сертификатов, как описано в этой статье. Это подразумевает создание пользовательского KSP, который будет вызываться LsaLogonUser при создании пакета аутентификации.

Мне удается создать пользовательский KSP и успешно протестировать его в отдельном приложении, которое напрямую вызывает LsaLogonUser. По сути, создаем пакет аутентификации и передаем его LsaLogonUser, загружаем KSP, вызываем кучу функций и аутентифицируем пользователя, возвращающего результат успеха по коду состояния и загруженному профилю пользователя.

Однако, когда я использую тот же пакет аутентификации во время GetSerialization для учетных данных, KSP даже не загружается, и я получаю 0xc000000d (неверный параметр), о котором сообщает 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;
}

Мой вопрос заключается в том, почему KSP загружается, когда пакет аутентификации вызывается из LsaLogonUser напрямую, а не из провайдера учетных данных во время входа в 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 ответ

Решение

Из документов Microsoft о структуре KERB_CERTIFICATE_LOGON:

Указатели, хранящиеся в элементах типа UNICODE_STRING, относятся к началу структуры и не являются абсолютными указателями памяти.

Но документация не говорит вам, что CspData Указатель также должен быть относительно начала структуры...

Когда вы звоните LsaLogonUser с данными с абсолютными указателями памяти это будет работать, даже когда указатели являются относительными. При сериализации данных будут работать только все относительные указатели.

Также иногда (в зависимости от CREDENTIAL_PROVIDER_USAGE_SCENARIO) вы должны использовать KERB_CERTIFICATE_UNLOCK_LOGON вместо KERB_CERTIFICATE_LOGON.

Другие вопросы по тегам