How to Build Custom Logon UI’s in Windows Vista

Hi, Rajesh Gopisetty here. I am the India dev lead for the Information Security Tools team.

The blog post discusses the authentication model in Vista and how enterprise can use it to build custom logon UI’s.

Prior to Windows Vista, to log on to 3rd party servers or by 3rd party devices, ISVs need to replace the Graphical Identification and Authentication dynamic-link library (GINA) in Windows XP. For example, in order to authenticate a Windows PC with a Novell server, Novell needs to hook into the authentication process (to route the credentials to the Novell server). Just to do this small task, Novell and other ISVs are forced to replace all the existing UI and re-implement features, such as smart card support and remote desktop.

Windows Vista introduces a new authentication model where LogonUI and Winlogon talk directly with each other. A credential provider is a module that plugs into LogonUI that handles describing the credential information it requires for LogonUI to render and to also communicate with an external authentication provider. After the credential provider gathers the credential information and gets its callback (if needed) from an external authentication provider, it packages the final credentials for interactive logon for LogonUI to submit to Winlogon. As such, credential providers are completely transparent to Winlogon.

Here at Microsoft IT we have implemented a feature called Self Service Pin Unblock in a project named Microsoft Smart Access Manager (referred as MSAM in the blog) where we have used the credentials provider extensively. MSAM feature of Self service Pin unblock system and enables the user to reset his smart card Pin from the logon screen of Vista by answering a set of security questions which the user selects during registration process.

In this blog I will walk you through on building the below shown logon user interface using windows Vista SDK and Implementing ICredentialProvider interface

image

The CPP class (Reffered as MSAMSSPinUnblockProvider in the below code) implements ICredentialProvider.

 // interface that logonUI uses to decide which tiles to display.
// In this sample, we will display one tile that uses each of the nine

// available UI controls.
#include "MSAMSSPinUnblockProvider.h"

#include "guid.h"
// MSAMSSPinUnblockProvider 
MSAMSSPinUnblockProvider::MSAMSSPinUnblockProvider():
    _cRef(1)
{
    DllAddRef();
    _pCredential = NULL;
}
MSAMSSPinUnblockProvider::~MSAMSSPinUnblockProvider()
{
    if (_pCredential != NULL)
    {
        _pCredential->Release();
        _pCredential = NULL;
    }
    DllRelease();
}
// SetUsageScenario is the provider's cue that it's going to be asked for tiles

// in a subsequent call. In this sample we have chosen to precreate the credential 

// for the usage scenario passed in cpus instead of saving off cpus and only creating

// the credential when we're asked to.

HRESULT MSAMSSPinUnblockProvider::SetUsageScenario(
    CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
    DWORD dwFlags
    )
{
    UNREFERENCED_PARAMETER(dwFlags);
    HRESULT hr;
    // Decide which scenarios to support here. Returning E_NOTIMPL simply tells the caller
    // that we're not designed for that scenario.
    switch (cpus)
    {
 case CPUS_PLAP: //Add PLAP usage scenario.
    case CPUS_LOGON:
    case CPUS_UNLOCK_WORKSTATION:       
        // Create and initialize our credential.
        // A more advanced credprov might only enumerate tiles for the user whose owns the locked
        // session, since those are the only creds that wil work
     {
            
         
         Logging::ILoggerPtr logger = NULL;
           HRESULT hr = GetLogger(logger);
          
         if(!SUCCEEDED(hr))
           {
                //::MessageBox(NULL,"Getlogger failed","GetLogger failed", 0);
         }
            DWORD staticControlCount = 0;
            bool _computerInCorpNet = false;
         bool _computerHasInternetConnection = false;
         _pCredential = new MSAMSSPinUnblockCredential();
         
                 
         ControlDetails* cDetails = new ControlDetails();
         HRESULT hrr;
         DWORD maxNumberOfQuestions = 5;
          hrr = PopulateInitializingVaiables(&(this->tempCpfd),&(this->tempFieldStatePair),cDetails,logger,maxNumberOfQuestions);
            
         
          
            if (_pCredential != NULL)
            {
                            
             hr = _pCredential->Initialize(this->tempCpfd,this->tempFieldStatePair,cDetails->numberOfStaticControls,logger,maxNumberOfQuestions);
             if (FAILED(hr))
              {
                    _pCredential->Release();
                  _pCredential = NULL;
             }
            }
            else
         {
                hr = E_OUTOFMEMORY;
          }
            }
            break;
        case CPUS_CHANGE_PASSWORD:
       case CPUS_CREDUI:
            hr = E_NOTIMPL;
          break;
        default:
         hr = E_INVALIDARG;
           break;
     }
    return hr;
}
// SetSerialization takes the kind of buffer that you would normally return to LogonUI for

