与自定义凭据提供程序的远程桌面连接

时间:2019-05-26 20:32:12

标签: c++ windows winapi remote-desktop credential-providers

我使用VistaCredentialProviderSamples中的SampleWrapExistingCredentialProvider开发了一个自定义凭据提供程序。凭据提供程序已实现过滤器,该筛选器可过滤所有其他凭据提供程序,并且在登录时我只能看到我的凭据提供程序。问题是,如果我们使用远程桌面连接连接到它,则用户名/密码不会从Windows RDP客户端传递到凭据提供程序,并且我必须在RDP会话打开时再次输入它(与默认提供程序的行为不同)

我正在尝试探索代码的哪一部分处理这种情况,即凭据提供者接受来自远程桌面客户端的用户名/密码,并且不再询问。附件是在RDP客户端上提供成功凭据后我的凭据提供程序的屏幕截图。单击凭据提供程序的此图标后,将显示凭据提供程序磁贴,再次询问用户名和密码。非常感谢您提供有关如何从RDP客户端接收凭据的任何帮助。This is the logon screen that appears after I enter username password on remote desktop connection

This appears after I select my CP. Username and password not filled up

我为CREDUI返回了S_OK。我的SetUsageScenario如下:

HRESULT CSampleProvider::SetUsageScenario(
CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
DWORD dwFlags
)
{
    HRESULT hr;

// Create the password credential provider and query its interface for an
// ICredentialProvider we can use. Once it's up and running, ask it about the 
// usage scenario being provided.
IUnknown *pUnknown = NULL;
hr = ::CoCreateInstance(CLSID_PasswordCredentialProvider, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pUnknown));
if (SUCCEEDED(hr))
{
    hr = pUnknown->QueryInterface(IID_PPV_ARGS(&(_pWrappedProvider)));
    if (SUCCEEDED(hr))
    {
        hr = _pWrappedProvider->SetUsageScenario(cpus, dwFlags);
        switch (cpus)
        {
        case CPUS_LOGON:
        case CPUS_UNLOCK_WORKSTATION:
        case CPUS_CREDUI:
        {
            hr = S_OK;
            break;
        }
        case CPUS_CHANGE_PASSWORD:
        default:
            hr = E_INVALIDARG;
            break;
        }
    }
}
if (FAILED(hr))
{
    if (_pWrappedProvider != NULL)
    {
        _pWrappedProvider->Release();
        _pWrappedProvider = NULL;
    }
}

return hr;
}

3 个答案:

答案 0 :(得分:0)

您最有可能在寻找ICredentialProvider::SetUsageScenario。 这是我自己的USBLogin的实现。您需要为CPUS_CREDUI返回S_OK:

HRESULT STDMETHODCALLTYPE MyCP::SetUsageScenario(
    /* [in] */ CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
    /* [in] */ DWORD dwFlags)
    {
    ...

    switch (cpus)
        {
        case CPUS_LOGON:
        case CPUS_UNLOCK_WORKSTATION:
            Scenario = cpus;
            hr = S_OK;
            break;

        case CPUS_CREDUI:
            {
            int CredUI = 1;//  xXML.GetRootElement()->FindElementZ("config",true)->FindVariableZ("credui",true)->GetValueInt();
            if (CredUI)
                {
                Scenario = cpus;
                hr = S_OK;
                }
            else
                {
                hr = E_NOTIMPL;
                }
            break;
            }

        case CPUS_CHANGE_PASSWORD:
            hr = E_NOTIMPL;
            break;
        default:
            hr = E_INVALIDARG;
            break;
        }
    return hr;
    }

答案 1 :(得分:0)

根据官方文档https:// RDC and Custom Credential Providers

  

如果用户连接了非Microsoft凭据提供程序,则   您将在终端服务器上提示您再次输入凭据   (两次)。如果未启用NLA,则尽管使用   连接之前客户端上不受支持的凭据提供程序,   用户仍将连接。您将被留在登录   屏幕上,您可以在其中使用受支持的任何凭据提供程序   用于本地身份验证。无法避免两者   使用不受支持的凭据提供程序时进行身份验证。

     

话虽如此,如果您有自己的凭证提供者并且   尝试与Vista盒进行远程桌面连接(具有此功能   凭据提供商),则您需要登录两次。这是一   预期的行为,这是设计使然,没有合法的方法   避免它。

答案 2 :(得分:0)

  

用户名/密码未从Windows RDP客户端传递到   凭证提供者,我必须在RDP会话时再次输入   打开(与默认提供程序的行为不同)

Windows无法通过魔术知道客户端的用户名/密码,而该用户名/密码是通过rdp连接的。

在客户端开始时,某些凭据提供程序必须创建CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION并将其传递给服务器。在其中clsidCredentialProvider中说出哪个具体提供程序收集此序列化。

CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION服务器必须做什么?显然将其传递给某些凭证提供者SetSerialization方法。但是为什么呢?对所有人 ?没有。再次仅适用于clsid与clsidCredentialProvider中的CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION完全匹配的提供者。此提供程序(如果存在且未过滤)必须记住该凭据,然后在被称为GetCredentialCount时说其具有dafault凭据(而不是CREDENTIAL_PROVIDER_NO_DEFAULT),并且通常可以使用此凭据进行自动登录。

