diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/README.md b/Microsoft Credential prvider v2 example - improved docs WIP/README.md new file mode 100644 index 0000000..b32b982 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/README.md @@ -0,0 +1,32 @@ +V2 Credential Provider Sample +============================= + +Demonstrates how to build a v2 credential provider that makes use of the new capabilities introduced to credential provider framework in Windows 8 and Windows 8.1. + +To get a copy of Windows, go to [Downloads and tools](http://go.microsoft.com/fwlink/p/?linkid=301696). + +To get a copy of Visual Studio, go to [Visual Studio Downloads](http://go.microsoft.com/fwlink/p/?linkid=301697). + +Operating system requirements +----------------------------- + +Client + +Windows 8.1 + +Server + +Windows Server 2012 R2 + +Build the sample +---------------- + +1. Start Visual Studio and select **File** \> **Open** \> **Project/Solution**. +2. Go to the directory named for the sample, and double-click the Visual Studio Solution (.sln) file. +3. Press F7 or use **Build** \> **Build Solution** to build the sample. + +Run the sample +-------------- + +To debug the app and then run it, press F5 or use **Debug** \> **Start Debugging**. To run the app without debugging, press Ctrl+F5 or use **Debug** \> **Start Without Debugging**. + diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/CSampleCredential.cpp b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/CSampleCredential.cpp new file mode 100644 index 0000000..c1806d0 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/CSampleCredential.cpp @@ -0,0 +1,700 @@ +// +// 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 "CSampleCredential.h" +#include "guid.h" + +CSampleCredential::CSampleCredential(): + _cRef(1), + _pCredProvCredentialEvents(nullptr), + _pszUserSid(nullptr), + _pszQualifiedUserName(nullptr), + _fIsLocalUser(false), + _fChecked(false), + _fShowControls(false), + _dwComboIndex(0) +{ + DllAddRef(); + + ZeroMemory(_rgCredProvFieldDescriptors, sizeof(_rgCredProvFieldDescriptors)); + ZeroMemory(_rgFieldStatePairs, sizeof(_rgFieldStatePairs)); + ZeroMemory(_rgFieldStrings, sizeof(_rgFieldStrings)); +} + +CSampleCredential::~CSampleCredential() +{ + if (_rgFieldStrings[SFI_PASSWORD]) + { + size_t lenPassword = wcslen(_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); + } + CoTaskMemFree(_pszUserSid); + CoTaskMemFree(_pszQualifiedUserName); + DllRelease(); +} + + +// Initializes one credential with the field information passed in. +// Set the value of the SFI_LARGE_TEXT field to pwzUsername. +HRESULT CSampleCredential::Initialize(CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus, + _In_ CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR const *rgcpfd, + _In_ FIELD_STATE_PAIR const *rgfsp, + _In_ ICredentialProviderUser *pcpUser) +{ + HRESULT hr = S_OK; + _cpus = cpus; + + GUID guidProvider; + pcpUser->GetProviderID(&guidProvider); + _fIsLocalUser = (guidProvider == Identity_LocalUserProvider); + + // 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 value of all the fields. + if (SUCCEEDED(hr)) + { + hr = SHStrDupW(L"Sample Credential", &_rgFieldStrings[SFI_LABEL]); + } + if (SUCCEEDED(hr)) + { + hr = SHStrDupW(L"Sample Credential Provider", &_rgFieldStrings[SFI_LARGE_TEXT]); + } + if (SUCCEEDED(hr)) + { + hr = SHStrDupW(L"Edit Text", &_rgFieldStrings[SFI_EDIT_TEXT]); + } + if (SUCCEEDED(hr)) + { + hr = SHStrDupW(L"", &_rgFieldStrings[SFI_PASSWORD]); + } + if (SUCCEEDED(hr)) + { + hr = SHStrDupW(L"Submit", &_rgFieldStrings[SFI_SUBMIT_BUTTON]); + } + if (SUCCEEDED(hr)) + { + hr = SHStrDupW(L"Checkbox", &_rgFieldStrings[SFI_CHECKBOX]); + } + if (SUCCEEDED(hr)) + { + hr = SHStrDupW(L"Combobox", &_rgFieldStrings[SFI_COMBOBOX]); + } + if (SUCCEEDED(hr)) + { + hr = SHStrDupW(L"Launch helper window", &_rgFieldStrings[SFI_LAUNCHWINDOW_LINK]); + } + if (SUCCEEDED(hr)) + { + hr = SHStrDupW(L"Hide additional controls", &_rgFieldStrings[SFI_HIDECONTROLS_LINK]); + } + if (SUCCEEDED(hr)) + { + hr = pcpUser->GetStringValue(PKEY_Identity_QualifiedUserName, &_pszQualifiedUserName); + } + if (SUCCEEDED(hr)) + { + PWSTR pszUserName; + pcpUser->GetStringValue(PKEY_Identity_UserName, &pszUserName); + if (pszUserName != nullptr) + { + wchar_t szString[256]; + StringCchPrintf(szString, ARRAYSIZE(szString), L"User Name: %s", pszUserName); + hr = SHStrDupW(szString, &_rgFieldStrings[SFI_FULLNAME_TEXT]); + CoTaskMemFree(pszUserName); + } + else + { + hr = SHStrDupW(L"User Name is NULL", &_rgFieldStrings[SFI_FULLNAME_TEXT]); + } + } + if (SUCCEEDED(hr)) + { + PWSTR pszDisplayName; + pcpUser->GetStringValue(PKEY_Identity_DisplayName, &pszDisplayName); + if (pszDisplayName != nullptr) + { + wchar_t szString[256]; + StringCchPrintf(szString, ARRAYSIZE(szString), L"Display Name: %s", pszDisplayName); + hr = SHStrDupW(szString, &_rgFieldStrings[SFI_DISPLAYNAME_TEXT]); + CoTaskMemFree(pszDisplayName); + } + else + { + hr = SHStrDupW(L"Display Name is NULL", &_rgFieldStrings[SFI_DISPLAYNAME_TEXT]); + } + } + if (SUCCEEDED(hr)) + { + PWSTR pszLogonStatus; + pcpUser->GetStringValue(PKEY_Identity_LogonStatusString, &pszLogonStatus); + if (pszLogonStatus != nullptr) + { + wchar_t szString[256]; + StringCchPrintf(szString, ARRAYSIZE(szString), L"Logon Status: %s", pszLogonStatus); + hr = SHStrDupW(szString, &_rgFieldStrings[SFI_LOGONSTATUS_TEXT]); + CoTaskMemFree(pszLogonStatus); + } + else + { + hr = SHStrDupW(L"Logon Status is NULL", &_rgFieldStrings[SFI_LOGONSTATUS_TEXT]); + } + } + + if (SUCCEEDED(hr)) + { + hr = pcpUser->GetSid(&_pszUserSid); + } + + return hr; +} + +// 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 != nullptr) + { + _pCredProvCredentialEvents->Release(); + } + return pcpce->QueryInterface(IID_PPV_ARGS(&_pCredProvCredentialEvents)); +} + +// LogonUI calls this to tell us to release the callback. +HRESULT CSampleCredential::UnAdvise() +{ + if (_pCredProvCredentialEvents) + { + _pCredProvCredentialEvents->Release(); + } + _pCredProvCredentialEvents = nullptr; + 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 = wcslen(_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; +} + +// Get info for a particular field of a tile. Called by logonUI to get information +// to display the tile. +HRESULT CSampleCredential::GetFieldState(DWORD dwFieldID, + _Out_ CREDENTIAL_PROVIDER_FIELD_STATE *pcpfs, + _Out_ CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE *pcpfis) +{ + HRESULT hr; + + // Validate our parameters. + if ((dwFieldID < ARRAYSIZE(_rgFieldStatePairs))) + { + *pcpfs = _rgFieldStatePairs[dwFieldID].cpfs; + *pcpfis = _rgFieldStatePairs[dwFieldID].cpfis; + hr = S_OK; + } + else + { + hr = E_INVALIDARG; + } + return hr; +} + +// Sets ppwsz to the string value of the field at the index dwFieldID +HRESULT CSampleCredential::GetStringValue(DWORD dwFieldID, _Outptr_result_nullonfailure_ PWSTR *ppwsz) +{ + HRESULT hr; + *ppwsz = nullptr; + + // Check to make sure dwFieldID is a legitimate index + if (dwFieldID < ARRAYSIZE(_rgCredProvFieldDescriptors)) + { + // Make a copy of the string and return that. The caller + // is responsible for freeing it. + hr = SHStrDupW(_rgFieldStrings[dwFieldID], ppwsz); + } + else + { + hr = E_INVALIDARG; + } + + return hr; +} + +// Get the image to show in the user tile +HRESULT CSampleCredential::GetBitmapValue(DWORD dwFieldID, _Outptr_result_nullonfailure_ HBITMAP *phbmp) +{ + HRESULT hr; + *phbmp = nullptr; + + if ((SFI_TILEIMAGE == dwFieldID)) + { + HBITMAP hbmp = LoadBitmap(HINST_THISDLL, MAKEINTRESOURCE(IDB_TILE_IMAGE)); + if (hbmp != nullptr) + { + 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(DWORD dwFieldID, _Out_ DWORD *pdwAdjacentTo) +{ + HRESULT hr; + + if (SFI_SUBMIT_BUTTON == dwFieldID) + { + // 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(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 *ppwszStored = &_rgFieldStrings[dwFieldID]; + CoTaskMemFree(*ppwszStored); + hr = SHStrDupW(pwz, ppwszStored); + } + else + { + hr = E_INVALIDARG; + } + + return hr; +} + +// Returns whether a checkbox is checked or not as well as its label. +HRESULT CSampleCredential::GetCheckboxValue(DWORD dwFieldID, _Out_ BOOL *pbChecked, _Outptr_result_nullonfailure_ PWSTR *ppwszLabel) +{ + HRESULT hr; + *ppwszLabel = nullptr; + + // Validate parameters. + if (dwFieldID < ARRAYSIZE(_rgCredProvFieldDescriptors) && + (CPFT_CHECKBOX == _rgCredProvFieldDescriptors[dwFieldID].cpft)) + { + *pbChecked = _fChecked; + hr = SHStrDupW(_rgFieldStrings[SFI_CHECKBOX], ppwszLabel); + } + else + { + hr = E_INVALIDARG; + } + + return hr; +} + +// Sets whether the specified checkbox is checked or not. +HRESULT CSampleCredential::SetCheckboxValue(DWORD dwFieldID, BOOL bChecked) +{ + HRESULT hr; + + // Validate parameters. + if (dwFieldID < ARRAYSIZE(_rgCredProvFieldDescriptors) && + (CPFT_CHECKBOX == _rgCredProvFieldDescriptors[dwFieldID].cpft)) + { + _fChecked = bChecked; + hr = S_OK; + } + else + { + hr = E_INVALIDARG; + } + + return hr; +} + +// Returns the number of items to be included in the combobox (pcItems), as well as the +// currently selected item (pdwSelectedItem). +HRESULT CSampleCredential::GetComboBoxValueCount(DWORD dwFieldID, _Out_ DWORD *pcItems, _Deref_out_range_(<, *pcItems) _Out_ DWORD *pdwSelectedItem) +{ + HRESULT hr; + *pcItems = 0; + *pdwSelectedItem = 0; + + // Validate parameters. + if (dwFieldID < ARRAYSIZE(_rgCredProvFieldDescriptors) && + (CPFT_COMBOBOX == _rgCredProvFieldDescriptors[dwFieldID].cpft)) + { + *pcItems = ARRAYSIZE(s_rgComboBoxStrings); + *pdwSelectedItem = 0; + hr = S_OK; + } + else + { + hr = E_INVALIDARG; + } + + return hr; +} + +// Called iteratively to fill the combobox with the string (ppwszItem) at index dwItem. +HRESULT CSampleCredential::GetComboBoxValueAt(DWORD dwFieldID, DWORD dwItem, _Outptr_result_nullonfailure_ PWSTR *ppwszItem) +{ + HRESULT hr; + *ppwszItem = nullptr; + + // Validate parameters. + if (dwFieldID < ARRAYSIZE(_rgCredProvFieldDescriptors) && + (CPFT_COMBOBOX == _rgCredProvFieldDescriptors[dwFieldID].cpft)) + { + hr = SHStrDupW(s_rgComboBoxStrings[dwItem], ppwszItem); + } + else + { + hr = E_INVALIDARG; + } + + return hr; +} + +// Called when the user changes the selected item in the combobox. +HRESULT CSampleCredential::SetComboBoxSelectedValue(DWORD dwFieldID, DWORD dwSelectedItem) +{ + HRESULT hr; + + // Validate parameters. + if (dwFieldID < ARRAYSIZE(_rgCredProvFieldDescriptors) && + (CPFT_COMBOBOX == _rgCredProvFieldDescriptors[dwFieldID].cpft)) + { + _dwComboIndex = dwSelectedItem; + hr = S_OK; + } + else + { + hr = E_INVALIDARG; + } + + return hr; +} + +// Called when the user clicks a command link. +HRESULT CSampleCredential::CommandLinkClicked(DWORD dwFieldID) +{ + HRESULT hr = S_OK; + + CREDENTIAL_PROVIDER_FIELD_STATE cpfsShow = CPFS_HIDDEN; + + // Validate parameter. + if (dwFieldID < ARRAYSIZE(_rgCredProvFieldDescriptors) && + (CPFT_COMMAND_LINK == _rgCredProvFieldDescriptors[dwFieldID].cpft)) + { + HWND hwndOwner = nullptr; + switch (dwFieldID) + { + case SFI_LAUNCHWINDOW_LINK: + if (_pCredProvCredentialEvents) + { + _pCredProvCredentialEvents->OnCreatingWindow(&hwndOwner); + } + + // Pop a messagebox indicating the click. + ::MessageBox(hwndOwner, L"Command link clicked", L"Click!", 0); + break; + case SFI_HIDECONTROLS_LINK: + _pCredProvCredentialEvents->BeginFieldUpdates(); + cpfsShow = _fShowControls ? CPFS_DISPLAY_IN_SELECTED_TILE : CPFS_HIDDEN; + _pCredProvCredentialEvents->SetFieldState(nullptr, SFI_FULLNAME_TEXT, cpfsShow); + _pCredProvCredentialEvents->SetFieldState(nullptr, SFI_DISPLAYNAME_TEXT, cpfsShow); + _pCredProvCredentialEvents->SetFieldState(nullptr, SFI_LOGONSTATUS_TEXT, cpfsShow); + _pCredProvCredentialEvents->SetFieldState(nullptr, SFI_CHECKBOX, cpfsShow); + _pCredProvCredentialEvents->SetFieldState(nullptr, SFI_EDIT_TEXT, cpfsShow); + _pCredProvCredentialEvents->SetFieldState(nullptr, SFI_COMBOBOX, cpfsShow); + _pCredProvCredentialEvents->SetFieldString(nullptr, SFI_HIDECONTROLS_LINK, _fShowControls? L"Hide additional controls" : L"Show additional controls"); + _pCredProvCredentialEvents->EndFieldUpdates(); + _fShowControls = !_fShowControls; + break; + default: + hr = E_INVALIDARG; + } + + } + else + { + hr = E_INVALIDARG; + } + + return hr; +} + +// 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, + _Outptr_result_maybenull_ PWSTR *ppwszOptionalStatusText, + _Out_ CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon) +{ + HRESULT hr = E_UNEXPECTED; + *pcpgsr = CPGSR_NO_CREDENTIAL_NOT_FINISHED; + *ppwszOptionalStatusText = nullptr; + *pcpsiOptionalStatusIcon = CPSI_NONE; + ZeroMemory(pcpcs, sizeof(*pcpcs)); + + // For local user, the domain and user name can be split from _pszQualifiedUserName (domain\username). + // CredPackAuthenticationBuffer() cannot be used because it won't work with unlock scenario. + if (_fIsLocalUser) + { + PWSTR pwzProtectedPassword; + hr = ProtectIfNecessaryAndCopyPassword(_rgFieldStrings[SFI_PASSWORD], _cpus, &pwzProtectedPassword); + if (SUCCEEDED(hr)) + { + PWSTR pszDomain; + PWSTR pszUsername; + hr = SplitDomainAndUsername(_pszQualifiedUserName, &pszDomain, &pszUsername); + if (SUCCEEDED(hr)) + { + KERB_INTERACTIVE_UNLOCK_LOGON kiul; + hr = KerbInteractiveUnlockLogonInit(pszDomain, pszUsername, 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; + // 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; + } + } + } + CoTaskMemFree(pszDomain); + CoTaskMemFree(pszUsername); + } + CoTaskMemFree(pwzProtectedPassword); + } + } + else + { + DWORD dwAuthFlags = CRED_PACK_PROTECTED_CREDENTIALS | CRED_PACK_ID_PROVIDER_CREDENTIALS; + + // First get the size of the authentication buffer to allocate + if (!CredPackAuthenticationBuffer(dwAuthFlags, _pszQualifiedUserName, const_cast(_rgFieldStrings[SFI_PASSWORD]), nullptr, &pcpcs->cbSerialization) && + (GetLastError() == ERROR_INSUFFICIENT_BUFFER)) + { + pcpcs->rgbSerialization = static_cast(CoTaskMemAlloc(pcpcs->cbSerialization)); + if (pcpcs->rgbSerialization != nullptr) + { + hr = S_OK; + + // Retrieve the authentication buffer + if (CredPackAuthenticationBuffer(dwAuthFlags, _pszQualifiedUserName, const_cast(_rgFieldStrings[SFI_PASSWORD]), pcpcs->rgbSerialization, &pcpcs->cbSerialization)) + { + ULONG ulAuthPackage; + hr = RetrieveNegotiateAuthPackage(&ulAuthPackage); + if (SUCCEEDED(hr)) + { + pcpcs->ulAuthenticationPackage = ulAuthPackage; + pcpcs->clsidCredentialProvider = CLSID_CSample; + + // 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 + { + hr = HRESULT_FROM_WIN32(GetLastError()); + if (SUCCEEDED(hr)) + { + hr = E_FAIL; + } + } + + if (FAILED(hr)) + { + CoTaskMemFree(pcpcs->rgbSerialization); + } + } + else + { + hr = E_OUTOFMEMORY; + } + } + } + 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(NTSTATUS ntsStatus, + NTSTATUS ntsSubstatus, + _Outptr_result_maybenull_ PWSTR *ppwszOptionalStatusText, + _Out_ CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon) +{ + *ppwszOptionalStatusText = nullptr; + *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, ppwszOptionalStatusText))) + { + *pcpsiOptionalStatusIcon = s_rgLogonStatusInfo[dwStatusInfo].cpsi; + } + } + + // If we failed the logon, try to erase the password field. + if (FAILED(HRESULT_FROM_NT(ntsStatus))) + { + if (_pCredProvCredentialEvents) + { + _pCredProvCredentialEvents->SetFieldString(this, SFI_PASSWORD, L""); + } + } + + // Since nullptr is a valid value for *ppwszOptionalStatusText and *pcpsiOptionalStatusIcon + // this function can't fail. + return S_OK; +} + +// Gets the SID of the user corresponding to the credential. +HRESULT CSampleCredential::GetUserSid(_Outptr_result_nullonfailure_ PWSTR *ppszSid) +{ + *ppszSid = nullptr; + HRESULT hr = E_UNEXPECTED; + if (_pszUserSid != nullptr) + { + hr = SHStrDupW(_pszUserSid, ppszSid); + } + // Return S_FALSE with a null SID in ppszSid for the + // credential to be associated with an empty user tile. + + return hr; +} + +// GetFieldOptions to enable the password reveal button and touch keyboard auto-invoke in the password field. +HRESULT CSampleCredential::GetFieldOptions(DWORD dwFieldID, + _Out_ CREDENTIAL_PROVIDER_CREDENTIAL_FIELD_OPTIONS *pcpcfo) +{ + *pcpcfo = CPCFO_NONE; + + if (dwFieldID == SFI_PASSWORD) + { + *pcpcfo = CPCFO_ENABLE_PASSWORD_REVEAL; + } + else if (dwFieldID == SFI_TILEIMAGE) + { + *pcpcfo = CPCFO_ENABLE_TOUCH_KEYBOARD_AUTO_INVOKE; + } + + return S_OK; +} diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/CSampleCredential.h b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/CSampleCredential.h new file mode 100644 index 0000000..f898230 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/CSampleCredential.h @@ -0,0 +1,120 @@ +// +// 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. +// +// CSampleCredential is our implementation of ICredentialProviderCredential. +// ICredentialProviderCredential is what LogonUI uses to let a credential +// provider specify what a user tile looks like and then tell it what the +// user has entered into the tile. ICredentialProviderCredential is also +// responsible for packaging up the users credentials into a buffer that +// LogonUI then sends on to LSA. + +#pragma once + +#include +#include +#include +#include +#include "common.h" +#include "dll.h" +#include "resource.h" + +class CSampleCredential : public ICredentialProviderCredential2, ICredentialProviderCredentialWithFieldOptions +{ +public: + // IUnknown + IFACEMETHODIMP_(ULONG) AddRef() + { + return ++_cRef; + } + + IFACEMETHODIMP_(ULONG) Release() + { + long cRef = --_cRef; + if (!cRef) + { + delete this; + } + return cRef; + } + + IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void **ppv) + { + static const QITAB qit[] = + { + QITABENT(CSampleCredential, ICredentialProviderCredential), // IID_ICredentialProviderCredential + QITABENT(CSampleCredential, ICredentialProviderCredential2), // IID_ICredentialProviderCredential2 + QITABENT(CSampleCredential, ICredentialProviderCredentialWithFieldOptions), //IID_ICredentialProviderCredentialWithFieldOptions + {0}, + }; + return QISearch(this, qit, riid, ppv); + } + public: + // ICredentialProviderCredential + IFACEMETHODIMP Advise(_In_ ICredentialProviderCredentialEvents *pcpce); + IFACEMETHODIMP UnAdvise(); + + IFACEMETHODIMP SetSelected(_Out_ BOOL *pbAutoLogon); + IFACEMETHODIMP SetDeselected(); + + IFACEMETHODIMP GetFieldState(DWORD dwFieldID, + _Out_ CREDENTIAL_PROVIDER_FIELD_STATE *pcpfs, + _Out_ CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE *pcpfis); + + IFACEMETHODIMP GetStringValue(DWORD dwFieldID, _Outptr_result_nullonfailure_ PWSTR *ppwsz); + IFACEMETHODIMP GetBitmapValue(DWORD dwFieldID, _Outptr_result_nullonfailure_ HBITMAP *phbmp); + IFACEMETHODIMP GetCheckboxValue(DWORD dwFieldID, _Out_ BOOL *pbChecked, _Outptr_result_nullonfailure_ PWSTR *ppwszLabel); + IFACEMETHODIMP GetComboBoxValueCount(DWORD dwFieldID, _Out_ DWORD *pcItems, _Deref_out_range_(<, *pcItems) _Out_ DWORD *pdwSelectedItem); + IFACEMETHODIMP GetComboBoxValueAt(DWORD dwFieldID, DWORD dwItem, _Outptr_result_nullonfailure_ PWSTR *ppwszItem); + IFACEMETHODIMP GetSubmitButtonValue(DWORD dwFieldID, _Out_ DWORD *pdwAdjacentTo); + + IFACEMETHODIMP SetStringValue(DWORD dwFieldID, _In_ PCWSTR pwz); + IFACEMETHODIMP SetCheckboxValue(DWORD dwFieldID, BOOL bChecked); + IFACEMETHODIMP SetComboBoxSelectedValue(DWORD dwFieldID, DWORD dwSelectedItem); + IFACEMETHODIMP CommandLinkClicked(DWORD dwFieldID); + + IFACEMETHODIMP GetSerialization(_Out_ CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE *pcpgsr, + _Out_ CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcs, + _Outptr_result_maybenull_ PWSTR *ppwszOptionalStatusText, + _Out_ CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon); + IFACEMETHODIMP ReportResult(NTSTATUS ntsStatus, + NTSTATUS ntsSubstatus, + _Outptr_result_maybenull_ PWSTR *ppwszOptionalStatusText, + _Out_ CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon); + + + // ICredentialProviderCredential2 + IFACEMETHODIMP GetUserSid(_Outptr_result_nullonfailure_ PWSTR *ppszSid); + + // ICredentialProviderCredentialWithFieldOptions + IFACEMETHODIMP GetFieldOptions(DWORD dwFieldID, + _Out_ CREDENTIAL_PROVIDER_CREDENTIAL_FIELD_OPTIONS *pcpcfo); + + public: + HRESULT Initialize(CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus, + _In_ CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR const *rgcpfd, + _In_ FIELD_STATE_PAIR const *rgfsp, + _In_ ICredentialProviderUser *pcpUser); + CSampleCredential(); + + private: + + virtual ~CSampleCredential(); + long _cRef; + CREDENTIAL_PROVIDER_USAGE_SCENARIO _cpus; // The usage scenario for which we were enumerated. + CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR _rgCredProvFieldDescriptors[SFI_NUM_FIELDS]; // An array holding the type and name of each field in the tile. + FIELD_STATE_PAIR _rgFieldStatePairs[SFI_NUM_FIELDS]; // An array holding the state of each field in the tile. + PWSTR _rgFieldStrings[SFI_NUM_FIELDS]; // An array holding the string value of each field. This is different from the name of the field held in _rgCredProvFieldDescriptors. + PWSTR _pszUserSid; + PWSTR _pszQualifiedUserName; // The user name that's used to pack the authentication buffer + ICredentialProviderCredentialEvents2* _pCredProvCredentialEvents; // Used to update fields. + // CredentialEvents2 for Begin and EndFieldUpdates. + BOOL _fChecked; // Tracks the state of our checkbox. + DWORD _dwComboIndex; // Tracks the current index of our combobox. + bool _fShowControls; // Tracks the state of our show/hide controls link. + bool _fIsLocalUser; // If the cred prov is assosiating with a local user tile +}; diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/CSampleProvider.cpp b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/CSampleProvider.cpp new file mode 100644 index 0000000..d25ef1d --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/CSampleProvider.cpp @@ -0,0 +1,279 @@ +// +// 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. +// +// CSampleProvider implements ICredentialProvider, which is the main +// 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 +#include "CSampleProvider.h" +#include "CSampleCredential.h" +#include "guid.h" + +CSampleProvider::CSampleProvider(): + _cRef(1), + _pCredential(nullptr), + _pCredProviderUserArray(nullptr) +{ + DllAddRef(); +} + +CSampleProvider::~CSampleProvider() +{ + if (_pCredential != nullptr) + { + _pCredential->Release(); + _pCredential = nullptr; + } + if (_pCredProviderUserArray != nullptr) + { + _pCredProviderUserArray->Release(); + _pCredProviderUserArray = nullptr; + } + + DllRelease(); +} + +// SetUsageScenario is the provider's cue that it's going to be asked for tiles +// in a subsequent call. +HRESULT CSampleProvider::SetUsageScenario( + CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus, + DWORD /*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_LOGON: + case CPUS_UNLOCK_WORKSTATION: + // The reason why we need _fRecreateEnumeratedCredentials is because ICredentialProviderSetUserArray::SetUserArray() is called after ICredentialProvider::SetUsageScenario(), + // while we need the ICredentialProviderUserArray during enumeration in ICredentialProvider::GetCredentialCount() + _cpus = cpus; + _fRecreateEnumeratedCredentials = true; + hr = S_OK; + 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. +// +// If you wish to see an example of SetSerialization, please see either the SampleCredentialProvider +// sample or the SampleCredUICredentialProvider sample. [The logonUI team says, "The original sample that +// this was built on top of didn't have SetSerialization. And when we decided SetSerialization was +// important enough to have in the sample, it ended up being a non-trivial amount of work to integrate +// it into the main sample. We felt it was more important to get these samples out to you quickly than to +// hold them in order to do the work to integrate the SetSerialization changes from SampleCredentialProvider +// into this sample.] +HRESULT CSampleProvider::SetSerialization( + _In_ CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION const * /*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 CSampleProvider::Advise( + _In_ ICredentialProviderEvents * /*pcpe*/, + _In_ UINT_PTR /*upAdviseContext*/) +{ + return E_NOTIMPL; +} + +// Called by LogonUI when the ICredentialProviderEvents callback is no longer valid. +HRESULT CSampleProvider::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 CSampleProvider::GetFieldDescriptorCount( + _Out_ DWORD *pdwCount) +{ + *pdwCount = SFI_NUM_FIELDS; + return S_OK; +} + +// Gets the field descriptor for a particular field. +HRESULT CSampleProvider::GetFieldDescriptorAt( + DWORD dwIndex, + _Outptr_result_nullonfailure_ CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR **ppcpfd) +{ + HRESULT hr; + *ppcpfd = nullptr; + + // Verify dwIndex is a valid field. + if ((dwIndex < SFI_NUM_FIELDS) && ppcpfd) + { + hr = FieldDescriptorCoAllocCopy(s_rgCredProvFieldDescriptors[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 CSampleProvider::GetCredentialCount( + _Out_ DWORD *pdwCount, + _Out_ DWORD *pdwDefault, + _Out_ BOOL *pbAutoLogonWithDefault) +{ + *pdwDefault = CREDENTIAL_PROVIDER_NO_DEFAULT; + *pbAutoLogonWithDefault = FALSE; + + if (_fRecreateEnumeratedCredentials) + { + _fRecreateEnumeratedCredentials = false; + _ReleaseEnumeratedCredentials(); + _CreateEnumeratedCredentials(); + } + + *pdwCount = 1; + + return S_OK; +} + +// Returns the credential at the index specified by dwIndex. This function is called by logonUI to enumerate +// the tiles. +HRESULT CSampleProvider::GetCredentialAt( + DWORD dwIndex, + _Outptr_result_nullonfailure_ ICredentialProviderCredential **ppcpc) +{ + HRESULT hr = E_INVALIDARG; + *ppcpc = nullptr; + + if ((dwIndex == 0) && ppcpc) + { + hr = _pCredential->QueryInterface(IID_PPV_ARGS(ppcpc)); + } + return hr; +} + +// This function will be called by LogonUI after SetUsageScenario succeeds. +// Sets the User Array with the list of users to be enumerated on the logon screen. +HRESULT CSampleProvider::SetUserArray(_In_ ICredentialProviderUserArray *users) +{ + if (_pCredProviderUserArray) + { + _pCredProviderUserArray->Release(); + } + _pCredProviderUserArray = users; + _pCredProviderUserArray->AddRef(); + return S_OK; +} + +void CSampleProvider::_CreateEnumeratedCredentials() +{ + switch (_cpus) + { + case CPUS_LOGON: + case CPUS_UNLOCK_WORKSTATION: + { + _EnumerateCredentials(); + break; + } + default: + break; + } +} + +void CSampleProvider::_ReleaseEnumeratedCredentials() +{ + if (_pCredential != nullptr) + { + _pCredential->Release(); + _pCredential = nullptr; + } +} + +HRESULT CSampleProvider::_EnumerateCredentials() +{ + HRESULT hr = E_UNEXPECTED; + if (_pCredProviderUserArray != nullptr) + { + DWORD dwUserCount; + _pCredProviderUserArray->GetCount(&dwUserCount); + if (dwUserCount > 0) + { + ICredentialProviderUser *pCredUser; + hr = _pCredProviderUserArray->GetAt(0, &pCredUser); + if (SUCCEEDED(hr)) + { + _pCredential = new(std::nothrow) CSampleCredential(); + if (_pCredential != nullptr) + { + hr = _pCredential->Initialize(_cpus, s_rgCredProvFieldDescriptors, s_rgFieldStatePairs, pCredUser); + if (FAILED(hr)) + { + _pCredential->Release(); + _pCredential = nullptr; + } + } + else + { + hr = E_OUTOFMEMORY; + } + pCredUser->Release(); + } + } + } + return hr; +} + +// Boilerplate code to create our provider. +HRESULT CSample_CreateInstance(_In_ REFIID riid, _Outptr_ void **ppv) +{ + HRESULT hr; + CSampleProvider *pProvider = new(std::nothrow) CSampleProvider(); + if (pProvider) + { + hr = pProvider->QueryInterface(riid, ppv); + pProvider->Release(); + } + else + { + hr = E_OUTOFMEMORY; + } + return hr; +} diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/CSampleProvider.h b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/CSampleProvider.h new file mode 100644 index 0000000..4b93a70 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/CSampleProvider.h @@ -0,0 +1,85 @@ +// +// 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. +// + +#include "helpers.h" +#include +#include +#include + +#include "CSampleCredential.h" + +class CSampleProvider : public ICredentialProvider, + public ICredentialProviderSetUserArray +{ + public: + // IUnknown + IFACEMETHODIMP_(ULONG) AddRef() + { + return ++_cRef; + } + + IFACEMETHODIMP_(ULONG) Release() + { + long cRef = --_cRef; + if (!cRef) + { + delete this; + } + return cRef; + } + + IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void **ppv) + { + static const QITAB qit[] = + { + QITABENT(CSampleProvider, ICredentialProvider), // IID_ICredentialProvider + QITABENT(CSampleProvider, ICredentialProviderSetUserArray), // IID_ICredentialProviderSetUserArray + {0}, + }; + return QISearch(this, qit, riid, ppv); + } + + public: + IFACEMETHODIMP SetUsageScenario(CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus, DWORD dwFlags); + IFACEMETHODIMP SetSerialization(_In_ CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION const *pcpcs); + + IFACEMETHODIMP Advise(_In_ ICredentialProviderEvents *pcpe, _In_ UINT_PTR upAdviseContext); + IFACEMETHODIMP UnAdvise(); + + IFACEMETHODIMP GetFieldDescriptorCount(_Out_ DWORD *pdwCount); + IFACEMETHODIMP GetFieldDescriptorAt(DWORD dwIndex, _Outptr_result_nullonfailure_ CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR **ppcpfd); + + IFACEMETHODIMP GetCredentialCount(_Out_ DWORD *pdwCount, + _Out_ DWORD *pdwDefault, + _Out_ BOOL *pbAutoLogonWithDefault); + IFACEMETHODIMP GetCredentialAt(DWORD dwIndex, + _Outptr_result_nullonfailure_ ICredentialProviderCredential **ppcpc); + + IFACEMETHODIMP SetUserArray(_In_ ICredentialProviderUserArray *users); + + friend HRESULT CSample_CreateInstance(_In_ REFIID riid, _Outptr_ void** ppv); + + protected: + CSampleProvider(); + __override ~CSampleProvider(); + + private: + void _ReleaseEnumeratedCredentials(); + void _CreateEnumeratedCredentials(); + HRESULT _EnumerateEmpty(); + HRESULT _EnumerateCredentials(); + HRESULT _EnumerateEmptyTileCredential(); +private: + long _cRef; // Used for reference counting. + CSampleCredential *_pCredential; // SampleV2Credential + bool _fRecreateEnumeratedCredentials; + CREDENTIAL_PROVIDER_USAGE_SCENARIO _cpus; + ICredentialProviderUserArray *_pCredProviderUserArray; + +}; diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/Dll.cpp b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/Dll.cpp new file mode 100644 index 0000000..eb24f83 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/Dll.cpp @@ -0,0 +1,151 @@ +// +// 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. +// +// Standard dll required functions and class factory implementation. + +#include +#include +#include "Dll.h" +#include "helpers.h" + +static long g_cRef = 0; // global dll reference count +HINSTANCE g_hinst = NULL; // global dll hinstance + +extern HRESULT CSample_CreateInstance(__in REFIID riid, __deref_out void** ppv); +EXTERN_C GUID CLSID_CSample; + +class CClassFactory : public IClassFactory +{ +public: + CClassFactory() : _cRef(1) + { + } + + // IUnknown + IFACEMETHODIMP QueryInterface(__in REFIID riid, __deref_out void **ppv) + { + static const QITAB qit[] = + { + QITABENT(CClassFactory, IClassFactory), + { 0 }, + }; + return QISearch(this, qit, riid, ppv); + } + + IFACEMETHODIMP_(ULONG) AddRef() + { + return InterlockedIncrement(&_cRef); + } + + IFACEMETHODIMP_(ULONG) Release() + { + long cRef = InterlockedDecrement(&_cRef); + if (!cRef) + delete this; + return cRef; + } + + // IClassFactory + IFACEMETHODIMP CreateInstance(__in IUnknown* pUnkOuter, __in REFIID riid, __deref_out void **ppv) + { + HRESULT hr; + if (!pUnkOuter) + { + hr = CSample_CreateInstance(riid, ppv); + } + else + { + *ppv = NULL; + hr = CLASS_E_NOAGGREGATION; + } + return hr; + } + + IFACEMETHODIMP LockServer(__in BOOL bLock) + { + if (bLock) + { + DllAddRef(); + } + else + { + DllRelease(); + } + return S_OK; + } + +private: + ~CClassFactory() + { + } + long _cRef; +}; + +HRESULT CClassFactory_CreateInstance(__in REFCLSID rclsid, __in REFIID riid, __deref_out void **ppv) +{ + *ppv = NULL; + + HRESULT hr; + + if (CLSID_CSample == rclsid) + { + CClassFactory* pcf = new CClassFactory(); + if (pcf) + { + hr = pcf->QueryInterface(riid, ppv); + pcf->Release(); + } + else + { + hr = E_OUTOFMEMORY; + } + } + else + { + hr = CLASS_E_CLASSNOTAVAILABLE; + } + return hr; +} + +void DllAddRef() +{ + InterlockedIncrement(&g_cRef); +} + +void DllRelease() +{ + InterlockedDecrement(&g_cRef); +} + +STDAPI DllCanUnloadNow() +{ + return (g_cRef > 0) ? S_FALSE : S_OK; +} + +STDAPI DllGetClassObject(__in REFCLSID rclsid, __in REFIID riid, __deref_out void** ppv) +{ + return CClassFactory_CreateInstance(rclsid, riid, ppv); +} + +STDAPI_(BOOL) DllMain(__in HINSTANCE hinstDll, __in DWORD dwReason, __in void *) +{ + switch (dwReason) + { + case DLL_PROCESS_ATTACH: + DisableThreadLibraryCalls(hinstDll); + break; + case DLL_PROCESS_DETACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + + g_hinst = hinstDll; + return TRUE; +} + diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/Dll.h b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/Dll.h new file mode 100644 index 0000000..a8efaf6 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/Dll.h @@ -0,0 +1,18 @@ +// +// 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. +// +// + +#pragma once + +// global dll hinstance +extern HINSTANCE g_hinst; +#define HINST_THISDLL g_hinst + +void DllAddRef(); +void DllRelease(); diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/SampleV2CredentialProvider.sln b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/SampleV2CredentialProvider.sln new file mode 100644 index 0000000..f0cbe48 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/SampleV2CredentialProvider.sln @@ -0,0 +1,26 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2012 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "SampleV2CredentialProvider", "SampleV2CredentialProvider.vcxproj", "{98E74D71-5237-41FA-8D36-206C0D110626}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {98E74D71-5237-41FA-8D36-206C0D110626}.Debug|Win32.ActiveCfg = Debug|Win32 + {98E74D71-5237-41FA-8D36-206C0D110626}.Debug|Win32.Build.0 = Debug|Win32 + {98E74D71-5237-41FA-8D36-206C0D110626}.Debug|x64.ActiveCfg = Debug|x64 + {98E74D71-5237-41FA-8D36-206C0D110626}.Debug|x64.Build.0 = Debug|x64 + {98E74D71-5237-41FA-8D36-206C0D110626}.Release|Win32.ActiveCfg = Release|Win32 + {98E74D71-5237-41FA-8D36-206C0D110626}.Release|Win32.Build.0 = Release|Win32 + {98E74D71-5237-41FA-8D36-206C0D110626}.Release|x64.ActiveCfg = Release|x64 + {98E74D71-5237-41FA-8D36-206C0D110626}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/SampleV2CredentialProvider.vcxproj b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/SampleV2CredentialProvider.vcxproj new file mode 100644 index 0000000..2738e35 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/SampleV2CredentialProvider.vcxproj @@ -0,0 +1,183 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {98E74D71-5237-41FA-8D36-206C0D110626} + Win32Proj + SampleV2CredentialProvider + + + + DynamicLibrary + true + v110 + Unicode + + + DynamicLibrary + true + v110 + Unicode + + + DynamicLibrary + false + v110 + true + Unicode + + + DynamicLibrary + false + v110 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + NotUsing + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;SAMPLEV2CREDENTIALPROVIDER_EXPORTS;%(PreprocessorDefinitions) + false + true + + + Windows + true + Credui.lib;Shlwapi.lib;Secur32.lib;%(AdditionalDependencies) + samplev2credentialprovider.def + + + + + NotUsing + Level4 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;SAMPLEV2CREDENTIALPROVIDER_EXPORTS;%(PreprocessorDefinitions) + false + + + Windows + true + Credui.lib;Shlwapi.lib;Secur32.lib;%(AdditionalDependencies) + samplev2credentialprovider.def + + + + + Level4 + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;SAMPLEV2CREDENTIALPROVIDER_EXPORTS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + Credui.lib;Shlwapi.lib;Secur32.lib;%(AdditionalDependencies) + samplev2credentialprovider.def + + + + + Level4 + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;SAMPLEV2CREDENTIALPROVIDER_EXPORTS;%(PreprocessorDefinitions) + true + + + Windows + true + true + true + Credui.lib;Shlwapi.lib;Secur32.lib;%(AdditionalDependencies) + samplev2credentialprovider.def + + + + + + \ No newline at end of file diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/SampleV2CredentialProvider.vcxproj.filters b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/SampleV2CredentialProvider.vcxproj.filters new file mode 100644 index 0000000..9b27343 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/SampleV2CredentialProvider.vcxproj.filters @@ -0,0 +1,77 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Resource Files + + + + + + + + + + Source Files + + + + + Resource Files + + + \ No newline at end of file diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/Unregister.reg b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/Unregister.reg new file mode 100644 index 0000000..652da3f --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/Unregister.reg @@ -0,0 +1,3 @@ +Windows Registry Editor Version 5.00 + +[-HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{5fd3d285-0dd9-4362-8855-e0abaacd4af6}] diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/common.h b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/common.h new file mode 100644 index 0000000..dfafe0c --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/common.h @@ -0,0 +1,95 @@ +// +// 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. +// +// This file contains some global variables that describe what our +// sample tile looks like. For example, it defines what fields a tile has +// and which fields show in which states of LogonUI. This sample illustrates +// the use of each UI field type. + +#pragma once +#include "helpers.h" + +// The indexes of each of the fields in our credential provider's tiles. Note that we're +// using each of the nine available field types here. +enum SAMPLE_FIELD_ID +{ + SFI_TILEIMAGE = 0, + SFI_LABEL = 1, + SFI_LARGE_TEXT = 2, + SFI_PASSWORD = 3, + SFI_SUBMIT_BUTTON = 4, + SFI_LAUNCHWINDOW_LINK = 5, + SFI_HIDECONTROLS_LINK = 6, + SFI_FULLNAME_TEXT = 7, + SFI_DISPLAYNAME_TEXT = 8, + SFI_LOGONSTATUS_TEXT = 9, + SFI_CHECKBOX = 10, + SFI_EDIT_TEXT = 11, + SFI_COMBOBOX = 12, + SFI_NUM_FIELDS = 13, // Note: if new fields are added, keep NUM_FIELDS last. This is used as a count of the number of fields +}; + +// The first value indicates when the tile is displayed (selected, not selected) +// the second indicates things like whether the field is enabled, whether it has key focus, etc. +struct FIELD_STATE_PAIR +{ + CREDENTIAL_PROVIDER_FIELD_STATE cpfs; + CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE cpfis; +}; + +// These two arrays are seperate because a credential provider might +// want to set up a credential with various combinations of field state pairs +// and field descriptors. + +// The field state value indicates whether the field is displayed +// in the selected tile, the deselected tile, or both. +// The Field interactive state indicates when +static const FIELD_STATE_PAIR s_rgFieldStatePairs[] = +{ + { CPFS_DISPLAY_IN_BOTH, CPFIS_NONE }, // SFI_TILEIMAGE + { CPFS_HIDDEN, CPFIS_NONE }, // SFI_LABEL + { CPFS_DISPLAY_IN_BOTH, CPFIS_NONE }, // SFI_LARGE_TEXT + { CPFS_DISPLAY_IN_SELECTED_TILE, CPFIS_FOCUSED }, // SFI_PASSWORD + { CPFS_DISPLAY_IN_SELECTED_TILE, CPFIS_NONE }, // SFI_SUBMIT_BUTTON + { CPFS_DISPLAY_IN_SELECTED_TILE, CPFIS_NONE }, // SFI_LAUNCHWINDOW_LINK + { CPFS_DISPLAY_IN_SELECTED_TILE, CPFIS_NONE }, // SFI_HIDECONTROLS_LINK + { CPFS_DISPLAY_IN_SELECTED_TILE, CPFIS_NONE }, // SFI_FULLNAME_TEXT + { CPFS_DISPLAY_IN_SELECTED_TILE, CPFIS_NONE }, // SFI_DISPLAYNAME_TEXT + { CPFS_DISPLAY_IN_SELECTED_TILE, CPFIS_NONE }, // SFI_LOGONSTATUS_TEXT + { CPFS_DISPLAY_IN_SELECTED_TILE, CPFIS_NONE }, // SFI_CHECKBOX + { CPFS_DISPLAY_IN_SELECTED_TILE, CPFIS_NONE }, // SFI_EDIT_TEXT + { CPFS_DISPLAY_IN_SELECTED_TILE, CPFIS_NONE }, // SFI_COMBOBOX +}; + +// Field descriptors for unlock and logon. +// The first field is the index of the field. +// The second is the type of the field. +// The third is the name of the field, NOT the value which will appear in the field. +static const CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR s_rgCredProvFieldDescriptors[] = +{ + { SFI_TILEIMAGE, CPFT_TILE_IMAGE, L"Image", CPFG_CREDENTIAL_PROVIDER_LOGO }, + { SFI_LABEL, CPFT_SMALL_TEXT, L"Tooltip", CPFG_CREDENTIAL_PROVIDER_LABEL }, + { SFI_LARGE_TEXT, CPFT_LARGE_TEXT, L"Sample Credential Provider" }, + { SFI_PASSWORD, CPFT_PASSWORD_TEXT, L"Password text" }, + { SFI_SUBMIT_BUTTON, CPFT_SUBMIT_BUTTON, L"Submit" }, + { SFI_LAUNCHWINDOW_LINK, CPFT_COMMAND_LINK, L"Launch helper window" }, + { SFI_HIDECONTROLS_LINK, CPFT_COMMAND_LINK, L"Hide additional controls" }, + { SFI_FULLNAME_TEXT, CPFT_SMALL_TEXT, L"Full name: " }, + { SFI_DISPLAYNAME_TEXT, CPFT_SMALL_TEXT, L"Display name: " }, + { SFI_LOGONSTATUS_TEXT, CPFT_SMALL_TEXT, L"Logon status: " }, + { SFI_CHECKBOX, CPFT_CHECKBOX, L"Checkbox" }, + { SFI_EDIT_TEXT, CPFT_EDIT_TEXT, L"Edit text" }, + { SFI_COMBOBOX, CPFT_COMBOBOX, L"Combobox" }, +}; + +static const PWSTR s_rgComboBoxStrings[] = +{ + L"First", + L"Second", + L"Third", +}; diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/guid.cpp b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/guid.cpp new file mode 100644 index 0000000..0d656ad --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/guid.cpp @@ -0,0 +1,11 @@ +// +// 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. +// + +#include +#include "guid.h" diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/guid.h b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/guid.h new file mode 100644 index 0000000..c78ee11 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/guid.h @@ -0,0 +1,11 @@ +// +// 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. +// + +// 5fd3d285-0dd9-4362-8855-e0abaacd4af6 +DEFINE_GUID(CLSID_CSample, 0x5fd3d285, 0x0dd9, 0x4362, 0x88, 0x55, 0xe0, 0xab, 0xaa, 0xcd, 0x4a, 0xf6); diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/helpers.cpp b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/helpers.cpp new file mode 100644 index 0000000..9f7dcc3 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/helpers.cpp @@ -0,0 +1,710 @@ +// +// 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. +// +// Helper functions for copying parameters and packaging the buffer +// for GetSerialization. + + +#include "helpers.h" +#include + +// +// Copies the field descriptor pointed to by rcpfd into a buffer allocated +// using CoTaskMemAlloc. Returns that buffer in ppcpfd. +// +HRESULT FieldDescriptorCoAllocCopy( + _In_ const CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR &rcpfd, + _Outptr_result_nullonfailure_ CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR **ppcpfd + ) +{ + HRESULT hr; + *ppcpfd = nullptr; + DWORD cbStruct = sizeof(**ppcpfd); + + CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR *pcpfd = (CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR*)CoTaskMemAlloc(cbStruct); + if (pcpfd) + { + pcpfd->dwFieldID = rcpfd.dwFieldID; + pcpfd->cpft = rcpfd.cpft; + pcpfd->guidFieldType = rcpfd.guidFieldType; + + if (rcpfd.pszLabel) + { + hr = SHStrDupW(rcpfd.pszLabel, &pcpfd->pszLabel); + } + else + { + pcpfd->pszLabel = nullptr; + hr = S_OK; + } + } + else + { + hr = E_OUTOFMEMORY; + } + + if (SUCCEEDED(hr)) + { + *ppcpfd = pcpfd; + } + else + { + CoTaskMemFree(pcpfd); + } + + return hr; +} + +// +// Coppies rcpfd into the buffer pointed to by pcpfd. The caller is responsible for +// allocating pcpfd. This function uses CoTaskMemAlloc to allocate memory for +// pcpfd->pszLabel. +// +HRESULT FieldDescriptorCopy( + _In_ const CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR &rcpfd, + _Out_ CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR *pcpfd + ) +{ + HRESULT hr; + CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR cpfd; + + cpfd.dwFieldID = rcpfd.dwFieldID; + cpfd.cpft = rcpfd.cpft; + cpfd.guidFieldType = rcpfd.guidFieldType; + + if (rcpfd.pszLabel) + { + hr = SHStrDupW(rcpfd.pszLabel, &cpfd.pszLabel); + } + else + { + cpfd.pszLabel = nullptr; + hr = S_OK; + } + + if (SUCCEEDED(hr)) + { + *pcpfd = cpfd; + } + + return hr; +} + +// +// This function copies the length of pwz and the pointer pwz into the UNICODE_STRING structure +// This function is intended for serializing a credential in GetSerialization only. +// Note that this function just makes a copy of the string pointer. It DOES NOT ALLOCATE storage! +// Be very, very sure that this is what you want, because it probably isn't outside of the +// exact GetSerialization call where the sample uses it. +// +HRESULT UnicodeStringInitWithString( + _In_ PWSTR pwz, + _Out_ UNICODE_STRING *pus + ) +{ + HRESULT hr; + if (pwz) + { + size_t lenString = wcslen(pwz); + USHORT usCharCount; + hr = SizeTToUShort(lenString, &usCharCount); + if (SUCCEEDED(hr)) + { + USHORT usSize; + hr = SizeTToUShort(sizeof(wchar_t), &usSize); + if (SUCCEEDED(hr)) + { + hr = UShortMult(usCharCount, usSize, &(pus->Length)); // Explicitly NOT including NULL terminator + if (SUCCEEDED(hr)) + { + pus->MaximumLength = pus->Length; + pus->Buffer = pwz; + hr = S_OK; + } + else + { + hr = HRESULT_FROM_WIN32(ERROR_ARITHMETIC_OVERFLOW); + } + } + } + } + else + { + hr = E_INVALIDARG; + } + return hr; +} + +// +// The following function is intended to be used ONLY with the Kerb*Pack functions. It does +// no bounds-checking because its callers have precise requirements and are written to respect +// its limitations. +// You can read more about the UNICODE_STRING type at: +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/unicode_string.asp +// +static void _UnicodeStringPackedUnicodeStringCopy( + __in const UNICODE_STRING& rus, + __in PWSTR pwzBuffer, + __out UNICODE_STRING *pus + ) +{ + pus->Length = rus.Length; + pus->MaximumLength = rus.Length; + pus->Buffer = pwzBuffer; + + CopyMemory(pus->Buffer, rus.Buffer, pus->Length); +} + +// +// Initialize the members of a KERB_INTERACTIVE_UNLOCK_LOGON with weak references to the +// passed-in strings. This is useful if you will later use KerbInteractiveUnlockLogonPack +// to serialize the structure. +// +// The password is stored in encrypted form for CPUS_LOGON and CPUS_UNLOCK_WORKSTATION +// because the system can accept encrypted credentials. It is not encrypted in CPUS_CREDUI +// because we cannot know whether our caller can accept encrypted credentials. +// +HRESULT KerbInteractiveUnlockLogonInit( + _In_ PWSTR pwzDomain, + _In_ PWSTR pwzUsername, + _In_ PWSTR pwzPassword, + _In_ CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus, + _Out_ KERB_INTERACTIVE_UNLOCK_LOGON *pkiul + ) +{ + KERB_INTERACTIVE_UNLOCK_LOGON kiul; + ZeroMemory(&kiul, sizeof(kiul)); + + KERB_INTERACTIVE_LOGON *pkil = &kiul.Logon; + + // Note: this method uses custom logic to pack a KERB_INTERACTIVE_UNLOCK_LOGON with a + // serialized credential. We could replace the calls to UnicodeStringInitWithString + // and KerbInteractiveUnlockLogonPack with a single cal to CredPackAuthenticationBuffer, + // but that API has a drawback: it returns a KERB_INTERACTIVE_UNLOCK_LOGON whose + // MessageType is always KerbInteractiveLogon. + // + // If we only handled CPUS_LOGON, this drawback would not be a problem. For + // CPUS_UNLOCK_WORKSTATION, we could cast the output buffer of CredPackAuthenticationBuffer + // to KERB_INTERACTIVE_UNLOCK_LOGON and modify the MessageType to KerbWorkstationUnlockLogon, + // but such a cast would be unsupported -- the output format of CredPackAuthenticationBuffer + // is not officially documented. + + // Initialize the UNICODE_STRINGS to share our username and password strings. + HRESULT hr = UnicodeStringInitWithString(pwzDomain, &pkil->LogonDomainName); + if (SUCCEEDED(hr)) + { + hr = UnicodeStringInitWithString(pwzUsername, &pkil->UserName); + if (SUCCEEDED(hr)) + { + hr = UnicodeStringInitWithString(pwzPassword, &pkil->Password); + if (SUCCEEDED(hr)) + { + // Set a MessageType based on the usage scenario. + switch (cpus) + { + case CPUS_UNLOCK_WORKSTATION: + pkil->MessageType = KerbWorkstationUnlockLogon; + hr = S_OK; + break; + + case CPUS_LOGON: + pkil->MessageType = KerbInteractiveLogon; + hr = S_OK; + break; + + case CPUS_CREDUI: + pkil->MessageType = (KERB_LOGON_SUBMIT_TYPE)0; // MessageType does not apply to CredUI + hr = S_OK; + break; + + default: + hr = E_FAIL; + break; + } + + if (SUCCEEDED(hr)) + { + // KERB_INTERACTIVE_UNLOCK_LOGON is just a series of structures. A + // flat copy will properly initialize the output parameter. + CopyMemory(pkiul, &kiul, sizeof(*pkiul)); + } + } + } + } + + return hr; +} + +// +// WinLogon and LSA consume "packed" KERB_INTERACTIVE_UNLOCK_LOGONs. In these, the PWSTR members of each +// UNICODE_STRING are not actually pointers but byte offsets into the overall buffer represented +// by the packed KERB_INTERACTIVE_UNLOCK_LOGON. For example: +// +// rkiulIn.Logon.LogonDomainName.Length = 14 -> Length is in bytes, not characters +// rkiulIn.Logon.LogonDomainName.Buffer = sizeof(KERB_INTERACTIVE_UNLOCK_LOGON) -> LogonDomainName begins immediately +// after the KERB_... struct in the buffer +// rkiulIn.Logon.UserName.Length = 10 +// rkiulIn.Logon.UserName.Buffer = sizeof(KERB_INTERACTIVE_UNLOCK_LOGON) + 14 -> UNICODE_STRINGS are NOT null-terminated +// +// rkiulIn.Logon.Password.Length = 16 +// rkiulIn.Logon.Password.Buffer = sizeof(KERB_INTERACTIVE_UNLOCK_LOGON) + 14 + 10 +// +// THere's more information on this at: +// http://msdn.microsoft.com/msdnmag/issues/05/06/SecurityBriefs/#void +// + +HRESULT KerbInteractiveUnlockLogonPack( + _In_ const KERB_INTERACTIVE_UNLOCK_LOGON &rkiulIn, + _Outptr_result_bytebuffer_(*pcb) BYTE **prgb, + _Out_ DWORD *pcb + ) +{ + HRESULT hr; + + const KERB_INTERACTIVE_LOGON *pkilIn = &rkiulIn.Logon; + + // alloc space for struct plus extra for the three strings + DWORD cb = sizeof(rkiulIn) + + pkilIn->LogonDomainName.Length + + pkilIn->UserName.Length + + pkilIn->Password.Length; + + KERB_INTERACTIVE_UNLOCK_LOGON *pkiulOut = (KERB_INTERACTIVE_UNLOCK_LOGON*)CoTaskMemAlloc(cb); + if (pkiulOut) + { + ZeroMemory(&pkiulOut->LogonId, sizeof(pkiulOut->LogonId)); + + // + // point pbBuffer at the beginning of the extra space + // + BYTE *pbBuffer = (BYTE*)pkiulOut + sizeof(*pkiulOut); + + // + // set up the Logon structure within the KERB_INTERACTIVE_UNLOCK_LOGON + // + KERB_INTERACTIVE_LOGON *pkilOut = &pkiulOut->Logon; + + pkilOut->MessageType = pkilIn->MessageType; + + // + // copy each string, + // fix up appropriate buffer pointer to be offset, + // advance buffer pointer over copied characters in extra space + // + _UnicodeStringPackedUnicodeStringCopy(pkilIn->LogonDomainName, (PWSTR)pbBuffer, &pkilOut->LogonDomainName); + pkilOut->LogonDomainName.Buffer = (PWSTR)(pbBuffer - (BYTE*)pkiulOut); + pbBuffer += pkilOut->LogonDomainName.Length; + + _UnicodeStringPackedUnicodeStringCopy(pkilIn->UserName, (PWSTR)pbBuffer, &pkilOut->UserName); + pkilOut->UserName.Buffer = (PWSTR)(pbBuffer - (BYTE*)pkiulOut); + pbBuffer += pkilOut->UserName.Length; + + _UnicodeStringPackedUnicodeStringCopy(pkilIn->Password, (PWSTR)pbBuffer, &pkilOut->Password); + pkilOut->Password.Buffer = (PWSTR)(pbBuffer - (BYTE*)pkiulOut); + + *prgb = (BYTE*)pkiulOut; + *pcb = cb; + + hr = S_OK; + } + else + { + hr = E_OUTOFMEMORY; + } + + return hr; +} + +// +// This function packs the string pszSourceString in pszDestinationString +// for use with LSA functions including LsaLookupAuthenticationPackage. +// +static HRESULT _LsaInitString( + __out PSTRING pszDestinationString, + __in PCSTR pszSourceString + ) +{ + size_t cchLength = strlen(pszSourceString); + USHORT usLength; + HRESULT hr = SizeTToUShort(cchLength, &usLength); + if (SUCCEEDED(hr)) + { + pszDestinationString->Buffer = (PCHAR)pszSourceString; + pszDestinationString->Length = usLength; + pszDestinationString->MaximumLength = pszDestinationString->Length+1; + hr = S_OK; + } + return hr; +} + +// +// Retrieves the 'negotiate' AuthPackage from the LSA. In this case, Kerberos +// For more information on auth packages see this msdn page: +// http://msdn.microsoft.com/library/default.asp?url=/library/en-us/secauthn/security/msv1_0_lm20_logon.asp +// +HRESULT RetrieveNegotiateAuthPackage(_Out_ ULONG *pulAuthPackage) +{ + HRESULT hr; + HANDLE hLsa; + + NTSTATUS status = LsaConnectUntrusted(&hLsa); + if (SUCCEEDED(HRESULT_FROM_NT(status))) + { + ULONG ulAuthPackage; + LSA_STRING lsaszKerberosName; + _LsaInitString(&lsaszKerberosName, NEGOSSP_NAME_A); + + status = LsaLookupAuthenticationPackage(hLsa, &lsaszKerberosName, &ulAuthPackage); + if (SUCCEEDED(HRESULT_FROM_NT(status))) + { + *pulAuthPackage = ulAuthPackage; + hr = S_OK; + } + else + { + hr = HRESULT_FROM_NT(status); + } + LsaDeregisterLogonProcess(hLsa); + } + else + { + hr = HRESULT_FROM_NT(status); + } + + return hr; +} + +// +// Return a copy of pwzToProtect encrypted with the CredProtect API. +// +// pwzToProtect must not be NULL or the empty string. +// +static HRESULT _ProtectAndCopyString( + _In_ PCWSTR pwzToProtect, + _Outptr_result_nullonfailure_ PWSTR *ppwzProtected + ) +{ + *ppwzProtected = nullptr; + + // pwzToProtect is const, but CredProtect takes a non-const string. + // So, make a copy that we know isn't const. + PWSTR pwzToProtectCopy; + HRESULT hr = SHStrDupW(pwzToProtect, &pwzToProtectCopy); + 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 cchProtected = 0; + if (!CredProtectW(FALSE, pwzToProtectCopy, (DWORD)wcslen(pwzToProtectCopy)+1, nullptr, &cchProtected, nullptr)) + { + DWORD dwErr = GetLastError(); + + if ((ERROR_INSUFFICIENT_BUFFER == dwErr) && (0 < cchProtected)) + { + // Allocate a buffer long enough for the encrypted string. + PWSTR pwzProtected = (PWSTR)CoTaskMemAlloc(cchProtected * sizeof(wchar_t)); + if (pwzProtected) + { + // The second call to CredProtect actually encrypts the string. + if (CredProtectW(FALSE, pwzToProtectCopy, (DWORD)wcslen(pwzToProtectCopy)+1, pwzProtected, &cchProtected, nullptr)) + { + *ppwzProtected = pwzProtected; + hr = S_OK; + } + else + { + CoTaskMemFree(pwzProtected); + + dwErr = GetLastError(); + hr = HRESULT_FROM_WIN32(dwErr); + } + } + else + { + hr = E_OUTOFMEMORY; + } + } + else + { + hr = HRESULT_FROM_WIN32(dwErr); + } + } + else + { + hr = E_UNEXPECTED; + } + + CoTaskMemFree(pwzToProtectCopy); + } + + return hr; +} + +// +// If pwzPassword should be encrypted, return a copy encrypted with CredProtect. +// +// If not, just return a copy. +// +HRESULT ProtectIfNecessaryAndCopyPassword( + _In_ PCWSTR pwzPassword, + _In_ CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus, + _Outptr_result_nullonfailure_ PWSTR *ppwzProtectedPassword + ) +{ + *ppwzProtectedPassword = nullptr; + + 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, ake a copy that we know isn't const. + PWSTR pwzPasswordCopy; + hr = SHStrDupW(pwzPassword, &pwzPasswordCopy); + if (SUCCEEDED(hr)) + { + bool bCredAlreadyEncrypted = 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) + { + bCredAlreadyEncrypted = true; + } + } + + // Passwords should not be encrypted in the CPUS_CREDUI scenario. We + // cannot know if our caller expects or can handle an encryped password. + if (CPUS_CREDUI == cpus || bCredAlreadyEncrypted) + { + hr = SHStrDupW(pwzPasswordCopy, ppwzProtectedPassword); + } + else + { + hr = _ProtectAndCopyString(pwzPasswordCopy, ppwzProtectedPassword); + } + + CoTaskMemFree(pwzPasswordCopy); + } + } + else + { + hr = SHStrDupW(L"", ppwzProtectedPassword); + } + + return hr; +} + +// +// Unpack a KERB_INTERACTIVE_UNLOCK_LOGON *in place*. That is, reset the Buffers from being offsets to +// being real pointers. This means, of course, that passing the resultant struct across any sort of +// memory space boundary is not going to work -- repack it if necessary! +// +void KerbInteractiveUnlockLogonUnpackInPlace( + _Inout_updates_bytes_(cb) KERB_INTERACTIVE_UNLOCK_LOGON *pkiul, + DWORD cb + ) +{ + if (sizeof(*pkiul) <= cb) + { + KERB_INTERACTIVE_LOGON *pkil = &pkiul->Logon; + + // Sanity check: if the range described by each (Buffer + MaximumSize) falls within the total bytecount, + // we can be pretty confident that the Buffers are actually offsets and that this is a packed credential. + if (((ULONG_PTR)pkil->LogonDomainName.Buffer + pkil->LogonDomainName.MaximumLength <= cb) && + ((ULONG_PTR)pkil->UserName.Buffer + pkil->UserName.MaximumLength <= cb) && + ((ULONG_PTR)pkil->Password.Buffer + pkil->Password.MaximumLength <= cb)) + { + pkil->LogonDomainName.Buffer = pkil->LogonDomainName.Buffer + ? (PWSTR)((BYTE*)pkiul + (ULONG_PTR)pkil->LogonDomainName.Buffer) + : nullptr; + + pkil->UserName.Buffer = pkil->UserName.Buffer + ? (PWSTR)((BYTE*)pkiul + (ULONG_PTR)pkil->UserName.Buffer) + : nullptr; + + pkil->Password.Buffer = pkil->Password.Buffer + ? (PWSTR)((BYTE*)pkiul + (ULONG_PTR)pkil->Password.Buffer) + : nullptr; + } + } +} + +// +// Use the CredPackAuthenticationBuffer and CredUnpackAuthenticationBuffer to convert a 32 bit WOW +// cred blob into a 64 bit native blob by unpacking it and immediately repacking it. +// +HRESULT KerbInteractiveUnlockLogonRepackNative( + _In_reads_bytes_(cbWow) BYTE *rgbWow, + _In_ DWORD cbWow, + _Outptr_result_bytebuffer_(*pcbNative) BYTE **prgbNative, + _Out_ DWORD *pcbNative + ) +{ + HRESULT hr = E_OUTOFMEMORY; + PWSTR pszDomainUsername = nullptr; + DWORD cchDomainUsername = 0; + PWSTR pszPassword = nullptr; + DWORD cchPassword = 0; + + *prgbNative = nullptr; + *pcbNative = 0; + + // Unpack the 32 bit KERB structure + CredUnPackAuthenticationBufferW(CRED_PACK_WOW_BUFFER, rgbWow, cbWow, pszDomainUsername, &cchDomainUsername, nullptr, nullptr, pszPassword, &cchPassword); + if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) + { + pszDomainUsername = (PWSTR) LocalAlloc(0, cchDomainUsername * sizeof(wchar_t)); + if (pszDomainUsername) + { + pszPassword = (PWSTR) LocalAlloc(0, cchPassword * sizeof(wchar_t)); + if (pszPassword) + { + if (CredUnPackAuthenticationBufferW(CRED_PACK_WOW_BUFFER, rgbWow, cbWow, pszDomainUsername, &cchDomainUsername, nullptr, nullptr, pszPassword, &cchPassword)) + { + hr = S_OK; + } + else + { + hr = GetLastError(); + } + } + } + } + + // Repack native + if (SUCCEEDED(hr)) + { + hr = E_OUTOFMEMORY; + CredPackAuthenticationBufferW(0, pszDomainUsername, pszPassword, *prgbNative, pcbNative); + if (ERROR_INSUFFICIENT_BUFFER == GetLastError()) + { + *prgbNative = (BYTE*) LocalAlloc(LMEM_ZEROINIT, *pcbNative); + if (*prgbNative) + { + if (CredPackAuthenticationBufferW(0, pszDomainUsername, pszPassword, *prgbNative, pcbNative)) + { + hr = S_OK; + } + else + { + LocalFree(*prgbNative); + } + } + } + } + + LocalFree(pszDomainUsername); + if (pszPassword) + { + SecureZeroMemory(pszPassword, cchPassword * sizeof(wchar_t)); + LocalFree(pszPassword); + } + return hr; +} + +// Concatonates pwszDomain and pwszUsername and places the result in *ppwszDomainUsername. +HRESULT DomainUsernameStringAlloc( + _In_ PCWSTR pwszDomain, + _In_ PCWSTR pwszUsername, + _Outptr_result_nullonfailure_ PWSTR *ppwszDomainUsername + ) +{ + HRESULT hr; + *ppwszDomainUsername = nullptr; + size_t cchDomain = wcslen(pwszDomain); + size_t cchUsername = wcslen(pwszUsername); + // Length of domain, 1 character for '\', length of Username, plus null terminator. + size_t cbLen = sizeof(wchar_t) * (cchDomain + 1 + cchUsername +1); + PWSTR pwszDest = (PWSTR)HeapAlloc(GetProcessHeap(), 0, cbLen); + if (pwszDest) + { + hr = StringCbPrintfW(pwszDest, cbLen, L"%s\\%s", pwszDomain, pwszUsername); + if (SUCCEEDED(hr)) + { + *ppwszDomainUsername = pwszDest; + } + else + { + HeapFree(GetProcessHeap(), 0, pwszDest); + } + } + else + { + hr = E_OUTOFMEMORY; + } + + return hr; +} + +HRESULT SplitDomainAndUsername(_In_ PCWSTR pszQualifiedUserName, _Outptr_result_nullonfailure_ PWSTR *ppszDomain, _Outptr_result_nullonfailure_ PWSTR *ppszUsername) +{ + HRESULT hr = E_UNEXPECTED; + *ppszDomain = nullptr; + *ppszUsername = nullptr; + PWSTR pszDomain; + PWSTR pszUsername; + const wchar_t *pchWhack = wcschr(pszQualifiedUserName, L'\\'); + const wchar_t *pchEnd = pszQualifiedUserName + wcslen(pszQualifiedUserName) - 1; + + if (pchWhack != nullptr) + { + const wchar_t *pchDomainBegin = pszQualifiedUserName; + const wchar_t *pchDomainEnd = pchWhack - 1; + const wchar_t *pchUsernameBegin = pchWhack + 1; + const wchar_t *pchUsernameEnd = pchEnd; + + size_t lenDomain = pchDomainEnd - pchDomainBegin + 1; // number of actual chars, NOT INCLUDING null terminated string + pszDomain = static_cast(CoTaskMemAlloc(sizeof(wchar_t) * (lenDomain + 1))); + if (pszDomain != nullptr) + { + hr = StringCchCopyN(pszDomain, lenDomain + 1, pchDomainBegin, lenDomain); + if (SUCCEEDED(hr)) + { + size_t lenUsername = pchUsernameEnd - pchUsernameBegin + 1; // number of actual chars, NOT INCLUDING null terminated string + pszUsername = static_cast(CoTaskMemAlloc(sizeof(wchar_t) * (lenUsername + 1))); + if (pszUsername != nullptr) + { + hr = StringCchCopyN(pszUsername, lenUsername + 1, pchUsernameBegin, lenUsername); + if (SUCCEEDED(hr)) + { + *ppszDomain = pszDomain; + *ppszUsername = pszUsername; + } + else + { + CoTaskMemFree(pszUsername); + } + } + else + { + hr = E_OUTOFMEMORY; + } + } + + if (FAILED(hr)) + { + CoTaskMemFree(pszDomain); + } + } + else + { + hr = E_OUTOFMEMORY; + } + } + return hr; +} \ No newline at end of file diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/helpers.h b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/helpers.h new file mode 100644 index 0000000..324ff07 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/helpers.h @@ -0,0 +1,101 @@ +// +// 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. +// +// Helper functions for copying parameters and packaging the buffer +// for GetSerialization. + +#pragma once + +#pragma warning(push) +#pragma warning(disable: 28251) +#include +#include +#pragma warning(pop) + +#define SECURITY_WIN32 +#include +#include + +#include +#include + +#pragma warning(push) +#pragma warning(disable: 4995) +#include +#pragma warning(pop) + +#pragma warning(push) +#pragma warning(disable: 28301) +#include +#pragma warning(pop) + +//makes a copy of a field descriptor using CoTaskMemAlloc +HRESULT FieldDescriptorCoAllocCopy( + _In_ const CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR &rcpfd, + _Outptr_result_nullonfailure_ CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR **ppcpfd + ); + +//makes a copy of a field descriptor on the normal heap +HRESULT FieldDescriptorCopy( + _In_ const CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR &rcpfd, + _Out_ CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR *pcpfd + ); + +//creates a UNICODE_STRING from a NULL-terminated string +HRESULT UnicodeStringInitWithString( + _In_ PWSTR pwz, + _Out_ UNICODE_STRING *pus + ); + +//initializes a KERB_INTERACTIVE_UNLOCK_LOGON with weak references to the provided credentials +HRESULT KerbInteractiveUnlockLogonInit( + _In_ PWSTR pwzDomain, + _In_ PWSTR pwzUsername, + _In_ PWSTR pwzPassword, + _In_ CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus, + _Out_ KERB_INTERACTIVE_UNLOCK_LOGON *pkiul + ); + +//packages the credentials into the buffer that the system expects +HRESULT KerbInteractiveUnlockLogonPack( + _In_ const KERB_INTERACTIVE_UNLOCK_LOGON &rkiulIn, + _Outptr_result_bytebuffer_(*pcb) BYTE **prgb, + _Out_ DWORD *pcb + ); + +//get the authentication package that will be used for our logon attempt +HRESULT RetrieveNegotiateAuthPackage( + _Out_ ULONG *pulAuthPackage + ); + +//encrypt a password (if necessary) and copy it; if not, just copy it +HRESULT ProtectIfNecessaryAndCopyPassword( + _In_ PCWSTR pwzPassword, + _In_ CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus, + _Outptr_result_nullonfailure_ PWSTR *ppwzProtectedPassword + ); + +HRESULT KerbInteractiveUnlockLogonRepackNative( + _In_reads_bytes_(cbWow) BYTE *rgbWow, + _In_ DWORD cbWow, + _Outptr_result_bytebuffer_(*pcbNative) BYTE **prgbNative, + _Out_ DWORD *pcbNative + ); + +void KerbInteractiveUnlockLogonUnpackInPlace( + _Inout_updates_bytes_(cb) KERB_INTERACTIVE_UNLOCK_LOGON *pkiul, + DWORD cb + ); + +HRESULT DomainUsernameStringAlloc( + _In_ PCWSTR pwszDomain, + _In_ PCWSTR pwszUsername, + _Outptr_result_nullonfailure_ PWSTR *ppwszDomainUsername + ); + +HRESULT SplitDomainAndUsername(_In_ PCWSTR pszQualifiedUserName, _Outptr_result_nullonfailure_ PWSTR *ppszDomain, _Outptr_result_nullonfailure_ PWSTR *ppszUsername); \ No newline at end of file diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/register.reg b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/register.reg new file mode 100644 index 0000000..3664419 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/register.reg @@ -0,0 +1,12 @@ +Windows Registry Editor Version 5.00 + +[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{5fd3d285-0dd9-4362-8855-e0abaacd4af6}] +@="SampleV2CredentialProvider" + +[HKEY_CLASSES_ROOT\CLSID\{5fd3d285-0dd9-4362-8855-e0abaacd4af6}] +@="SampleV2CredentialProvider" + +[HKEY_CLASSES_ROOT\CLSID\{5fd3d285-0dd9-4362-8855-e0abaacd4af6}\InprocServer32] +@="SampleV2CredentialProvider.dll" +"ThreadingModel"="Apartment" + diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/resource.h b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/resource.h new file mode 100644 index 0000000..f701542 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/resource.h @@ -0,0 +1,10 @@ +// +// 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. +// + +#define IDB_TILE_IMAGE 101 diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/resources.rc b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/resources.rc new file mode 100644 index 0000000..ee51dd2 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/resources.rc @@ -0,0 +1,6 @@ +#include "Resource.h" + +// Bitmaps: +IDB_TILE_IMAGE BITMAP DISCARDABLE "tileimage.bmp" +END + diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/samplev2credentialprovider.def b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/samplev2credentialprovider.def new file mode 100644 index 0000000..68c8bf1 --- /dev/null +++ b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/samplev2credentialprovider.def @@ -0,0 +1,5 @@ +LIBRARY SampleV2CredentialProvider.dll + +EXPORTS + DllCanUnloadNow PRIVATE + DllGetClassObject PRIVATE diff --git a/Microsoft Credential prvider v2 example - improved docs WIP/cpp/tileimage.bmp b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/tileimage.bmp new file mode 100644 index 0000000..d4b11ba Binary files /dev/null and b/Microsoft Credential prvider v2 example - improved docs WIP/cpp/tileimage.bmp differ