Sample Credential Providers

 

 

 

                                               

Windows Vista

Sample Credential Providers Overview

 

 

Contents

Terms of Use

Release Notes

SampleCredentialProvider

The SampleCredentialProvider Codebase

Project Structure

Common Tasks For Extending SampleCredentialProvider

Tips & Tricks

Automating The Deployment Process

Developing With CredUI

Debugging LogonUI

If Your System Becomes Unstable

SampleCredUICredentialProvider

SampleAllControlsCredentialProvider

SampleHardwareEventCredentialProvider

SampleWrapExistingCredentialProvider

Default Tiles

Wrapping Existing Credential Providers

Credential Provider Architecture

Summary

Questions

Appendix A – Frequently Asked Questions


Terms of Use

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.

 

Information in this document, including URL and other Internet Web site references, is subject to change without notice.  Unless otherwise noted, the example companies, organizations, products, domain names, e-mail addresses, logos, people, places, and events depicted herein are fictitious, and no association with any real company, organization, product, domain name, e-mail address, logo, person, place, or event is intended or should be inferred.  Complying with all applicable copyright laws is the responsibility of the user. Without limiting the rights under copyright, no part of this document may be reproduced, stored in or introduced into a retrieval system, or transmitted in any form or by any means (electronic, mechanical, photocopying, recording, or otherwise), or for any purpose, without the express written permission of Microsoft Corporation.

 

Microsoft may have patents, patent applications, trademarks, copyrights, or other intellectual property rights covering subject matter in this document.  Except as expressly provided in any written license agreement from Microsoft, the furnishing of this document does not give you any license to these patents, trademarks, copyrights, or other intellectual property.

 

Copyright (c) Microsoft Corporation. All rights reserved.

 

Microsoft, Windows Vista, Windows XP, and Visual Studio are either registered trademarks or trademarks of Microsoft Corporation in the United States and/or other countries.


Introduction

In this document, we’ll take a look at some custom Windows Vista credential provider samples. Specifically, we’ll cover the following topics:

 

1.    Understanding the base SampleCredentialProvider codebase.

2.    Common tasks for extending the base SampleCredentialProvider codebase.

3.    SampleAllControlsCredentialProvider, which exposes each possible UI control.

4.    SampleCredentialFilter, which demonstrates the ICredentialProviderFilter interface.

5.    SampleCredUICredentialProvider, which supports CredUI.

6.    SampleHardwareEventCredentialProvider, which supports asynchronous events.

7.    SampleWrapExistingCredentialProvider, which wraps the default username/password credential provider in Windows Vista.

Release Notes

This is the fourth release of the samples.  The first release targeted the February CTP.  The second release targeted Beta2 and added additional samples.  The third release updates those samples for Vista RTM.  This fourth release updates the samples for Windows 7.

SampleCredentialProvider

Out of the box, SampleCredentialProvider provides a DLL project that exposes the two COM interfaces required to develop a credential provider: ICredentialProvider and ICredentialProviderCredential. ICredentialProvider exposes the functionality to enumerate available credentials, and ICredentialProviderCredential exposes the functionality required for each specific credential during the authentication process.

The SampleCredentialProvider Codebase

The SampleCredentialProvider project provides a working baseline credential provider. This sample is hardcoded to expose two accounts: Administrator and Guest.  The following screenshot shows what this might look like on a domain joined machine.

 

 

Project Structure

The SampleCredentialProvider project includes a small set of files, each with their own purpose:

 

File[s]

Purpose

common.h

Describes the UI and layout of the credentials. Edit this file to change the number and type of UI elements in each credential tile.

CSampleCredential.h/.cpp

Defines the behavior of a credential tile. Edit these files to change the way a tile responds to user input.

CSampleProvider.h/.cpp

Defines the behavior of the credential provider, which typically manages one or more CSampleCredentials. Edit this file to change the way credentials are enumerated.

dll.h/.cpp, samplecredentialprovider.def

Fulfills baseline support for COM server and DLL requirements. You shouldn’t need to edit these files.

guid.h/.cpp

Defines the provider’s GUID. You’ll need to edit guid.h to reference your unique GUID.

helpers.h/.cpp

Provides utility methods for working with UNICODE strings and auth packages. You shouldn’t need to edit these files.

Register.reg, Unregister.reg

Registers and unregisters the sample credential provider, respectively. You’ll need to edit these files to use the GUID from guid.h wherever a GUID appears. You’ll also need to edit the Register.reg file to reflect the name of your object (the first two registry keys) and the name of the DLL (the third registry key).

resource.h, resources.rc

Manages provider resources, such as the tile image. Edit these files if you want to add more resources, such as images, to the credential provider.

tileimage.bmp

The image to display on the credential tile. Edit this file (or add different images) to change the image that appears in the tile.

 

These files are divided between the Helpers\ subfolder, which includes code common to each sample, and the SampleXxx\ subfolders, which are specific to each sample.

Common Tasks For Extending SampleCredentialProvider

Since SampleCredentialProvider provides a great baseline for developing custom providers, it is recommended that you customize it to meet your needs, rather than starting from scratch. The following steps will walk you through the process of customizing the SampleCredentialProvider project that is common to all extensions.

 

We’ll use “MyCredentialProvider” as the new project name, so be sure to change it to reflect the name you want to use. We’re also not going to rename any of the folders or files to reflect the name unless it is required to build successfully, so this may be something you choose to do once you’re comfortable with the codebase.

 

Please note that these samples are intended to be run against the RTM version of Windows 7.  They should be compiled against the RTM version of the Windows SDK.

 

1.    Set up Visual Studio 2008 in Tools | Options to use the executables, includes, and libs from the SDK instead of the ones shipped with VS.  For more info on how to do this, see ReleaseNotes.Htm in the root of the SDK directory.

2.    In the SampleCredentialProvider folder, double-click the SampleCredentialProvider.sln to open it in Visual Studio 2005.

3.    In the Solution Explorer, right-click the SampleCredentialProvider project node and select Rename. Change the name to “MyCredentialProvider” and press Enter to lock in.

4.    In the Solution Explorer, right-click the MyCredentialProvider project node and select Properties. This will launch the MyCredentialProvider Property Pages dialog.

5.    In the left tree view, select the Configuration Properties | C/C++ node.