从客户端(mstsc)的密码提供者创建序列化。因此将是__uuidof(PasswordCredentialProvider)中的__uuidof(V1PasswordCredentialProvider)clsidCredentialProvider(如果客户端在win7上运行)。

,但是您在自我过滤器中禁用了此提供程序。结果您自己中断了流程。

过滤器必须实现UpdateRemoteCredential方法。并在此处复制和更新通过了 pcpcsIn 。这是最重要的一部分,我们必须替换 clsidCredentialProvider 到自己的CLSID。结果将调用我们的SetSerialization方法。在这里,我们需要还原原始的 CLSID ,然后再将其传递给包装的凭证。

也很重要-在GetCredentialCount内-首先将其传递给包装的凭证,然后执行*pbAutoLogonWithDefault = FALSE;-禁用自动登录-如果您需要以下信息(OTP?),则不能执行此操作(自动登录)客户。

UpdateRemoteCredential内部的

方法中,如果声明为const,则不能修改 pcpcsIn 。因此我们需要将更新凭据写入 pcpcsOut 。因为系统无法知道rgbSerialization所需的大小,所以我们需要自己分配。然后系统将其释放。明显需要使用CoTaskMemAlloc来分配rgbSerialization

所以-所有这些都可以理解,而无需任何文档。但是,如果所有这些都记录在案-也不会太糟。

UpdateRemoteCredential编写代码:

HRESULT STDMETHODCALLTYPE CSampleProvider::UpdateRemoteCredential( 
    /* [annotation][in] */ 
    _In_  const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcsIn,
    /* [annotation][out] */ 
    _Out_  CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcsOut)
{

    if (pcpcsIn->clsidCredentialProvider != __uuidof(PasswordCredentialProvider) && 
        pcpcsIn->clsidCredentialProvider != __uuidof(V1PasswordCredentialProvider))
    {
        // we dont know format of serialization
        return E_UNEXPECTED;
    }

    ULONG cbSerialization = pcpcsIn->cbSerialization;

    if (pcpcsOut->rgbSerialization = (PBYTE)CoTaskMemAlloc(cbSerialization + sizeof(GUID)))
    {
        memcpy(pcpcsOut->rgbSerialization, pcpcsIn->rgbSerialization, cbSerialization);
        memcpy(pcpcsOut->rgbSerialization + cbSerialization, &pcpcsIn->clsidCredentialProvider, sizeof(GUID));

        pcpcsOut->cbSerialization = cbSerialization + sizeof(GUID);
        pcpcsOut->ulAuthenticationPackage = pcpcsIn->ulAuthenticationPackage;
        pcpcsOut->clsidCredentialProvider = __uuidof(CSampleProvider);

        return S_OK;
    }

    return E_OUTOFMEMORY;
}

如果我们不知道clsidCredentialProvider-只需返回E_UNEXPECTED

否则,请分配更多(在sizeof(CLSID)上)内存,最后保存原始clsidCredentialProvider

现在SetSerialization

HRESULT STDMETHODCALLTYPE CSampleProvider::SetSerialization(
    __in const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs
    )
{   
    if (pcpcs->clsidCredentialProvider == __uuidof(CSampleProvider))
    {
        // can not query WTSIsRemoteSession, small optimization
        _IsRemoteSession = true;

        // we got this via ICredentialProviderFilter::UpdateRemoteCredential
        ULONG cbSerialization = pcpcs->cbSerialization;

        if (cbSerialization >= sizeof(GUID))
        {
            // restore original clsidCredentialProvider
            cbSerialization -= sizeof(GUID);
            memcpy(const_cast<GUID*>(&pcpcs->clsidCredentialProvider), pcpcs->rgbSerialization + cbSerialization, sizeof(GUID));
            const_cast<CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION*>(pcpcs)->cbSerialization = cbSerialization;
        }
    }

    return _pWrappedProvider->SetSerialization(pcpcs);
}

还原原始clsidCredentialProvider并修复cbSerialization。也因为在我的情况下pcpcs->clsidCredentialProvider == __uuidof(CSampleProvider)只能在UpdateRemoteCredential内设置(对于RDP,我不能在客户端使用CPUS_CREDUI,而只能对“以管理员身份运行”)–我只知道这是远程连接并保存此信息(_IsRemoteSession = true;)以便不调用WTSIsRemoteSession

最后GetCredentialCount

HRESULT STDMETHODCALLTYPE CSampleProvider::GetCredentialCount(
    __out DWORD* pdwCount,
    __out_range(<,*pdwCount) DWORD* pdwDefault,
    __out BOOL* pbAutoLogonWithDefault
    )
{
    HRESULT hr = _pWrappedProvider->GetCredentialCount(pdwCount, pdwDefault, pbAutoLogonWithDefault);

    *pbAutoLogonWithDefault = FALSE;//!!!

    return hr;
}

请注意非常重要的*pbAutoLogonWithDefault = FALSE;//!!!