// an authentication attempt.  It's the opposite of ICredentialProviderCredential::GetSerialization.

// GetSerialization is implement by a credential and serializes that credential.  Instead,

// SetSerialization takes the serialization and uses it to create a tile.
//
// SetSerialization is called for two main scenarios.  The first scenario is in the credui case

// where it is prepopulating a tile with credentials that the user chose to store in the OS.

// The second situation is in a remote logon case where the remote client may wish to 

// prepopulate a tile with a username, or in some cases, completely populate the tile and

// use it to logon without showing any UI.
//
STDMETHODIMP MSAMSSPinUnblockProvider::SetSerialization(
    const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs
    )
{
  
    UNREFERENCED_PARAMETER(pcpcs);
    return E_NOTIMPL;
}
// Called by LogonUI to give you a callback.  Providers often use the callback if they

// some event would cause them to need to change the set of tiles that they enumerated.

HRESULT MSAMSSPinUnblockProvider::Advise(
    ICredentialProviderEvents* pcpe,
    UINT_PTR upAdviseContext
    )
{
    UNREFERENCED_PARAMETER(pcpe);
    UNREFERENCED_PARAMETER(upAdviseContext);
    return E_NOTIMPL;
}
// Called by LogonUI when the ICredentialProviderEvents callback is no longer valid.

HRESULT MSAMSSPinUnblockProvider::UnAdvise()
{
    return E_NOTIMPL;
}
// Called by LogonUI to determine the number of fields in your tiles.  This

// does mean that all your tiles must have the same number of fields.

// This number must include both visible and invisible fields. If you want a tile

// to have different fields from the other tiles you enumerate for a given usage

// scenario you must include them all in this count and then hide/show them as desired 

// using the field descriptors.

HRESULT MSAMSSPinUnblockProvider::GetFieldDescriptorCount(
    DWORD* pdwCount
    )
{
  *pdwCount = this->TOTAL_NUMBER_OF_FIELDS;
    return S_OK;
}
// Gets the field descriptor for a particular field.

HRESULT MSAMSSPinUnblockProvider::GetFieldDescriptorAt(
    DWORD dwIndex, 
    CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR** ppcpfd
    )
{    
    HRESULT hr;
    // Verify dwIndex is a valid field.
 if ((dwIndex < this->TOTAL_NUMBER_OF_FIELDS) && ppcpfd)
    {
        hr = FieldDescriptorCoAllocCopy(this->tempCpfd[dwIndex], ppcpfd);
    }
    else
    { 
        hr = E_INVALIDARG;
    }
    return hr;
}
// Sets pdwCount to the number of tiles that we wish to show at this time.

// Sets pdwDefault to the index of the tile which should be used as the default.

// The default tile is the tile which will be shown in the zoomed view by default. If 

// more than one provider specifies a default the last used cred prov gets to pick 

// the default. If *pbAutoLogonWithDefault is TRUE, LogonUI will immediately call 

// GetSerialization on the credential you've specified as the default and will submit 

// that credential for authentication without showing any further UI.

HRESULT MSAMSSPinUnblockProvider::GetCredentialCount(
    DWORD* pdwCount,
    DWORD* pdwDefault,
    BOOL* pbAutoLogonWithDefault
    )
{
    *pdwCount = 1;
    *pdwDefault = CREDENTIAL_PROVIDER_NO_DEFAULT;//0;
    *pbAutoLogonWithDefault = FALSE;
  
    return S_OK;
}
// Returns the credential at the index specified by dwIndex. This function is called by logonUI to enumerate

// the tiles.

HRESULT MSAMSSPinUnblockProvider::GetCredentialAt(
    DWORD dwIndex, 
    ICredentialProviderCredential** ppcpc
    )
{
    HRESULT hr;
    if((dwIndex == 0) && ppcpc)
    {
      hr = _pCredential->QueryInterface(IID_IConnectableCredentialProviderCredential, reinterpret_cast<void**>(ppcpc));
    }
    else
    {
        hr = E_INVALIDARG;
    }
    return hr;
}
// Boilerplate code to create our provider.

HRESULT MSAMSSPinUnblockProvider_CreateInstance(REFIID riid, void** ppv)
{
    HRESULT hr;
    MSAMSSPinUnblockProvider* pProvider = new MSAMSSPinUnblockProvider();
    if (pProvider)
    {
        hr = pProvider->QueryInterface(riid, ppv);
        pProvider->Release();
    }
    else
    {
        hr = E_OUTOFMEMORY;
    }
    
    return hr;
}

In the next part of the my blog we’ll take a look at implementing smart card authentication using Credential Provider.