6.    Make sure the path to your Vista SDK include directory is included in Additional Include Directories. On a default install it ends up at “C:\Program Files\Microsoft SDKs\Windows\v1.0\Include”.

7.    In the left tree view, select the Configuration Properties | Linker node to display the general properties of the linker configuration.

8.    Make sure the path to your Vista SDK library directory is included in Additional Library Directories. On a default install it ends up at “C:\Program Files\Microsoft SDKs\Windows\v1.0\Lib".

9.    Press OK to dismiss the dialog.

10. Open samplecredentialprovider.def. Change “SAMPLECREDENTIALPROVIDER.DLL” to “MYCREDENTIALPROVIDER.DLL”. Save samplecredentialprovider.def.

11. Open guid.h. Replace the GUID in DEFINE_GUID with a unique one. You can generate a unique GUID from Tools | Create GUID. Be sure to remember it for later. Save guid.h.

12. Open Register.reg in notepad or Visual Studio (do not execute it in Explorer). Replace the GUIDs in each registry key with the one created in the last step. Also change “sampleprovider” to “MyCredentialProvider” as well as "SampleCredentialProvider.dll" to "MyCredentialProvider.dll". Save Register.reg.

13. Open Unregister.reg in notepad or Visual Studio (do not execute it in Explorer). Replace the GUID in the registry key with the one used in the Register.reg. Save Unregister.reg.

14. Select Build | Build Solution. If there are any build errors, review the steps above.

15. Copy the freshly built MyCredentialProvider.dll to the System32 directory of the test machine.

16. Copy Register.reg to the test machine and run it from Explorer to register the credential provider.

17. From the Start Menu, select the option to switch users.

18. The login screen should now have an extra Administrator and Guest account.  If all you see is one large tile, click Switch User to see the list of tiles.

 

19. You should be able to log in using the newly created Administrator account tile. The Guest account may or may not be enabled due to the security settings of your system.

20. If you want to remove the sample, copy Unregister.reg to the test machine and run it to unregister the credential provider.

Tips & Tricks

The following are some tips & tricks that can help you during the development cycle. These are useful to keep in mind when reviewing the other samples covered later in this document.

Automating The Deployment Process

If you are developing on a test machine to begin with, you can automate the DLL deployment process of by adding a Post-Build Event that automatically copies the output DLL to the System32 directory. For example, you can go to the Configuration Properties | Build Events | Post-Build Event tab of the project’s Property Pages dialog and set the following for Command Line:

 

copy "$(OutDir)\$(ProjectName).dll" %systemroot%\system32 /Y

 

However, be careful when performing a second build since the Post-Build Event only occurs after a successful build, and Visual C++ will skip the build process if the binaries are already up-to-date.

Developing With CredUI

Although you may not want to support the CredUI scenario in your final credential provider, you may wish to use it during development and debugging if you are developing on the test machine. Since the CredUI scenario runs from a normal desktop session, you’ll be able to attach the Visual Studio debugger to it at runtime, drastically simplifying the process of development and testing.

 

To do this:

1.    Create a new Win32 console project in Visual Studio.

2.    Update the main function of your new project to make a call to CredUIPromptForWindowsCredentials, such as:

 

#include "stdafx.h"

#include <windows.h>

#include <WinCred.h>

 

int _tmain(int argc, _TCHAR* argv[])

{

  BOOL save = false;

  DWORD authPackage = 0;

  LPVOID authBuffer;

  ULONG authBufferSize = 0;

  CREDUI_INFO credUiInfo;

 

  credUiInfo.pszCaptionText = TEXT("My caption");

  credUiInfo.pszMessageText = TEXT("My message");

  credUiInfo.cbSize = sizeof(credUiInfo);

  credUiInfo.hbmBanner = NULL;

  credUiInfo.hwndParent = NULL;

 

  CredUIPromptForWindowsCredentials(&(credUiInfo), 0, &(authPackage),

    NULL, 0, &authBuffer, &authBufferSize, &(save), 0);

}

 

3.    Update the project to link against CredUI.lib. You may need to update your “Additional Include Directories” and “Additional Library Directories” to point to the Windows Vista SDK from the project’s property pages dialog.

4.    Add your credential provider project to the solution.

5.    Build the credential provider and make sure the latest version is deployed to the System32 directory and is registered as a credential provider.

6.    Set breakpoints, etc, and then run the console app in debug mode. Your breakpoints should get hit as appropriate after CredUIPromptForWindowsCredentials is called from the console app.

 

 

 

Debugging LogonUI

