// // THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO // THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A // PARTICULAR PURPOSE. // // Copyright (c) Microsoft Corporation. All rights reserved. // // #ifndef WIN32_NO_STATUS #include #define WIN32_NO_STATUS #endif #include #include #include "CSampleCredential.h" #include "guid.h" // CSampleCredential //////////////////////////////////////////////////////// CSampleCredential::CSampleCredential(): _cRef(1), _pCredProvCredentialEvents(NULL) { DllAddRef(); ZeroMemory(_rgCredProvFieldDescriptors, sizeof(_rgCredProvFieldDescriptors)); ZeroMemory(_rgFieldStatePairs, sizeof(_rgFieldStatePairs)); ZeroMemory(_rgFieldStrings, sizeof(_rgFieldStrings)); } CSampleCredential::~CSampleCredential() { if (_rgFieldStrings[SFI_PASSWORD]) { size_t lenPassword = lstrlen(_rgFieldStrings[SFI_PASSWORD]); SecureZeroMemory(_rgFieldStrings[SFI_PASSWORD], lenPassword * sizeof(*_rgFieldStrings[SFI_PASSWORD])); } for (int i = 0; i < ARRAYSIZE(_rgFieldStrings); i++) { CoTaskMemFree(_rgFieldStrings[i]); CoTaskMemFree(_rgCredProvFieldDescriptors[i].pszLabel); } DllRelease(); } // Initializes one credential with the field information passed in. // Set the value of the SFI_USERNAME field to pwzUsername. HRESULT CSampleCredential::Initialize( __in CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus, __in const CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR* rgcpfd, __in const FIELD_STATE_PAIR* rgfsp, __in DWORD dwFlags, __in PCWSTR pwzUsername, __in PCWSTR pwzPassword ) { HRESULT hr = S_OK; _cpus = cpus; _dwFlags = dwFlags; // Copy the field descriptors for each field. This is useful if you want to vary the // field descriptors based on what Usage scenario the credential was created for. for (DWORD i = 0; SUCCEEDED(hr) && i < ARRAYSIZE(_rgCredProvFieldDescriptors); i++) { _rgFieldStatePairs[i] = rgfsp[i]; hr = FieldDescriptorCopy(rgcpfd[i], &_rgCredProvFieldDescriptors[i]); } // Initialize the String values of all the fields. if (SUCCEEDED(hr)) { hr = SHStrDupW(pwzUsername, &_rgFieldStrings[SFI_USERNAME]); } if (SUCCEEDED(hr)) { hr = SHStrDupW(pwzPassword ? pwzPassword : L"", &_rgFieldStrings[SFI_PASSWORD]); } if (SUCCEEDED(hr)) { hr = SHStrDupW(L"Submit", &_rgFieldStrings[SFI_SUBMIT_BUTTON]); } return S_OK; } // LogonUI calls this in order to give us a callback in case we need to notify it of anything. HRESULT CSampleCredential::Advise(__in ICredentialProviderCredentialEvents* pcpce) { if (_pCredProvCredentialEvents != NULL) { _pCredProvCredentialEvents->Release(); } _pCredProvCredentialEvents = pcpce; _pCredProvCredentialEvents->AddRef(); return S_OK; } // LogonUI calls this to tell us to release the callback. HRESULT CSampleCredential::UnAdvise() { if (_pCredProvCredentialEvents) { _pCredProvCredentialEvents->Release(); } _pCredProvCredentialEvents = NULL; return S_OK; } // LogonUI calls this function when our tile is selected (zoomed). // If you simply want fields to show/hide based on the selected state, // there's no need to do anything here - you can set that up in the // field definitions. But if you want to do something // more complicated, like change the contents of a field when the tile is // selected, you would do it here. HRESULT CSampleCredential::SetSelected(__out BOOL* pbAutoLogon) { *pbAutoLogon = FALSE; return S_OK; } // Similarly to SetSelected, LogonUI calls this when your tile was selected // and now no longer is. The most common thing to do here (which we do below) // is to clear out the password field. HRESULT CSampleCredential::SetDeselected() { HRESULT hr = S_OK; if (_rgFieldStrings[SFI_PASSWORD]) { size_t lenPassword = lstrlen(_rgFieldStrings[SFI_PASSWORD]); SecureZeroMemory(_rgFieldStrings[SFI_PASSWORD], lenPassword * sizeof(*_rgFieldStrings[SFI_PASSWORD])); CoTaskMemFree(_rgFieldStrings[SFI_PASSWORD]); hr = SHStrDupW(L"", &_rgFieldStrings[SFI_PASSWORD]); if (SUCCEEDED(hr) && _pCredProvCredentialEvents) { _pCredProvCredentialEvents->SetFieldString(this, SFI_PASSWORD, _rgFieldStrings[SFI_PASSWORD]); } } return hr; } // Gets info for a particular field of a tile. Called by logonUI to get information to // display the tile. HRESULT CSampleCredential::GetFieldState( __in DWORD dwFieldID, __out CREDENTIAL_PROVIDER_FIELD_STATE* pcpfs, __out CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE* pcpfis ) { HRESULT hr; // Validate paramters. if ((dwFieldID < ARRAYSIZE(_rgFieldStatePairs)) && pcpfs && pcpfis) { *pcpfs = _rgFieldStatePairs[dwFieldID].cpfs; *pcpfis = _rgFieldStatePairs[dwFieldID].cpfis; hr = S_OK; } else { hr = E_INVALIDARG; } return hr; } // Sets ppwz to the string value of the field at the index dwFieldID. HRESULT CSampleCredential::GetStringValue( __in DWORD dwFieldID, __deref_out PWSTR* ppwz ) { HRESULT hr; // Check to make sure dwFieldID is a legitimate index. if (dwFieldID < ARRAYSIZE(_rgCredProvFieldDescriptors) && ppwz) { // Make a copy of the string and return that. The caller // is responsible for freeing it. hr = SHStrDupW(_rgFieldStrings[dwFieldID], ppwz); } else { hr = E_INVALIDARG; } return hr; } // Gets the image to show in the user tile. HRESULT CSampleCredential::GetBitmapValue( __in DWORD dwFieldID, __out HBITMAP* phbmp ) { HRESULT hr; if ((SFI_TILEIMAGE == dwFieldID) && phbmp) { HBITMAP hbmp = LoadBitmap(HINST_THISDLL, MAKEINTRESOURCE(IDB_TILE_IMAGE)); if (hbmp != NULL) { hr = S_OK; *phbmp = hbmp; } else { hr = HRESULT_FROM_WIN32(GetLastError()); } } else { hr = E_INVALIDARG; } return hr; } // Sets pdwAdjacentTo to the index of the field the submit button should be // adjacent to. We recommend that the submit button is placed next to the last // field which the user is required to enter information in. Optional fields // should be below the submit button. HRESULT CSampleCredential::GetSubmitButtonValue( __in DWORD dwFieldID, __out DWORD* pdwAdjacentTo ) { HRESULT hr; // Validate parameters. if ((SFI_SUBMIT_BUTTON == dwFieldID) && pdwAdjacentTo) { // pdwAdjacentTo is a pointer to the fieldID you want the submit button to appear next to. *pdwAdjacentTo = SFI_PASSWORD; hr = S_OK; } else { hr = E_INVALIDARG; } return hr; } // Sets the value of a field which can accept a string as a value. // This is called on each keystroke when a user types into an edit field. HRESULT CSampleCredential::SetStringValue( __in DWORD dwFieldID, __in PCWSTR pwz ) { HRESULT hr; // Validate parameters. if (dwFieldID < ARRAYSIZE(_rgCredProvFieldDescriptors) && (CPFT_EDIT_TEXT == _rgCredProvFieldDescriptors[dwFieldID].cpft || CPFT_PASSWORD_TEXT == _rgCredProvFieldDescriptors[dwFieldID].cpft)) { PWSTR* ppwzStored = &_rgFieldStrings[dwFieldID]; CoTaskMemFree(*ppwzStored); hr = SHStrDupW(pwz, ppwzStored); } else { hr = E_INVALIDARG; } return hr; } //------------- // The following methods are for logonUI to get the values of various UI elements and then communicate // to the credential about what the user did in that field. However, these methods are not implemented // because our tile doesn't contain these types of UI elements HRESULT CSampleCredential::GetCheckboxValue( __in DWORD dwFieldID, __out BOOL* pbChecked, __deref_out PWSTR* ppwzLabel ) { UNREFERENCED_PARAMETER(dwFieldID); UNREFERENCED_PARAMETER(pbChecked); UNREFERENCED_PARAMETER(ppwzLabel); return E_NOTIMPL; } HRESULT CSampleCredential::GetComboBoxValueCount( __in DWORD dwFieldID, __out DWORD* pcItems, __out_range(<,*pcItems) DWORD* pdwSelectedItem ) { UNREFERENCED_PARAMETER(dwFieldID); UNREFERENCED_PARAMETER(pcItems); UNREFERENCED_PARAMETER(pdwSelectedItem); return E_NOTIMPL; } HRESULT CSampleCredential::GetComboBoxValueAt( __in DWORD dwFieldID, __in DWORD dwItem, __deref_out PWSTR* ppwzItem ) { UNREFERENCED_PARAMETER(dwFieldID); UNREFERENCED_PARAMETER(dwItem); UNREFERENCED_PARAMETER(ppwzItem); return E_NOTIMPL; } HRESULT CSampleCredential::SetCheckboxValue( __in DWORD dwFieldID, __in BOOL bChecked ) { UNREFERENCED_PARAMETER(dwFieldID); UNREFERENCED_PARAMETER(bChecked); return E_NOTIMPL; } HRESULT CSampleCredential::SetComboBoxSelectedValue( __in DWORD dwFieldId, __in DWORD dwSelectedItem ) { UNREFERENCED_PARAMETER(dwFieldId); UNREFERENCED_PARAMETER(dwSelectedItem); return E_NOTIMPL; } HRESULT CSampleCredential::CommandLinkClicked(__in DWORD dwFieldID) { UNREFERENCED_PARAMETER(dwFieldID); return E_NOTIMPL; } //------ end of methods for controls we don't have in our tile ----// // Collect the username and password into a serialized credential for the correct usage scenario // (logon/unlock is what's demonstrated in this sample). LogonUI then passes these credentials // back to the system to log on. HRESULT CSampleCredential::GetSerialization( __out CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE* pcpgsr, __out CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION* pcpcs, __deref_out_opt PWSTR* ppwzOptionalStatusText, __out CREDENTIAL_PROVIDER_STATUS_ICON* pcpsiOptionalStatusIcon ) { UNREFERENCED_PARAMETER(ppwzOptionalStatusText); UNREFERENCED_PARAMETER(pcpsiOptionalStatusIcon); HRESULT hr; WCHAR wsz[MAX_COMPUTERNAME_LENGTH+1]; DWORD cch = ARRAYSIZE(wsz); DWORD cb = 0; BYTE* rgb = NULL; if (GetComputerNameW(wsz, &cch)) { PWSTR pwzProtectedPassword; hr = ProtectIfNecessaryAndCopyPassword(_rgFieldStrings[SFI_PASSWORD], _cpus, &pwzProtectedPassword); // Only CredUI scenarios should use CredPackAuthenticationBuffer. Custom packing logic is necessary for // logon and unlock scenarios in order to specify the correct MessageType. if (CPUS_CREDUI == _cpus) { if (SUCCEEDED(hr)) { PWSTR pwzDomainUsername = NULL; hr = DomainUsernameStringAlloc(wsz, _rgFieldStrings[SFI_USERNAME], &pwzDomainUsername); if (SUCCEEDED(hr)) { // We use KERB_INTERACTIVE_UNLOCK_LOGON in both unlock and logon scenarios. It contains a // KERB_INTERACTIVE_LOGON to hold the creds plus a LUID that is filled in for us by Winlogon // as necessary. if (!CredPackAuthenticationBufferW((CREDUIWIN_PACK_32_WOW & _dwFlags) ? CRED_PACK_WOW_BUFFER : 0, pwzDomainUsername, pwzProtectedPassword, rgb, &cb)) { if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) { rgb = (BYTE*)HeapAlloc(GetProcessHeap(), 0, cb); if (rgb) { // If the CREDUIWIN_PACK_32_WOW flag is set we need to return 32 bit buffers to our caller we do this by // passing CRED_PACK_WOW_BUFFER to CredPacAuthenticationBufferW. if (!CredPackAuthenticationBufferW((CREDUIWIN_PACK_32_WOW & _dwFlags) ? CRED_PACK_WOW_BUFFER : 0, pwzDomainUsername, pwzProtectedPassword, rgb, &cb)) { HeapFree(GetProcessHeap(), 0, rgb); hr = HRESULT_FROM_WIN32(GetLastError()); } else { hr = S_OK; } } else { hr = E_OUTOFMEMORY; } } else { hr = E_FAIL; } HeapFree(GetProcessHeap(), 0, pwzDomainUsername); } else { hr = E_FAIL; } } CoTaskMemFree(pwzProtectedPassword); } } else { KERB_INTERACTIVE_UNLOCK_LOGON kiul; // Initialize kiul with weak references to our credential. hr = KerbInteractiveUnlockLogonInit(wsz, _rgFieldStrings[SFI_USERNAME], pwzProtectedPassword, _cpus, &kiul); if (SUCCEEDED(hr)) { // We use KERB_INTERACTIVE_UNLOCK_LOGON in both unlock and logon scenarios. It contains a // KERB_INTERACTIVE_LOGON to hold the creds plus a LUID that is filled in for us by Winlogon // as necessary. hr = KerbInteractiveUnlockLogonPack(kiul, &pcpcs->rgbSerialization, &pcpcs->cbSerialization); } } if (SUCCEEDED(hr)) { ULONG ulAuthPackage; hr = RetrieveNegotiateAuthPackage(&ulAuthPackage); if (SUCCEEDED(hr)) { pcpcs->ulAuthenticationPackage = ulAuthPackage; pcpcs->clsidCredentialProvider = CLSID_CSample; // In CredUI scenarios, we must pass back the buffer constructed with CredPackAuthenticationBuffer. if (CPUS_CREDUI == _cpus) { pcpcs->rgbSerialization = rgb; pcpcs->cbSerialization = cb; } // At this point the credential has created the serialized credential used for logon // By setting this to CPGSR_RETURN_CREDENTIAL_FINISHED we are letting logonUI know // that we have all the information we need and it should attempt to submit the // serialized credential. *pcpgsr = CPGSR_RETURN_CREDENTIAL_FINISHED; } else { HeapFree(GetProcessHeap(), 0, rgb); } } } else { DWORD dwErr = GetLastError(); hr = HRESULT_FROM_WIN32(dwErr); } return hr; } struct REPORT_RESULT_STATUS_INFO { NTSTATUS ntsStatus; NTSTATUS ntsSubstatus; PWSTR pwzMessage; CREDENTIAL_PROVIDER_STATUS_ICON cpsi; }; static const REPORT_RESULT_STATUS_INFO s_rgLogonStatusInfo[] = { { STATUS_LOGON_FAILURE, STATUS_SUCCESS, L"Incorrect password or username.", CPSI_ERROR, }, { STATUS_ACCOUNT_RESTRICTION, STATUS_ACCOUNT_DISABLED, L"The account is disabled.", CPSI_WARNING }, }; // ReportResult is completely optional. Its purpose is to allow a credential to customize the string // and the icon displayed in the case of a logon failure. For example, we have chosen to // customize the error shown in the case of bad username/password and in the case of the account // being disabled. HRESULT CSampleCredential::ReportResult( __in NTSTATUS ntsStatus, __in NTSTATUS ntsSubstatus, __deref_out_opt PWSTR* ppwzOptionalStatusText, __out CREDENTIAL_PROVIDER_STATUS_ICON* pcpsiOptionalStatusIcon ) { *ppwzOptionalStatusText = NULL; *pcpsiOptionalStatusIcon = CPSI_NONE; DWORD dwStatusInfo = (DWORD)-1; // Look for a match on status and substatus. for (DWORD i = 0; i < ARRAYSIZE(s_rgLogonStatusInfo); i++) { if (s_rgLogonStatusInfo[i].ntsStatus == ntsStatus && s_rgLogonStatusInfo[i].ntsSubstatus == ntsSubstatus) { dwStatusInfo = i; break; } } if ((DWORD)-1 != dwStatusInfo) { if (SUCCEEDED(SHStrDupW(s_rgLogonStatusInfo[dwStatusInfo].pwzMessage, ppwzOptionalStatusText))) { *pcpsiOptionalStatusIcon = s_rgLogonStatusInfo[dwStatusInfo].cpsi; } } // If we failed the logon, try to erase the password field. if (!SUCCEEDED(HRESULT_FROM_NT(ntsStatus))) { if (_pCredProvCredentialEvents) { _pCredProvCredentialEvents->SetFieldString(this, SFI_PASSWORD, L""); } } // Since NULL is a valid value for *ppwzOptionalStatusText and *pcpsiOptionalStatusIcon // this function can't fail. return S_OK; }