js.hrt
js.hrt

Reputation: 159

Remote Desktop Connection with Custom Credential Provider

I have developed a custom credential provider using the SampleWrapExistingCredentialProvider from VistaCredentialProviderSamples. The credential provider has a filter implemented that filters all other credential providers and I see just my credential provider at the time of logon. The issue is that if we connect to it using remote desktop connection, the username/password are not passed from windows RDP client to the Credential provider and I have to enter it again when RDP session opens (unlike the behavior with default provider)

I am trying to explore which part of the code handles this scenario that the credential provider accepts the username/password from remote desktop client and does not ask again. Attached is the screenshot of my credential provider after providing successful credentials on the RDP client. After I click this icon of my credential provider, I am shown the credential provider tile that asking again for the username and password. Any help would be highly appreciated on how to receive credentials from RDP client.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

I have returned S_OK for CREDUI. My SetUsageScenario is as follows:

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;
}

Upvotes: 2

Views: 4973

Answers (2)

RbMm
RbMm

Reputation: 33744

the username/password are not passed from windows RDP client to the Credential provider and I have to enter it again when RDP session opens (unlike the behavior with default provider)

windows can not by some magic know client username/password, which connecting by rdp.

at begin on client side some credential provider must create CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION and pass it to server. inside it clsidCredentialProvider say which concrete provider collect this serialization.

what server must do with this CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION ? obvious pass it to some credential provider SetSerialization method. but for which ? for all ? no. again obvivous only for provider which clsid exactly matches clsidCredentialProvider from CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION. this provider (if exist and not filtered) must remember this credentional and then when called GetCredentialCount - say that it have dafault credentional (not CREDENTIAL_PROVIDER_NO_DEFAULT) and it usually ready for auto logon attempt with this.

from client side (mstsc) the password provider create serialization. so will be __uuidof(PasswordCredentialProvider) or __uuidof(V1PasswordCredentialProvider) (if client run on win7) in clsidCredentialProvider.

but you disable this providers in self filter. as result you yourself break process.

filter must implement UpdateRemoteCredential method. and here copy and update passed pcpcsIn. most importand part of this - we must replace clsidCredentialProvider to self CLSID. as result our SetSerialization method will be called. here we need restore original CLSID before pass it to wrapped credentional.

also important place - inside GetCredentialCount - first pass it to wrapped credentional and then do *pbAutoLogonWithDefault = FALSE; - disable autologon - you can not do this (autologon) if you require additional (OTP ?) information from client.

inside UpdateRemoteCredential method we can not modify pcpcsIn - if declared as const. so we need write our update credentional to pcpcsOut. because system can not know which size is required for rgbSerialization - we need allocate it yourself. and system then free it. obvivous need use CoTaskMemAlloc for allocate rgbSerialization.

so - all this can be understanded and without any documentation. however if all this was documented - will be not bad too.

so code for 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;
}

if we dont know the clsidCredentialProvider - simply return E_UNEXPECTED

oterwise allocate more (on sizeof(CLSID)) memory and save original clsidCredentialProvider in the end

now 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);
}

restore original clsidCredentialProvider and fix cbSerialization. also because pcpcs->clsidCredentialProvider == __uuidof(CSampleProvider) can be set only inside UpdateRemoteCredential in my case (i not do CPUS_CREDUI on client side for RDP, only for "run as admin") - i just know that this is remote connection and save this information (_IsRemoteSession = true;) for not call WTSIsRemoteSession

finally 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;
}

note very important *pbAutoLogonWithDefault = FALSE;//!!! line

Upvotes: 3

Strive Sun
Strive Sun

Reputation: 6289

According to official documentationhttps: RDC and Custom Credential Providers

If the user connected with a non-Microsoft credential provider, then you will be prompted on the terminal server to enter credentials again (twice). If NLA is not enabled, then despite entering using an unsupported credential provider on the client prior to the connection, the user will still be connected. You will be left at the logon screen, where you can use any credential provider that is supported for local authentication. There’s no way to avoid the two authentications when using unsupported credential providers.

Having said that, if you have your own credential providers and you try to do a remote desktop connection to a Vista box (having this Credential provider) then you would need to log-in twice. This is an expected behavior and it is by design and there is no legitimate way to avoid it.

Upvotes: 1

Related Questions