none
Getting NLA credentials in a Credential Provider

    Question

  • Hi,

    I'm currently working on a credential provider (which is almost finished) but I can't find any info/documentation about how to retrieve the credentials that were provided by NLA when connecting with Remote Desktop. 

    I know for a fact it's possible to get NLA credentials, because I have seen closed source examples where this works (e.g. http://blog.scorpionsoft.com/blog/2011/02/updated-credential-provider.html). I think you need SetSerialization for it but I can't find any information with it regarding NLA. I tried some open source credprovs but they don't support using NLA credentials.

    Please help :)

    Thursday, August 11, 2016 1:16 AM

Answers

  • After spending a lot more time on this, I figured out that it's indeed in SetSerialization that should be called with credentials. But this never happened. Then I found out you kind of need to hack the CredProvFilter like this:

    HRESULT CEAAAProviderFilter::UpdateRemoteCredential(const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcsIn, CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcsOut) { if (!pcpcsIn) // no point continuing has there are no credentials return E_NOTIMPL; pcpcsOut->ulAuthenticationPackage = pcpcsIn->ulAuthenticationPackage; pcpcsOut->cbSerialization = pcpcsIn->cbSerialization; pcpcsOut->rgbSerialization = pcpcsIn->rgbSerialization;

    // CLSID SHOULD BE DEFINED LIKE THIS:
    // {BF6C0870-5051-4DA1-B636-37B13F99C7D9}
    // DEFINE_GUID(CLSID_MyCredProvider,
    //    0xbf6c0870, 0x5051, 0x4da1, 0xb6, 0x36, 0x37, 0xb1, 0x3f, 0x99, 0xc7, 0xd9);

    pcpcsOut->clsidCredentialProvider = CLSID_MyCredProvider; // if I need to copy the buffer contents I will use: if (pcpcsOut->cbSerialization > 0 && (pcpcsOut->rgbSerialization = (BYTE*)CoTaskMemAlloc(pcpcsIn->cbSerialization)) != NULL) { CopyMemory(pcpcsOut->rgbSerialization, pcpcsIn->rgbSerialization, pcpcsIn-> cbSerialization); return S_OK; } else return E_NOTIMPL; }

    Source: https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/4134d896-85f5-460e-b621-c3b8acf3a1c9/credential-provider-icredentialprovidersetserialization-is-never-called?forum=windowsgeneraldevelopmentissues

    Now I get the username and password. But the password is encrypted somehow. How do I decrypt it?


    Thursday, August 11, 2016 10:18 AM
  • Fixed decryption as well, you need CredUnprotectW for this.
    FYI:

    // DECRYPT serializedPass (PWSTR)
    PWSTR pwzProtectedPassword;
    hr = SHStrDupW(serializedPass, &pwzProtectedPassword);
    if (SUCCEEDED(hr))
    {
    	UnProtectIfNecessaryAndCopyPassword(pwzProtectedPassword, &serializedPass);
    }
    CoTaskMemFree(pwzProtectedPassword);
    And:
    //
    // If pwzPassword should be decrypted, return a copy decrypted with CredProtect.
    // 
    // If not, just return a copy.
    //
    HRESULT UnProtectIfNecessaryAndCopyPassword(
    	__in PCWSTR pwzPassword,
    	__deref_out PWSTR* ppwzUnProtectedPassword
    	)
    {
    	*ppwzUnProtectedPassword = NULL;
    
    	HRESULT hr;
    
    	// ProtectAndCopyString is intended for non-empty strings only.  Empty passwords
    	// do not need to be encrypted.
    	if (pwzPassword && *pwzPassword)
    	{
    		// pwzPassword is const, but CredIsProtected takes a non-const string.
    		// So, make a copy that we know isn't const.
    		PWSTR pwzPasswordCopy;
    		hr = SHStrDupW(pwzPassword, &pwzPasswordCopy);
    		if (SUCCEEDED(hr))
    		{
    			bool bCredAlreadyDecrypted = false;
    			CRED_PROTECTION_TYPE protectionType;
    
    			// If the password is already encrypted, we should not encrypt it again.
    			// An encrypted password may be received through SetSerialization in the 
    			// CPUS_LOGON scenario during a Terminal Services connection, for instance.
    			if (CredIsProtectedW(pwzPasswordCopy, &protectionType))
    			{
    				if (CredUnprotected == protectionType)
    				{
    					bCredAlreadyDecrypted = true;
    				}
    			}
    
    			if (bCredAlreadyDecrypted)
    			{
    				hr = SHStrDupW(pwzPasswordCopy, ppwzUnProtectedPassword);
    			}
    			else
    			{
    				// Unprotect / descrypt the password
    				hr = _UnProtectAndCopyString(pwzPasswordCopy, ppwzUnProtectedPassword);
    			}
    
    			CoTaskMemFree(pwzPasswordCopy);
    		}
    	}
    	else
    	{
    		hr = SHStrDupW(L"", ppwzUnProtectedPassword);
    	}
    
    	return hr;
    }
    
    //
    // Return a copy of pwzToProtect decrypted with the CredProtect API.
    //
    // pwzToProtect must not be NULL or the empty string.
    //
    static HRESULT _UnProtectAndCopyString(
        __in PCWSTR pwzToUnProtect,
        __deref_out PWSTR* ppwzUnProtected
        )
    {
        *ppwzUnProtected = NULL;

        // pwzToProtect is const, but CredProtect takes a non-const string.
        // So, make a copy that we know isn't const.
        PWSTR pwzToUnProtectCopy;
        HRESULT hr = SHStrDupW(pwzToUnProtect, &pwzToUnProtectCopy);
        if (SUCCEEDED(hr))
        {
            // The first call to CredProtect determines the length of the encrypted string.
            // Because we pass a NULL output buffer, we expect the call to fail.
            //
            // Note that the third parameter to CredProtect, the number of characters of pwzToProtectCopy
            // to encrypt, must include the NULL terminator!
            DWORD cchUnProtected = 0;
            if (!CredUnprotectW(FALSE, pwzToUnProtectCopy, (DWORD)wcslen(pwzToUnProtectCopy) + 1, NULL, &cchUnProtected))
            {
                DWORD dwErr = GetLastError();

                if ((ERROR_INSUFFICIENT_BUFFER == dwErr) && (0 < cchUnProtected))
                {
                    // Allocate a buffer long enough for the encrypted string.
                    PWSTR pwzUnProtected = (PWSTR)CoTaskMemAlloc(cchUnProtected * sizeof(WCHAR));
                    if (pwzUnProtected)
                    {
                        // The second call to CredProtect actually encrypts the string.
                        if (CredUnprotectW(FALSE, pwzToUnProtectCopy, (DWORD)wcslen(pwzToUnProtectCopy) + 1, pwzUnProtected, &cchUnProtected))
                        {
                            *ppwzUnProtected = pwzUnProtected;
                            hr = S_OK;
                        }
                        else
                        {
                            CoTaskMemFree(pwzUnProtected);

                            dwErr = GetLastError();
                            hr = HRESULT_FROM_WIN32(dwErr);
                        }
                    }
                    else
                    {
                        hr = E_OUTOFMEMORY;
                    }
                }
                else
                {
                    hr = HRESULT_FROM_WIN32(dwErr);
                }
            }

            CoTaskMemFree(pwzToUnProtectCopy);
        }

        return hr;
    }





    Thursday, August 11, 2016 2:32 PM

All replies

  • After spending a lot more time on this, I figured out that it's indeed in SetSerialization that should be called with credentials. But this never happened. Then I found out you kind of need to hack the CredProvFilter like this:

    HRESULT CEAAAProviderFilter::UpdateRemoteCredential(const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcsIn, CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcsOut) { if (!pcpcsIn) // no point continuing has there are no credentials return E_NOTIMPL; pcpcsOut->ulAuthenticationPackage = pcpcsIn->ulAuthenticationPackage; pcpcsOut->cbSerialization = pcpcsIn->cbSerialization; pcpcsOut->rgbSerialization = pcpcsIn->rgbSerialization;

    // CLSID SHOULD BE DEFINED LIKE THIS:
    // {BF6C0870-5051-4DA1-B636-37B13F99C7D9}
    // DEFINE_GUID(CLSID_MyCredProvider,
    //    0xbf6c0870, 0x5051, 0x4da1, 0xb6, 0x36, 0x37, 0xb1, 0x3f, 0x99, 0xc7, 0xd9);

    pcpcsOut->clsidCredentialProvider = CLSID_MyCredProvider; // if I need to copy the buffer contents I will use: if (pcpcsOut->cbSerialization > 0 && (pcpcsOut->rgbSerialization = (BYTE*)CoTaskMemAlloc(pcpcsIn->cbSerialization)) != NULL) { CopyMemory(pcpcsOut->rgbSerialization, pcpcsIn->rgbSerialization, pcpcsIn-> cbSerialization); return S_OK; } else return E_NOTIMPL; }

    Source: https://social.msdn.microsoft.com/Forums/windowsdesktop/en-US/4134d896-85f5-460e-b621-c3b8acf3a1c9/credential-provider-icredentialprovidersetserialization-is-never-called?forum=windowsgeneraldevelopmentissues

    Now I get the username and password. But the password is encrypted somehow. How do I decrypt it?


    Thursday, August 11, 2016 10:18 AM
  • Fixed decryption as well, you need CredUnprotectW for this.
    FYI:

    // DECRYPT serializedPass (PWSTR)
    PWSTR pwzProtectedPassword;
    hr = SHStrDupW(serializedPass, &pwzProtectedPassword);
    if (SUCCEEDED(hr))
    {
    	UnProtectIfNecessaryAndCopyPassword(pwzProtectedPassword, &serializedPass);
    }
    CoTaskMemFree(pwzProtectedPassword);
    And:
    //
    // If pwzPassword should be decrypted, return a copy decrypted with CredProtect.
    // 
    // If not, just return a copy.
    //
    HRESULT UnProtectIfNecessaryAndCopyPassword(
    	__in PCWSTR pwzPassword,
    	__deref_out PWSTR* ppwzUnProtectedPassword
    	)
    {
    	*ppwzUnProtectedPassword = NULL;
    
    	HRESULT hr;
    
    	// ProtectAndCopyString is intended for non-empty strings only.  Empty passwords
    	// do not need to be encrypted.
    	if (pwzPassword && *pwzPassword)
    	{
    		// pwzPassword is const, but CredIsProtected takes a non-const string.
    		// So, make a copy that we know isn't const.
    		PWSTR pwzPasswordCopy;
    		hr = SHStrDupW(pwzPassword, &pwzPasswordCopy);
    		if (SUCCEEDED(hr))
    		{
    			bool bCredAlreadyDecrypted = false;
    			CRED_PROTECTION_TYPE protectionType;
    
    			// If the password is already encrypted, we should not encrypt it again.
    			// An encrypted password may be received through SetSerialization in the 
    			// CPUS_LOGON scenario during a Terminal Services connection, for instance.
    			if (CredIsProtectedW(pwzPasswordCopy, &protectionType))
    			{
    				if (CredUnprotected == protectionType)
    				{
    					bCredAlreadyDecrypted = true;
    				}
    			}
    
    			if (bCredAlreadyDecrypted)
    			{
    				hr = SHStrDupW(pwzPasswordCopy, ppwzUnProtectedPassword);
    			}
    			else
    			{
    				// Unprotect / descrypt the password
    				hr = _UnProtectAndCopyString(pwzPasswordCopy, ppwzUnProtectedPassword);
    			}
    
    			CoTaskMemFree(pwzPasswordCopy);
    		}
    	}
    	else
    	{
    		hr = SHStrDupW(L"", ppwzUnProtectedPassword);
    	}
    
    	return hr;
    }
    
    //
    // Return a copy of pwzToProtect decrypted with the CredProtect API.
    //
    // pwzToProtect must not be NULL or the empty string.
    //
    static HRESULT _UnProtectAndCopyString(
        __in PCWSTR pwzToUnProtect,
        __deref_out PWSTR* ppwzUnProtected
        )
    {
        *ppwzUnProtected = NULL;

        // pwzToProtect is const, but CredProtect takes a non-const string.
        // So, make a copy that we know isn't const.
        PWSTR pwzToUnProtectCopy;
        HRESULT hr = SHStrDupW(pwzToUnProtect, &pwzToUnProtectCopy);
        if (SUCCEEDED(hr))
        {
            // The first call to CredProtect determines the length of the encrypted string.
            // Because we pass a NULL output buffer, we expect the call to fail.
            //
            // Note that the third parameter to CredProtect, the number of characters of pwzToProtectCopy
            // to encrypt, must include the NULL terminator!
            DWORD cchUnProtected = 0;
            if (!CredUnprotectW(FALSE, pwzToUnProtectCopy, (DWORD)wcslen(pwzToUnProtectCopy) + 1, NULL, &cchUnProtected))
            {
                DWORD dwErr = GetLastError();

                if ((ERROR_INSUFFICIENT_BUFFER == dwErr) && (0 < cchUnProtected))
                {
                    // Allocate a buffer long enough for the encrypted string.
                    PWSTR pwzUnProtected = (PWSTR)CoTaskMemAlloc(cchUnProtected * sizeof(WCHAR));
                    if (pwzUnProtected)
                    {
                        // The second call to CredProtect actually encrypts the string.
                        if (CredUnprotectW(FALSE, pwzToUnProtectCopy, (DWORD)wcslen(pwzToUnProtectCopy) + 1, pwzUnProtected, &cchUnProtected))
                        {
                            *ppwzUnProtected = pwzUnProtected;
                            hr = S_OK;
                        }
                        else
                        {
                            CoTaskMemFree(pwzUnProtected);

                            dwErr = GetLastError();
                            hr = HRESULT_FROM_WIN32(dwErr);
                        }
                    }
                    else
                    {
                        hr = E_OUTOFMEMORY;
                    }
                }
                else
                {
                    hr = HRESULT_FROM_WIN32(dwErr);
                }
            }

            CoTaskMemFree(pwzToUnProtectCopy);
        }

        return hr;
    }





    Thursday, August 11, 2016 2:32 PM