In short: hook up kd between your test machine and your debugging machine and then pipe ntsd over kd for logonUI.  There’s a fair amount of info on how to do this on the web (although not specifically for logonUI).  But here’s some basic pointers.

  1. Install the debugger package (http://www.microsoft.com/whdc/devtools/debugging/default.mspx)
  2. Get kd set up between the two computers (for more info look on the web or MSDN)
    1. Hook up your debug cable
    2. On the debugee machine, run something like the following (change command line arguments as necessary)

                                          i.    Bcdedit –debug on

                                         ii.    Bcdedit –dbgsettings serial debugport:1 baudrate:115200

    1. Reboot debuggee
    2. On debugger machine, run something like

                                          i.    Kd.exe –r –k com:port=com1,baud=115200

  1. Copy symbols that you will need locally to the box since NTSD won’t have access to the network
  2.  Create a key named logonui.exe under HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options
  3.   In the logonui.exe key, create a string value named Debugger
    1. Populate that value with the path to ntsd.exe on your machine, for example C:\debuggers\ntsd.exe -d -gG -y <path to local symbols> (space between –y and path required)
    2. –d pipes the NTSD output to KD
    3.   –g ignores the initial startup breakpoint in the process (if you want the process to break instantly when first executed to set BPs, do not set the small g)
    4. –G ignores the termination breakpoint, so the process will exit quietly
    5. –y sets the local symbol path on the debugee for NTSD
  4. Breaking in when a .DLL loads (not required, but good to know if needed)
    1. Follow all of the steps above for the executable that loads the .dll, but do not set the small g flag, then when the process starts, NTSD will break in
    2. Type sxeld <dll name>
    3. Then g the debugger and NTSD will break in on load of that dll and you can set breakpoints, etc.

If Your System Becomes Unstable

During the process of credential provider development, there is a good possibility that you might mess up the credential providers, possibly even crashing LogonUI.

 

Don’t Panic.

 

Typically, you can reboot Windows in safe mode (repeatedly tap F8 during early boot for the menu to do this). From safe mode you should be able to log in using the normal password provider, at which point you can unregister the offending credential provider.

 

The other thing you might do is to accidentally unregister one or more of the built-in credential providers by deleting its key from the registry. For reference, here are the keys from HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers:

 

Provider

Key

GenericProvider

{25CBB996-92ED-457e-B28C-4774084BD562}

PasswordProvider

{6f45dc1e-5384-457a-bc13-2cd81b0d28ed}

Smartcard Credential Provider

{8bf9a910-a8ff-457f-999f-a5ca10b4a885}

 

You should be able to reenter them after rebooting in safe mode.

SampleAllControlsCredentialProvider

The SampleAllControlsCredentialProvider project illustrates the usage of each of the nine UI controls available to credential providers. Here’s an example of what you’ll see when you run this sample from LogonUI:

 

 

Note that the Cancel button is automatically inserted by LogonUI.

 

One of the nice things about this sample is that it differs only slightly from the base sample credential provider discussed earlier. Specifically, the key areas changed are in Common.h and CSampleCredential.h.

 

In Common.h, we’ve added more controls to the SAMPLE_FIELD_ID enumeration, as well as respective entries for s_rgFieldStatePairs and s_rgCredProvFieldDescriptors. As you’ll see from the screenshots above, only the “tile image” and “large text” are configured to display in both selected and deselected mode, whereas the “small text” is configured to only appear when the tile is deselected. All other controls appear only in the selected tile. To change this behavior, modify the second member of the s_rgFieldStatePairs (it’s a CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE).

 

In addition to the new fields in Common.h, it’s necessary to add in support for each type of control to the CSampleCredential.cpp implementation. By default, the base sample only implements support for the String and Bitmap methods, such as GetStringValue, etc. Since those are the only types of controls used in the sample, it was fine to return E_NOTIMPL from the unused control methods, such as GetCheckboxValue. However, since we’re using these controls now, we’ve implemented support for getting and setting their respective values.

 

Control

Required Methods

SFI_TILEIMAGE

GetBitmapValue

SFI_LARGE_TEXT, SFI_SMALL_TEXT

GetStringValue

SFI_EDIT_TEXT,

SFI_PASSWORD

GetStringValue,

SetStringValue

SFI_SUBMIT_BUTTON

GetSubmitButtonValue

SFI_CHECKBOX

GetCheckboxValue,

SetCheckboxValue

SFI_COMBOBOX

GetComboBoxValueCount,

GetComboBoxValueAt,

SetComboBoxSelectedValue

SFI_COMMAND_LINK

GetStringValue,

CommandLinkClicked

 

SampleCredentialFilter

The ICredentialProviderFilter interface is used to disable one or more credential providers from enumerating.  The Filter method of the interface is called by LogonUI or CredUI with two parallel arrays of credential provider CLSIDs and BOOL variables; the filter’s implementation should set the BOOL corresponding to a credential provider to FALSE if that credential provider should be prevented from enumerating.

 

The sample credential provider implementation compares each credential provider CLSID against a hard-coded CLSID and, if it matches, disables that credential provider.  The hard-coded CLSID is GUID_NULL, which is not a valid CLSID.  The user of the samples should replace GUID_NULL with the CLSID credential provider they would like to disable.  For example, one could replace GUID_NULL with the CLSID of one of the sample credential providers described in this document. 

 

Care should be used when replacing GUID_NULL with the CLSID of a credential provider that is needed to log on to the test system.  If the filter mistakenly prevents a necessary credential provider from enumerating, see the discussion of Safe Mode in the FAQ section at the end of this document.

SampleCredUICredentialProvider

Windows Vista introduced, and Windows 7 continues to support, CredUIPromptForWindowsCredentials, which can be thought of as the next generation of CredUIPromptForCredentials (although CredUIPromptForCredentials is still maintained for backwards compatibility). Unlike CredUIPromptForCredentials, CredUIPromptForWindowsCredentials relies on the same credential providers used by the login screen. While implementing these credential providers are fundamentally the same, there is one place you’ll need to make a decision regarding how your credential provider works.

 

The implementation of CSampleProvider::SetUsageScenario contains everything we need to add support for CredUI. This method is called with a specific usage scenario (a CREDENTIAL_PROVIDER_USAGE_SCENARIO), which asks the credential provider if it supports it. By default, SampleCredentialProvider does not support the CPUS_CREDUI usage scenario, which means that an application using CredUIPromptForWindowsCredentials will not be able to access credentials provided through it. However, we have changed this for the SampleCredUICredentialProvider by having requests for the CPUS_CREDUI scenario treated in the same way as CPUS_LOGON – so it will enumerate the same two tiles in the Credui scenario.

SampleHardwareEventCredentialProvider

A common scenario for custom credentials involves external events, such as the arrival of a message generated by a fingerprint scanner. The SampleHardwareEventCredential sample illustrates processing asynchronous events such as these.

 

When run, this sample displays a window with a single button. This window is designed to emulate an external element that has two states: connected and disconnected. When disconnected, the credential merely displays a large text asking the user to connect:

 

 

By pressing the “Press to connect” button, you simulate a hardware event such as swiping a fingerprint and the credential provides different controls, allowing the user to log in:

 

 

Since you cannot change the controls used by a particular credential while handling a hardware event (because you haven’t been Advised on ICredentialProviderCredentialEvents), this sample actually implements two credentials: a “please connect” message credential and an actual “log in” credential, which is effectively the same as the CSampleCredential from the SampleCredentialProvider project. Depending on the state of the connection emulator window, the provider displays the proper one.

 

The window is created on a separate thread, which provides it with a way to pump messages while the provider thread is managed by an external authority. When the button is pressed, the window thread calls in to the provider, asking it to re-enumerate its credentials by calling the CredentialsChanged method on the ICredentialProviderEvents pointer it received in an earlier Advise call:

 

 

When the credentials are enumerated, methods like GetCredentialCount and GetFieldDescriptorCount are called again. In turn, the provider checks the state of the connection emulator and provides data for the appropriate credential. In either case, exactly one credential is always displayed. Note that we’re calling CredentialsChanged from a separate thread, which is okay to do. However, be extra careful when trying to call other methods from the separate thread.

 

While this sample illustrates using a button on a window as an event, you could customize CCommandWindow::ThreadProc to look for any event you need, provided it’s supported on the secure desktop.

SampleWrapExistingCredentialProvider

In some scenarios, you may find that an existing credential provider fits almost all of your needs, with the exception of an additional field or two you need to retrieve from the user. It would be a shame to have to re-implement the functionality of the existing credential provider, so this sample illustrates the process of wrapping an existing credential provider and extending it with two additional fields.

 

Please note that encapsulation (or "wrapping") should be used sparingly.  It is not a one size fits all replacement for the GINA chaining behavior.  Unlike GINA chaining, the behavior you add only applies if the user clicks on your credential tile and does not apply if they click on another credential tile.  Encapsulation is

only done explicitly and should only be done when you know exactly what the behavior of the wrapped credprov is.  It should be used when you want to extend the credential information that the wrapped credprov is getting.  If you merely want to do something extra with the credentials gathered by another credprov, then a network provider is likely more suited to your needs than a credential provider.

 

 

 

In our scenario, we simply attached an extra small text and combobox to the existing password provider’s credentials. We’ll let the existing credential provider decide how many credentials to enumerate, how to enumerate them, and how to authenticate. We’ll also let it deal with the behavior for the controls it defines.

 

Credential providers are COM objects, so they can be created and managed just like any other COM object. In our scenario, we use the CLSID_PasswordCredentialProvider found in CredentialProvider.h to instantiate the provider, and then we proxy most of the calls through to it, returning the results as though they were our own. However, if we receive calls related to our specific extensions, we handle those ourselves.

 

Since we don’t want to limit the functionality of the wrapped credential, it’s important to avoid assumptions where possible. For example, we don’t use a checkbox in our extension, but it is possible that the underlying credential may (if not now, then possibly sometime in the future). As a result, calls to methods we don’t do anything for should still be passed along to the wrapped credential.

 

In some cases, we do handle calls that our wrapped credential needs as well. Fortunately, we can use the dwFieldID parameter to determine whether the referenced fields are ours or theirs. Since our sample appends controls to the wrapped credential, we can perform a simple check to see if the field is ours or theirs

 

However, if you decide to insert controls between controls in the wrapped credential, you’ll need to be extra careful to track which field IDs are yours and which are theirs.

 

For some well-known credential fields, such as the password provider’s password field, you can determine their location by checking the guidFieldType property of their CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR. For example, password field’s CLSID is CPFG_LOGON_PASSWORD. More well-known CLSIDs are available in shlguid.h.

 

Default Tiles

A Credential Provider may specify a default tile when queried regarding the number of tiles it intends to return. Although this is straightforward from the perspective of each individual Credential Provider, developers should keep in mind that LogonUI will not necessarily treat their tile as the default in all cases. The Credential Provider most recently used for interactive logon will receive preference when multiple providers return a default tile. In other words, when two or more providers return a default tile, the tile specified by the Credential Provider last used to log on will be displayed in zoomed view. This behavior does not occur in a remote session because the most recently used Credential Provider is not saved.

 

Credential Providers may also specify default tiles for the CredUI usage scenario. The default tile in this scenario will receive focus when CredUI appears to the user. CredUI resolves multiple default tile conflicts using the same logic as LogonUI. There is no way for a Credential Provider to determine if other providers specify a default tile.

 

The default tile provided by the Credential Provider last used to successfully logon does not need to be the same tile used during the previous logon. The Credential Provider is free to specify any of its tiles when it enumerates the default.

 

Wrapping Existing Credential Providers

Although Microsoft provides a wrapped Credential Provider sample, it is very important that all third parties proceed with extreme caution when implementing a wrapped provider. As long as instantiating multiple instances of the wrapped Credential Provider does not cause problems, wrapping is a safe technique and allows third party developers to avoid rewriting code.  For instance, if the wrapped Credential Provider stores anything globally there may be issues creating two instances.  The Microsoft in-box providers should be safe to wrap by third parties.

 

Developers need to be aware that wrapping can be very dangerous if a Credential Provider filters out the original instance of the wrapped provider. In certain situations this is acceptable, but the consequences of unanticipated filtering should be considered to avoid unexpected (and undesirable) consequences.

 

IT Professionals writing Credential Providers which filter out providers on every box in a domain are most likely safe to filter at their discretion. These administrators own the experience on the desktops in their domain. Due to the nature of the controlled environment, they most likely do not need to worry about conflicting filters unexpectedly breaking their machines. Best practices should be followed when installing new Credential Providers. Deploy in a staged environment prior to rolling your changes out to the entire domain.

 

If you are an ISV or an OEM designing a Credential Provider intended for deployment to desktops you do not control then you will want to proceed with much greater caution. For instance, consider a case where two separate credential providers each wrap and filter the in-box password provider. Imagine at least one of them performs an operation effecting the entire machine before logon. Consider what happens when a user installs both these hypothetical credential providers on the same machine. During logon only one of them will be used. In this scenario the user can reach the desktop without performing an important operation specified by one of the credential providers. 

 

In general, you should

 

-       Only filter out other Credential Providers if you explicitly ask and obtain permission from the administrator in charge of setup

-       Not filter out any of the built-in providers (for instance, the password provider) unless one of the following is true

  • Not filtering an in-box provider will cause user confusion.  Consider the consequences of not filtering the in-box provider – if it does not severely impact user experience (and wouldn’t cause bad problems if a user logged on using this in-box provider), you probably do not need to filter it out.
  • If you are an OEM or ISV and you are guaranteed to be the only 3rd Party Credential Provider on the box, then you are essentially in ITPro category above and you should have no problem.

 

If you are not guaranteed to be the only Credential Provider on the box then you may introduce possible instabilities if you filter any built-in providers. Some Credential Provider authors might be tempted to do this in order to force users to use the installed 3rd party Credential Provider for all logons (for instance, to run a script).  It is bad practice to depend on users logging on using a specific Credential Provider. Third Party Credential Provider authors generally should not assume there will not be other Credential Provider installed on the user’s system.

 

 

 

Credential Provider Architecture

Unlike a GINA, Credential Provider authors cannot and should not write their code provider to enforce running certain code at every logon.  Unless it is absolutely guaranteed that another Credential Provider will never be installed on a the user’s machine, developers should design Credential Providers to provide a way for the user to log on without guarantees that their Credential Provider will be used for log on in all circumstances.

 

Credential Providers are intended to run in parallel. Using wrapping and filtering to attempt to force behavior similar to the GINA model of chaining should not be used unless you are guaranteed no other 3rd party Credential Provider will be installed on the box

Questions

Please contact credprov@microsoft.com with any questions.


Appendix A – Frequently Asked Questions

General Questions

Serialization

Secure Attention Sequence (CTRL+ALT+DEL)

Sessions, the Desktops, and the Credential Provider Life-Cycle

GINA Migration

X86 and X64

Filters and Uninstalling Credential Providers

Single Sign On Scenarios

Logon UI

CredUI

COM

 

General Questions

Q: I'm having a problem with my Credential Provider. Can I send my source code to someone at Microsoft for review?

A: No. Please do not send any source code to the Shell Credential Provider alias. We are happy to provide to answer your questions and provide assistance as you develop your credential provider but will delete any source code you send without reviewing it. It is ok to send pseudo-code (non code that will provide an idea of what you’re trying to do), but please do not send complete function definitions or class implementations. This is important - since it protects our partners, our customers (like you!) and Microsoft.

 

Q: Help! my Credential Provider is busted and I can't uninstall it! What should I do?

A: Don't panic. Restart the machine and logon in SAFE mode (hold F8 down during boot to bring up a menu where you may choose SAFE mode). Once in safe mode, launch regedit and un-register your Credential Provider from HKCR and HKLM. You should now be able to re-boot your computer and logon in normal mode.

 

Q: I want to map third-party unique identities (such as private certified names) into unique Windows credentials. Is this possible?

A: Yes, your Credential Provider needs to collect the third-party unique identity, retrieve the Windows principal name from the third-party account directory, and serialize credentials for the local account. This code should be placed in ICredentialProviderCredential::GetSerialization().

 

Q: Is it possible for my Credential Provider to make changes to other Credential Provider Tiles?

A: Credential Providers should limit themselves to handling their own tiles.

 

Q: How can I display more than 10 Credential Provider fields?

A: This is not supported.

 

Q: Where can I find credentialprovider.h ?

A: By default, credentialprovider.h is installed at "%systemdrive%\Program Files\Microsoft SDKs\Windows\v6.0\Include".

 

Q: I want to write a Credential Provider. Where should I start? Is there an overview document explaining how to write a Credential Provider?

A: Download the our published samples and technical reference. The technical reference contains an overview of the framework (as well as specific details explaining the interfaces). The zip contains an overview document explaining how to build the samples. It also includes pointers for registering and debugging your code.

 

Q: Is it possible to  develop a Credential Provider for Windows Vista using Visual Studio .NET 2003?

A: No. Credential Provider development requires the Windows Vista SDK or the Windows 7 SDK. The Windows Vista SDK requires Visual Studio 8 or higher.  The Windows 7 SDK requires Visual Studio 9.  Developing Credential Providers in Visual Studio .NET 2003 is not supported.

 

Q: The technical reference refers to "tiles". What exactly these?  Is there an underlying object I may access/customize?

A: Tiles correspond to instances of ICredentialProviderCredential. You will need to implement this interface (or IConnectableCredentialProviderCredential in order to control the behavior and fields of your Credential Provider's tiles.

 

Q: Where can I find the latest technical reference for Credential Providers?

A: The latest document we published for download is the Credential Provider Technical Reference.

 

Q: I want to return the image associated with a local user account for CPFT_TILE_IMAGE. Where is this image located?

A: This is not supported.

 

Q: I noticed the Password Provider lives in AuthUI.dll. If I de-register the in-box Password Provider, is it safe to delete AuthUI.dll?

A: No. Windows Vista requires AuthUI.dll for other functionality.

 

Q: What is the purpose of the Generic Provider?

A: The Generic Provider serializes a CPCSS with containing two strings. These strings are not encrypted. The Generic Provider Filter is used to ensure only the Generic Provider is instantiated within CPUS_CREDUI when the CREDUIWIN_GENERIC flag is passed to CredUIPromptForWindowsCredentials.

 

Q: I want to develop my own Smartcard Credential Provider. Where should I start? How can I detect Smart Card removal/insertion?

A: You will want to begin with the Hardware Event Credential Provider sample (see the question above). Your provider will be responsible for detecting smartcard insertion/removal, using SCardGetStatusChange. The other SCardXXX APIs will also be of interest as you develop.

 

Q: Why aren’t Credential Providers instantiated with CPUS_CHANGE_PASSWORD when users navigate to  'Control Panel' -> 'Classic View' -> 'User Account Control Panel' -> 'Change your Password'?

A: We wanted to provide novice users in the home environment a simple and easily discoverable method to change their password. Many users are accustomed to changing their password through this path. Since the control panel, by design, runs in the standard user's security context, we do not load Credential Providers on this control panel.

 

Q: I want to implement a Credential Provider which responds to a hardware event (such as smart card insertion). Is there sample code available?

A: Yes, one of our published samples demonstrates a Credential Provider reacting to a (simulated) hardware event. It provides a good place to start developing a hardware event driven Credential Provider.

 

Q: Have there been any updates to the Windows Vista Credential Provider architecture, API, error codes, or UI in the Vista RC2 release comparing to the RC1?  Will it change in the upcoming RTM release?

A: The Credential Provider interfaces have not changed since RC1. In order to help provide our partners and customers ample time to prepare for Windows Vista, our team has worked very hard to freeze all interfaces since April, 2006.

 

Q: I want to display a dialog box to collect/display information for my Credential Provider. How can I do this?

A: In order to display a MessageBox or dialog, you will need retrieve the Logon UI’s hwnd. To do this, you must call ICredentialProviderCredentialEvent::OnCreatingWindow(). Parent your dialog to this hwnd. Trying to display a dialog using any other parent hwnd is not supported. Credential Providers using any other method to show dialogs during a CPUS can hang the system under anticipated circumstances.

 

Q: Why doesn't the in-box Password Provider include a domain drop-down, like the one that came by default in Windows XP?

A: We eliminated the domain drop down in Windows Vista for the following reasons:

 

-       DsEnumerateDomainTrusts(), the function we called previously to enumerate domains in the drop down, was not guaranteed to return a complete list. It is designed to make a best effort, but in some cases would return only a partial list

-       Any third parties calling DsEnumerateDomainTrusts() will want to store a cached list of domain names in order to prevent users from becoming confused in the event their domain is not returned

-       By design, this API will try to retrieve the list of domains by checking resources over the network. This caused performance issues.

-       Third parties authoring a custom Credential Provider calling this function should be aware they can hang LogonUI if the call takes a long time to return. To prevent this, their solution should be architected so that the call occurs asynchronously.

 

Customers who require the drop down list available in XP may create a Credential Provider that includes this list in its fields. In most organizations, the logic to populate this list will not need to leverage DsEnumerateDomainTrusts. Instead, call a remote service or check a cached list on the machine which is updated via script that runs after logon.

 

Q: Which Microsoft Credential Providers ship in-box on Windows Vista?

A: The GenericProvider, NPProvider, PasswordProvider, RasProvider, and Smartcard Credential Provider ship in-box. These providers (as well as any third party providers installed on the machine) are listed under "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers" and "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\PLAP Providers".

 

Q: My implementation of ICredentialProviderCredential::ReportResult is not called if an error is encountered after serialization in CPUS_CREDUI. Is this a bug?

A: No, this is not a bug. ReportResult is not called in CPUS_CREDUI.

 

Q: My Credential Provider attempts to autosubmit by returning a default credential and setting pbAutoLogonWithDefault to true. When incorrect credentials are serialized, my tile continually attempts to auto-submit and enters an endless loop. What's wrong?

A: There are two different approaches to solving this problem.

 

-       You may create an internal structure to track your attempt to autosubmit credentials and, in the failure case, check it within ICredentialProviderCredential::ReportResult. From there you may change your structure to indicate you do not want to autosubmit, or take some other action.

-       Another approach to the problem is to have your Credential Provider enumerate more than one tile. When GetCredentialCount is called, designate a default tile and attempt to autologon using it. In the event of failure, the user will see the second (and likely empty) credential tile. This seems less intuitive than the alternative (re-enumerating on autologon failure), however you will find it is simpler to code.

 

Q: I can't seem to set the interactive states of my Credential Provider fields to read-only or disabled. I attempt to specify CPFIS_READONLY and CPIFS_DISABLED but this doesn't seem to work. What's wrong?

A: Nothing is wrong with your code. CPFIS_READONLY and CPFIS_DISABLED are not implemented for Windows Vista. We plan to add support for these interactive states in a future release.

 

Q: How can I get my Credential Provider to respond to AutoAdminLogon?

A: Set HKLM\Software\Microsoft\Windows NT\CurrentVersion\winlogon “AutoAdminLogon” to 1, filter the Password Provider, and submit a credential to autologon with.

 

Q: I want to configure my Credential Provider to perform autologon. How may I accomplish this?

A: In order to do this, you must set configure the machine for auto logon, configure your Credential Provider to specify a default credential, and set pbAutoLogonWithDefault to true. To set configure your machine for autologon, you must set the following registry key

 

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon]

“AutoLogonAdmin”=”1”

   

Q: I would like to hide and reveal one a field in my Credential Provider dynamically. How can I do this?

A: When your tile is advised, use methods exposed by ICPCE. When your tile is not advised, you must perform a full re-enumeration in order to change a field’s state from hidden to unhidden.

 

Q: I downloaded the Beta 2 samples and tried to compile them. I get an error trying to link to bufferoverflowu.lib. What should I do?

A: Changes in the build and SDK exposed this problem after we released the Beta 2 samples. We will release new versions of the samples at RTM. Check on msdn to see if the updated versions are available. You may also correct the problem with the samples you currently have by taken the following steps...

 

-       Comment out the line “#include <winres.h>” in resource.rc

-       In the solution explorer, right-click the node below the top-level solution and select Properties from the context menu

-       A dialog will open

-       In the left-hand tree control, navigate to Configuration Properties => Linker => Input

-       Remove bufferoverflowu.lib

-       Add gdi32.lib, ole32.lib, user32.lib

 

Serialization

Q: My application respects the GP Setting indicating smartcard must be used for domain credentials. I don't want to uninstall or filter the Password Provider, but I don't want password providers to enumerate in CredUI. How can I prevent non-smartcard providers from enumerating?

A: Simply stated, CredUIPromptForCredentials has no way to specify "smartcard only". Since both the Smart Card Provider and the Password Provider recognize the Kerberos authentication package, passing in Kerberos as the auth package will not prevent the Password Provider from enumerating credentials. To work around this limitation, your application must pass a partially filled Smart Card credential blob as well as the flag CREDUIWIN_IN_CRED_ONLY when calling CredUIPromptForWindowsCredentials. Refer to the Credential Provider Samples for additional information. Alternatively, if your credential provider is not capable of serializing Kerberos certificate credentials, it is good practice to register a filter and prevent your credential provider from enumerating tiles when this policy setting is enabled.

 

Secure Attention Sequence (CTRL+ALT+DEL)

Q: How can I initiate Ctrl+Alt+Del from my Credential Provider?

A: This is not supported. Requiring Ctrl+Alt+Del (CAD) for interactive logon is intended to defeat spoofing (for the purposes of credential harvesting). A Credential Provider generated substitute for this sequence does not provide protection against spoofing. Treating arbitrary hardware events (such as USB insertion, Smartcard insertion, etc) does not initiate a transition to the secure desktop in all scenarios. As a result, it's possible for an application running with standard user privileges to spoof the logon screen and steal a users credentials. Organizations who consider CAD to be inconvenient are able to disable the Group Policy setting requiring CAD during interactive logon. We encourage organizations to carefully consider the security implications of disabling this group policy setting before doing this.

 

Sessions, the Desktops, and the Credential Provider Life-Cycle

Q: Is my Credential Provider loaded in Session 0?

A: Your Credential Provider is instantiated in the same session as LogonUI. In Windows Vista, Session 0 is reserved for services and is almost never visible. When it is visible, LogonUI is not. The particular TS session your Credential Provider is instantiated is generally not a useful piece of information.

 

Q: Which desktop does LogonUI instantiate my Credential Provider on?

A: TS Sessions contain a single Window Station. This Station has may contain multiple desktops. LogonUI loads your Credental Provider on the Winlogon desktop.

 

Q: Is it permitted to use my Credential Provider to run SYSTEM applications on the Winlogon Desktop?

A: Although Windows Vista does not explicitly prevent SYSTEM applications from running on the Winlogon Desktop, this practice is not recommended and is strongly discouraged. SYSTEM privileges may be used to gain access to any resource on the computer. Running SYSTEM applications on the winlogon desktop exposes security vulnerabilities.

 

Q: Do I need to worry about malware running on the user desktop trying to sniff key strokes entry on the Winlogon Desktop?

A: Desktops are an isolation boundary for windows messages. Unless the TCB is compromised, your Credential Provider should not need to worry about this problem.

 

Q: Is it permitted to use my Credential Provider to run USER applications on the Winlogon Desktop?

A: USER applications may not run on the Winlogon Dekstop.

 

Q: How long does my Credential Provider exist?

A: In order to ensure forward compatibility with future versions of Windows, we recommend architecting your Credential Provider to collect and serialize credentials. Providers that depend on being created and destroyed at a specific time are not guaranteed to be forwards compatible.

 

Q: Does my Credential Provider exist throughout an entire TS session?

A: No. Credential Providers are created when necessary and then destroyed. LogonUI and CredUI both destroy Credential Provider objects before exiting.

 

GINA Migration

Q: My organization used a custom GINA in a previous version of Windows. How do Credential Providers interact with GINA?

A: They do not. Beginning with Windows Vista, Gina’s are no longer supported. Your solution will need to be re-factored in order to work properly on Windows Vista.

 

Q: I used biologon.dll with my GINA in Windows XP. Will this work in Windows Vista?

A: No. Biologon.dll does not work with Windows Vista.

 

Q: Are notification packages supported in Windows Vista?

A: Winlogon Notification Packages are no longer supported. Please refer to the published whitepaper Winlogon Notification Packages Removed Impact on Windows Vista Planning and Deployment.doc.

 

Q: In previous versions of Windows Vista, I wrote a custom GINA and registered a custom Winlogon Notification Package to detect events such as StartShell and StopScreenSaver. How can I use this in Windows Vista?

A: If you wish take action on a state transition in Windows Vista, you will need to use SENS or SCM notifications. Both solutions can be used on Windows Vista and earlier versions.

 

Q: My GINA used to load an application after the user successfully logged on. How can I do this with my Credential Provider?

A: Credential Providers are responsible for gathering information for the purposes of authentication and packaging that information as a credential blob. Credential Provider should not be designed to start applications to run on a user desktop on behalf of a specific user. In a perfect world the Credential Provider will already have been destroyed by the time applications are allowed to start as the logged on user. Technologies such as startup scripts are more appropriate for this purpose.

 

X86 and X64

Q: I downloaded the latest SDK, but I don't see the libraries I need to build 64-bit Credential Providers. Where can I find these libraries?

A: On x86 machines, 64-bit libraries are not installed by default. You must specifically select to install them during setup. Do this by drilling down through the nodes of the tree control the optional components.

 

Q: In Windows Vista x64, is a 32-bit CSP required to support custom CAPI?

A: When a 32-bit application, running on a 64-bit machine, calls CredUIPromptForWindowsCredentials, 32-bit versions of the Credential Providers are needed in all scenarios where secure desktop prompting is not enabled. Any 32-bit CAPI application will also use the 32-bit CSPs and card modules in this case. 64 bit versions of the Credential Providers, CSPs, and card modules are needed to support this scenario when secure prompting is enabled.

 

Q: Does the windows logon process use the 32 or 64 bit version of the CSP for smart card logon?

A: LogonUI is always native.

 You will need to compile a 64 bit version of your Credential Provider in order to support CPUS_LOGON on machines running a 64-bit version of Windows Vista.

 

Q: On Windows x64 bit platform, if an application using CAPI (for smart card logon for example) is running in 32 bit, will the WOW64 transparently redirect the CSP calls to 64 bit version of registered CSP or should two versions of the CSP be registered in the system, one for 32 and one for 64 bit applications?

A: Dlls like credential providers (CPs) and CSPs (and card modules) need to match the version of their host (the exe). 64-bit exes load only 64-bit dlls. 32-bit exes load only 32-bit dlls. You may need 32-bit versions of the each component on 64-bit platforms in order to support WOW64.

 

 


Filters and Uninstalling Credential Providers

Q: Does the Shell Credential Provider team publish sample code illustrating how to implement ICredentialProviderFilter?

A: No. Please refer to the Credential Provider Technical reference for details regarding authoring a Credential Provider Filter.

 

Q: My implementation of ICredentialProviderFilter is not loaded in SAFE mode. Is this a bug? Is there a way to run my Filter in SAFE mode?

A: This is not a bug. SAFE mode is intended to serve as a workaround in order to correct repair Operating Systems malfunctioning due to incorrectly configured components such as device drivers. By default, only the in-box Password Provider is loaded in SAFE mode. The in-box Smart Card Provider is also available if the machine is booted into SAFE mode with networking. This provides a fallback in case of a bad error. To over-ride the fallback logic and force logonUI to load Credential Provider filters in SAFE Mode, create and set the following registry key:

 

[HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers]

“ProhibitFallbacks”=dword:1

 

Important Note:  By setting this registry key, you will explicitly override the fallback safety mechanism Windows Vista supplies to troubleshoot configuration errors and malfunctioning Credential Providers. Users may be blocked from logon in the event of Credential Provider failure.

 

Q: Is it possible to de-register the in-box Credential Providers?

A: Yes, these may be de-registered just like any other COM object. They are registered under HKEY_CLASSES_ROOT\CLSID. Please refer to CredentialProvider.idl for a complete list of Credential Providers which ship in-box and corresponding CLSIDs.

 

Important note: By default, Windows Vista requires the Password Provider and Smart Card Provider to boot into SAFE mode. After de-registering Credential Providers on a machine, you will need to prohibit fallbacks (see question above) in order to load new Credential Providers in SAFE mode.

 

Single Sign On Scenarios

Q: I want to create a VPN connection after authentication. How can I do this with a Credential Provider?

A: Credential Providers are destroyed after logon. Connecting to a VPN after logon requires a network provider.

 

Q:  Is it possible to write a single sign on Credential Provider supporting 802.1x Authentication + Windows logon?
A: Yes. IConnectableCredentialProviderCredential, which inherits all methods from ICredentialProviderCredential, is suitable for this scenario. Your Credential Provider should collect credentials from the user and use them to connect to the a WAP through IConnectableCredentialProviderCredential::Connect().If the connection is successful, attempt to Serialize credentials for local machine logon using GetSerialization().

 

Note: Connect() will always be called before GetSerialization().

 

Q: Do you offer a sample Credential Provider illustrating this?

A: No.

 

Logon UI

Q: May I add items to the security options screen?

A: This is not supported.

 

Q: Will a CP implementing CPUS_LOGON work in TS?

A: Yes it will, since LogonUI in remote sessions loads CPs normally.

 

Q: I’d like to display a common control on Logon UI that doesn’t exist in the control set defined by the Credential Provider. Is this possible?

A: This is not recommended or supported.

 

Q: I’ve used ICredentialProviderCredentialEvents::OnCreatingWindows() to retrieve LogonUI’s HWND, and display a dialog parented to LogonUI. My dialog doesn’t appear with Glass and aero-themed menus. Why not?

A: The theme service is not enabled on the Winlogon Desktop. The theme service provides visual styles for Windows and applications. It is not possible to use glass, specify fonts, or specify colors for your dialog without the theme service.

 

Q: How can I change the background of LogonUI?

A: This is not supported in Windows Vista.

 

Q: How do I write my Credential Provider to autosubmit credentials when it has all the required information?

A: Your Credential Provider may autosubmit Credentials in two ways.

 

-       Specify a default Credential and set pbAutoLogonWithDefault to true in response to GetCredentialCount()

-       Set pbAutoLogon to true in response to SetSelected()

 

Both of these methods will cause a call to GetSerialization (and Connect() if the credential is an instance of IConnectableCredentialProviderCredential). To initiate auto-submission of credentials at a an arbitrary point in time, your provider may call ICredentialProvider::CredentialsChanged. This call will force a full re-enumeration of credential tiles. When LogonUI calls ICredentialProvider::GetCredentialCount, specify a default tile and set the value of pbAutoLogonWithDefault to true (as mentioned above).

 

Note: Developers should be aware that race conditions may exist if another provider installed on the machine also requests to autologon with the default during this call. Please refer to the technical reference for a detailed explanation of the logic LogonUI uses to decide which credential to submit to Winlogon in this case.

 

CredUI

Q: What is CPUS_CREDUI?

A: Windows Vista introduces a new public API for credential collection, CredUIPromptForWindowsCredentials. To see an instance of this API being used to collect credentials, open the remote desktop client (mstsc.exe), specify a target machine, and click 'connect'. The Windows Security dialog that appears loads Credential Providers in the CPUS_CREDUI scenario.

 

Q: My application calls CredUIPromptForCredentials. How can I leverage Credential Providers using this API?

A: CredUIPromptForCredentials is very different than CredUIPromptForWindowsCredentials. The legacy API (CredUIPromptForCredentials) is not aware of Credential Providers. In order to collect credentials within your application using Credential Providers, you must revise your application's code to call CredUIPromptForWindowsCredentials.

 

Q: How can I call CredUI and specify it only load a specific Credential Provider?

A: In general, this is not supported. Credential Providers aim to support credential agility. This means that if

an application accepts Kerberos credentials, any credential provider that knows how to serialize Kerberos credentials will be able to be used.

 

Q: How can I specify my Credential Provider to load in the User Account Control Prompt?

A: If your credential provider supports CPUS_CREDUI, it will be available in the UAC prompt. To support the CredUI usage scenario, your provider must return S_OK  when CPUS_CREDUI is passed in through SetUsageScenario(). Your provider must indicate it will enumerate at least one tile in in response to GetCredenitalCount() and then supply the credential in GetCredenialsAt(). Under normal circumstances, if your provider behaves in this way, your tile will appear in the UAC prompt.

 

Note: Please refer to Group Policies related to the UAC prompt. Specifically, if you intend for your credential provider to appear in the UAC prompt, it should respect the Group Policy setting “Enumerate administrator accounts on elevation”. Please refer to this GP for more information.

 

Q: How can I differentiate between CPUS_CREDUI for credential collection in TS and credential collection in the UAC prompt?

A: In general, we encourage Credential Provider authors to write their CP such that no differentiation in these two states is necessary. Enumerating a default (or 'empty') tile in CREDUI_CPUS is a good practice. Developers may examine the flags passed in ICredentialProvider::SetUsageScenario to gather more context regarding the scenario at run time. Specifically, CREDUIWIN_ENUMERATE_ADMINS will always be passed in CPUS_CREDUI when tiles are requested for the UAC dialog. Note: Referring to UAC specifically in your credential provider when you see CREDUIWIN_ENUMERATE_ADMINS is bad form since this flag may be passed during credential collection with CredUI in scenarios other than the UAC dialog.

 

 

COM

Q: How may I call methods exposed by ICPCE?

A: Methods exposed by ICPCE may only be called when your provider is advised. Your provider is advised around ICPC calls. Your provider is responsible for storing the ICPCE pointer while advised, and should null this pointer when unadvised. Do not pass the ICPCE pointer to other threads and attempt to call these methods. The ICPCE interface must be called from the credential provider’s thread.

 

Q: What apartment threading model should I write my CP to run in?

A: CPs must be written to run STA.

 

Q: May I call the methods on the ICPE interface asynchronously?

A: Yes, the methods on the ICPE interface can be called asynchronously.

 

Q: Do credential providers ever have to worry about reentrancy? 

A: No. There are no supported scenarios in which Credential Providers need to worry about reentrancy.

 

-       A separate TS session (with individual instances of Winlogon and LogonUI) is used each time a user is prompted for logon credentials.

-       Calls to CredUIPromptForWindowsCredentials return after a credential provider successfully serializes credentials.

 

Q: Can CPs be implemented in managed code through COM interop?  Or must they be native code?

A: Microsoft recommends all code running in system context be implemented in native code. We do not support Credential Providers written in managed code.

 

 

 

 

 

posted @ 2017-07-07 15:57  dozeoo  阅读(5728)  评论(0编辑  收藏  举报