Reputation: 159
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.
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
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
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