MSDN Magazin > Home > Ausgaben > 2007 > January >  Desktopsicherheit: Erstellen benutzerdefinierte...
Desktopsicherheit
Erstellen benutzerdefinierter Anmeldeverfahren mithilfe von Anmeldeinformationsanbietern für Windows Vista
Dan Greif

Themen in diesem Artikel:
  • Die neue Anmeldeinformationsanbieter-Architektur
  • Gründe für die Abschaffung der GINA-basierten Authentifizierung
  • Mehrstufige Authentifizierung
  • Entwickeln und Debuggen eines Anmeldeinformationsanbieters
In diesem Artikel werden folgende Technologien verwendet:
Windows Vista, C++
Laden Sie den Code für diesen Artikel herunter: CredentialProviders2007_01.exe (241 KB)
Code online durchsuchen
Windows Vista eröffnet Entwicklern viele neue Möglichkeiten für die Integration in die Plattform. Das neue Anmeldeinformationsanbieter-Modell stellt eine der umfassendsten Änderungen dar, durch die sich neue, vom Betriebssystem unterstützte Benutzerauthentifizierungsverfahren leichter umsetzen lassen. Damit wird das GINA-Modell (Graphical Identification and Authentication) ersetzt, das für die Entwickler eher schwierig zu verstehen und zu implementieren war und das zudem hohe Supportkosten für Microsoft verursachte.
Warum also ist eine Änderung an der Plug-In-Schnittstelle für die Windows®-Anmeldung so interessant? Der Anmeldebildschirm ist das Erste, was die Benutzer beim Einschalten des Computers sehen. Nachdem die Anmeldung nunmehr von Anmeldeinformationsanbietern gesteuert wird, ist es viel einfacher, das Anmeldeverfahren anzupassen und die Authentifizierungsmethoden einzubinden, die die Anforderungen des Unternehmens am besten erfüllen. Einfach gesagt, eröffnen Anmeldeinformationsanbieter eine unkomplizierte Möglichkeit, um eine höhere, stabilere Sicherheit zu gewährleisten.

Vergleich von Alt und Neu
Die GINA-basierte Anmeldearchitektur möchte ich an dieser Stelle gar nicht erst ausführlich besprechen. Stattdessen möchte ich diese beiden Architekturen miteinander vergleichen, damit Sie die neue Architektur und die damit verbundenen Änderungen besser verstehen.
In Umgebungen vor Windows Vista™ gibt es für jede Sitzung eine Instanz der Windows-Anmeldung (winlogon), die die interaktive Anmeldesequenz der Sitzung steuert. (Abbildung 1 zeigt die bisherige Anmeldearchitektur unter Windows XP und Windows Server® 2003. Bei einem gerade gestarteten System wird die interaktive Anmeldung an der Konsole stets in Sitzung Null durchgeführt. Sitzung Null umfasst Hostsystemdienste und andere wichtige Prozesse, wie z. B. den Prozess der lokalen Sicherheitsinstanz. (Viele Prozesse, die in Sitzung Null ausgeführt werden, sind in Abbildung 1 gar nicht dargestellt.)
Abbildung 1 GINA-Anmeldearchitektur 
Die auf dem Computer registrierte GINA wird in den Prozessbereich der Windows-Anmeldung geladen. (An dieser Stelle wäre auch die als „GINA chaining“ (GINA-Verkettung) bezeichnete Konfiguration möglich. Eine so komplizierte Konfiguration lässt sich jedoch nur schwer testen und unterstützen.) Abschließend werden durch GINA verschiedene Aufrufe an die Benutzeranmeldung (LogonUser) und an zugehörige Authentifizierungs-APIs vorgenommen.
Bei Windows Vista wird die Sitzung Null unter keinen Umständen für die interaktive Anmeldung verwendet (siehe Abbildung 2). Dies ist gut für die Sicherheit: So entsteht eine Sitzungsbegrenzung, die alle computerabhängigen Prozesse von den benutzerabhängigen Prozessen trennt. Darüber hinaus wird nun der globale Namespace des Kernel stärker kontrolliert, weil die von Benutzeranwendungen erstellten Objekte standardmäßig von diesem Namespace ferngehalten werden.
Abbildung 2 Neue Anmeldearchitektur 
Es gibt weiterhin eine Instanz der Windows-Anmeldung in jeder Sitzung neben Sitzung Null. Die Abbildung zeigt, dass mehrere Anmeldeinformationsanbieter auf dem System registriert und durch den neuen Prozess der Anmeldebenutzeroberfläche (LogonUI) geladen wurden.
Eine weitere wichtige Änderung betrifft die Komponente, die für das Rendern des grafischen Teils der Anmeldung zuständig ist. Bislang wurde dies von GINA durchgeführt. Folglich hätte eine Komponente eines Drittanbieters das Rendern übernehmen können. In der neuen Architektur geht diese Aufgabe an die Anmeldebenutzeroberfläche, die in das Betriebssystem integriert ist.
Wie wird also das anbieterabhängige Benutzeraufforderungsverhalten im neuen Modell erreicht? Bei der Anmeldeinformationsanbieter-Architektur muss jeder Anbieter seine Benutzeroberflächenelemente auflisten. Beispielsweise könnte ein Anbieter für die Anmeldebenutzeroberfläche festlegen, dass zwei Bearbeitungsfelder, zwei Beschriftungen, ein Kontrollkästchen und eine Bitmap erforderlich sind. Die Anmeldebenutzeroberfläche rendert diese Steuerelemente im Namen des Anmeldeinformationsanbieters. Damit ist ein großer Schritt auf dem Weg zum oben genannten Ziel getan: eine konsistente Darstellung und Vorgehensweise, die eine breite Palette an flexiblen Authentifizierungsverfahren unterstützt.
Das für Anmeldeinformationsanbieter zuständige Microsoft-Entwicklungsteam hat sich überlegt, dass externe Entwickler wohl lieber mit einem COM-basierten Plug-In-Modell arbeiten würden. In der Anfangsphase der Entwicklung von Windows Vista basierte der erste interne Entwurf der neuen Schnittstelle (ebenso wie GINA) ausschließlich auf LoadLibrary und auf Funktionszeigern. Die Erfahrungen aus diesem ersten Versuch wurden in den nachfolgenden COM-basierten Neuentwurf eingearbeitet. Die entstehende Schnittstelle ist effizienter und unkomplizierter. Betrachten wir nun den Beispielcode, der uns beim Einstieg in die credprov-Schnittstelle helfen soll.

Ein Hybrid-Anmeldeinformationsanbieter
Der Zeitpunkt für dieses neue Plug-In-Modell könnte kaum besser sein. (Vielleicht ist es sogar ein wenig überfällig.) Nunmehr können Entwickler leichter die Nachfrage nach mehrstufigen Authentifizierungsverfahren bedienen und gleichzeitig eine Methode bieten, die konsistent in Bezug auf die standardmäßigen Verfahren von Microsoft ist.
Andererseits ist die neue Schnittstelle ziemlich abstrakt. Eine ebenso abstrakte Beschreibung würde nur Langeweile hervorrufen. Am besten sehen Sie sich den Entwurf, die Entwicklung und die Tests eines neuen Anmeldeinformationsanbieters Schritt für Schritt an. Damit wird außerdem die von Microsoft schon bereitgestellte Dokumentation sinnvoll ergänzt. Entsprechende Hinweise finden Sie in der Randleiste „Weitere Ressourcen“.
Ich habe mit dem „Hybrid-Anmeldeinformationsanbieter“ ein Beispiel erstellt, anhand dessen ich einige der raffinierten neuen Features erläutern möchte. Beim Hybrid-Anmeldeinformationsanbieter können ein Benutzername, ein Kennwort und ein Domänenname auf einer Smartcard gespeichert werden. Sobald der Benutzer die Karte einführt, wird er automatisch angemeldet. (Der Beispielcode steht auf der MSDN®Magazin-Website zum Herunterladen bereit.) Ich habe den Code nicht von Grund auf neu geschrieben, sondern Code aus drei Quellen kombiniert:
  • Der kennwortbasierte Beispiel-Anmeldeinformationsanbieter aus dem Microsoft® Windows SDK.
  • Das alte PropCert-Beispiel, ebenfalls aus dem SDK. Den Kern bildet ein Win32®-Thread, der zertifikatbasierte Smartcard-Anmeldeinformationen liest.
  • Der Beispielcode aus meinem Artikel in der Ausgabe des MSDN Magazins vom November 2006. In diesem Artikel wurde die Interaktion mit dem Windows-Smartcardsubsystem über verwalteten Code erörtert.
Zum Beispielcode aus meinem Artikel vom November 2006 ist eine Erläuterung erforderlich. Die Anmeldeinformationsanbieter-Architektur und der zugehörige Host unterstützen nur systemeigenen Code. Bei meinem ersten Artikel lag der Schwerpunkt zwar auf verwaltetem Code, doch fand sich hier eine systemeigene Unterstützungs-DLL, mit der die neue Smartcardmodul-Schnittstelle problemlos verfügbar gemacht werden kann. Der Hybrid-Anmeldeinformationsanbieter basiert auf dieser Unterstützungs-DLL. Der vollständige Quellcode für diese DLL ist im Download enthalten, der im Artikel vom November 2006 zu finden ist.
Insgesamt lässt sich sagen, dass ein Großteil der Codebasis für den Hybrid-Anmeldeinformationsanbieter nicht neu ist. Unterm Strich fällt nur wenig Zeitaufwand für das Testen und das Debuggen an. Die Hauptdebugphase hat weniger als einen Tag in Anspruch genommen, was sehr für die Benutzerfreundlichkeit der neuen Schnittstelle spricht.
Im Folgenden soll dargelegt werden, was ich mit dem Beispiel-Anmeldeinformationsanbieter erreichen möchte.

Die Anforderungen
Beim Planen des Hybrid-Anmeldeinformationsanbieters hatte ich folgende Anforderungen im Hinterkopf:
  • Zugrundelegung einer Smartcard
  • Maximale Wiederverwendung von Code
  • Minimale zusätzliche Konfigurations- und Infrastrukturanforderungen
Dies hat mich zum Hybridansatz geführt, also Kennwort (für die Sicherheit) plus Smartcard (aus Gründen der Praktikabilität). Das Konzept des Hybridanbieters basiert auf Benutzername und Kennwort, und deshalb zog ich den vereinfachten Beispielkennwortanbieter des Plattform SDK als Grundlage heran. Dann fügte ich das PropCert-Beispiel aus dem SDK hinzu. Dieses Beispiel enthält die Logik zum Auflisten von Smartcard-Lesern, Karten und digitalen Zertifikaten. Ich stellte mir vor, dass ich einfach nur die zertifikatbasierte Logik in PropCert durch neuen Code zum Lesen meiner eigenen Anmeldeinformationen ersetzen und dann die beiden Beispiele miteinander verbinden musste.
Das Kennwort für die Anmeldeinformationen soll von einer Smartcard gelesen werden, was eine weitere Anforderung impliziert: ein Tool, mit dem sich eine Smartcard mit diesen Anmeldeinformationen initialisieren lässt. Dieses Initialisierungstool wird am Ende dieses Artikels näher erläutert.
Mit diesen Anforderungen im Hinterkopf sollen nun der Entwurf für die Anmeldeinformationsanbieter-Architektur und ihre Auswirkungen auf den Beispielcode betrachtet werden.

Der Entwurf
Als Erstes betrachten wir den Entwurf der Anmeldeinformationsanbieter-Architektur, und zwar aus Sicht eines Anmeldeinformationsanbieters zur Laufzeit.
Das Hybridbeispiel muss zwar noch ausführlich erläutert werden, soll jedoch hier schon als Grundlage für die Analyse der neuen, aktiven Anmeldeinformationsanbieter-Architektur dienen. Zur Vereinfachung dieser Erläuterung habe ich den Beispielcode mit Debugablaufverfolgung ausgestattet. Die Ablaufverfolgung besteht aus einem Aufruf von OutputDebugString aus jeder implementierten Anmeldeinformationsanbieter-Routine. In diesen Ablaufverfolgungsaufrufen werden zwei Abkürzungen verwendet. Die Aufrufe der neuen ICredentialProvider-Schnittstelle (Auszug in Abbildung 3) beginnen mit „Provider::“. Die Aufrufe der ICredentialProviderCredential-Schnittstelle (siehe Abbildung 4) beginnen mit „Credential::“. Beachten Sie, dass alle Schnittstellen, die mit dem Anmeldeinformationsanbieter zusammenhängen, im neuen öffentlichen Header „credentialprovider.h“ definiert sind.
ICredentialProviderCredential : public IUnknown
{
    HRESULT STDMETHODCALLTYPE Advise( 
        /* [in] */ ICredentialProviderCredentialEvents *pcpce);
    
    HRESULT STDMETHODCALLTYPE UnAdvise( void);
    
    HRESULT STDMETHODCALLTYPE SetSelected( 
        /* [out] */ BOOL *pbAutoLogon);
    
    HRESULT STDMETHODCALLTYPE SetDeselected( void);
    
    HRESULT STDMETHODCALLTYPE GetFieldState( 
        /* [in] */ DWORD dwFieldID,
        /* [out] */ CREDENTIAL_PROVIDER_FIELD_STATE *pcpfs,
        /* [out] */ CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE *pcpfis);
    
    HRESULT STDMETHODCALLTYPE GetStringValue( 
        /* [in] */ DWORD dwFieldID,
        /* [string][out] */ LPWSTR *ppsz);
    
    HRESULT STDMETHODCALLTYPE GetBitmapValue( 
        /* [in] */ DWORD dwFieldID,
        /* [out] */ HBITMAP *phbmp);
    
    HRESULT STDMETHODCALLTYPE GetCheckboxValue( 
        /* [in] */ DWORD dwFieldID,
        /* [out] */ BOOL *pbChecked,
        /* [string][out] */ LPWSTR *ppszLabel);
    
    HRESULT STDMETHODCALLTYPE GetSubmitButtonValue( 
        /* [in] */ DWORD dwFieldID,
        /* [out] */ DWORD *pdwAdjacentTo);
    
    HRESULT STDMETHODCALLTYPE GetComboBoxValueCount( 
        /* [in] */ DWORD dwFieldID,
        /* [out] */ DWORD *pcItems,
        /* [out] */ DWORD *pdwSelectedItem);
    
    HRESULT STDMETHODCALLTYPE GetComboBoxValueAt( 
        /* [in] */ DWORD dwFieldID,
        DWORD dwItem,
        /* [string][out] */ LPWSTR *ppszItem);
    
    HRESULT STDMETHODCALLTYPE SetStringValue( 
        /* [in] */ DWORD dwFieldID,
        /* [string][in] */ LPCWSTR psz);
    
    HRESULT STDMETHODCALLTYPE SetCheckboxValue( 
        /* [in] */ DWORD dwFieldID,
        /* [in] */ BOOL bChecked);
    
    HRESULT STDMETHODCALLTYPE SetComboBoxSelectedValue( 
        /* [in] */ DWORD dwFieldID,
        /* [in] */ DWORD dwSelectedItem);
    
    HRESULT STDMETHODCALLTYPE CommandLinkClicked( 
        /* [in] */ DWORD dwFieldID);
    
    HRESULT STDMETHODCALLTYPE GetSerialization( 
        /* [out] */ CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE 
                    *pcpgsr,
        /* [out] */ CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION *pcpcs,
        /* [out] */ LPWSTR *ppszOptionalStatusText,
        /* [out] */ CREDENTIAL_PROVIDER_STATUS_ICON 
                    *pcpsiOptionalStatusIcon);
    
    HRESULT STDMETHODCALLTYPE ReportResult( 
        /* [in] */ NTSTATUS ntsStatus,
        /* [in] */ NTSTATUS ntsSubstatus,
        /* [out] */ LPWSTR *ppszOptionalStatusText,
        /* [out] */ CREDENTIAL_PROVIDER_STATUS_ICON *pcpsiOptionalStatusIcon);
};


ICredentialProvider : public IUnknown
{
    HRESULT STDMETHODCALLTYPE SetUsageScenario( 
        /* [in] */ CREDENTIAL_PROVIDER_USAGE_SCENARIO cpus,
        /* [in] */ DWORD dwFlags);
    
    HRESULT STDMETHODCALLTYPE SetSerialization( 
        /* [in] */ const CREDENTIAL_PROVIDER_CREDENTIAL_SERIALIZATION
            *pcpcs);

    HRESULT STDMETHODCALLTYPE Advise( 
        /* [in] */ ICredentialProviderEvents *pcpe,
        /* [in] */ UINT_PTR upAdviseContext);

    HRESULT STDMETHODCALLTYPE UnAdvise( void);

    HRESULT STDMETHODCALLTYPE GetFieldDescriptorCount( 
        /* [out] */ DWORD *pdwCount);

    HRESULT STDMETHODCALLTYPE GetFieldDescriptorAt( 
        /* [in] */ DWORD dwIndex,
        /* [out] */ CREDENTIAL_PROVIDER_FIELD_DESCRIPTOR **ppcpfd);
    
    HRESULT STDMETHODCALLTYPE GetCredentialCount( 
        /* [out] */ DWORD *pdwCount,
        /* [out] */ DWORD *pdwDefault,
        /* [out] */ BOOL *pbAutoLogonWithDefault);
    
    HRESULT STDMETHODCALLTYPE GetCredentialAt( 
        /* [in] */ DWORD dwIndex,
        /* [out] */ ICredentialProviderCredential **ppcpc);
};

Halten Sie sich diesen Aspekt vor Augen, und betrachten Sie Abbildung 5. Dort finden Sie eine Liste der Debugereignisse, die während eines Beispielszenarios auftreten. (Die meisten Ereignisse werden noch ausführlich beschrieben.) Das Szenario zum Generieren der Aufrufsequenz ist ganz einfach. Verwenden Sie eine Windows Vista-Arbeitsstation, die mit einer Domäne verbunden ist. Konfigurieren Sie eine Smartcard mit Ihrem Benutzernamen, Ihrem Kennwort und dem Domänennamen. Führen Sie die Smartcard in ein Lesegerät ein, das an den Computer angeschlossen ist. Starten Sie dann das System neu.
1. [The system boots]

2. [LogonUI.exe process is created]

3. [Credential provider DLLs are loaded]

4. Provider::CreateInstance 

5. [User presses Ctrl+Alt+Del]

6. Provider::SetUsageScenario (CPUS_LOGON)

7. Credential::Initialize 

8. Provider::Advise 

9. Provider::GetCredentialCount 

10. Provider::GetCredentialAt (dwIndex = 0)

11. Provider::GetFieldDescriptorCount 

12. Provider::GetFieldDescriptorAt (dwIndex = 0)

13. Provider::GetFieldDescriptorAt (dwIndex = 1)

14. Provider::GetFieldDescriptorAt (dwIndex = 2)

15. Provider::GetFieldDescriptorAt (dwIndex = 3)

16. Provider::GetFieldDescriptorAt (dwIndex = 4)

17. Credential::GetBitmapValue (dwFieldID = 0; tile image)

18. Credential::GetStringValue (dwFieldID = 1; user name field)

19. Credential::GetFieldState (dwFieldID = 1; user name field)

20. Credential::GetStringValue (dwFieldID = 2; password field)

21. Credential::GetFieldState (dwFieldID = 2; password field)

22. Credential::GetSubmitButtonValue (dwFieldID = 3; submit button)

23. Credential::GetFieldState (dwFieldID = 3; submit button)

24. Credential::GetStringValue (dwFieldID = 4; domain name field)

25. Credential::GetFieldState (dwFieldID = 4; domain name field)

26. Credential::Advise 

27. Credential::GetSerialization 

28. Credential::UnAdvise 

29. Provider::UnAdvise 

30. [The WinLogon process calls LogonUser]

31. Credential::Advise 

32. Credential::ReportResult (ntsStatus = 0)

33. Credential::UnAdvise
Zuerst wird der Anmeldebenutzeroberflächen-Prozess der Konsolensitzung durch die Windows-Anmeldung gestartet. Beim Erstellen listet die Anmeldebenutzeroberfläche alle Anmeldeinformationsanbieter auf, die unter HKLM\Software\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers registriert sind. Alle Anbieter-DLLs werden geladen und erhalten einen Provider::CreateInstance-Aufruf. Beim Hybrid-Anmeldeinformationsanbieter führt dies dazu, dass ein CHybridProvider erstellt wird. (Siehe Schritt 1 bis 4 in Abbildung 5.)
Der Benutzer sieht nun den Anmeldebildschirm. Wenn der Benutzer nun Strg+Alt+Entf drückt, empfängt jeder Anbieter eine Provider::SetUsageScenario CPUS_LOGON-Benachrichtigung. Dies bedeutet für den Anbieter, dass der Benutzer eine interaktive Anmeldung durchführen möchte. Der Hybrid-Anmeldeinformationsanbieter versucht nun, die Anmeldeinformationen von einer eingeführten Smartcard zu lesen. Werden die entsprechenden Informationen gefunden, wird CHybridCredential instanziiert und dem aktuellen CHybridProvider zugeordnet. Anschließend folgt ein Aufruf an Credential::Initialize. (Siehe Schritt 5 bis 7 in Abbildung 5.)
Danach wird Provider::Advise für jeden geladenen Anbieter durch die Anmeldebenutzeroberfläche aufgerufen. Advise soll den Anbietern einen Methode zur Verfügung stellen, mit der die Anmeldebenutzeroberfläche asynchron über eine gewünschte Änderung an den sichtbaren Elementen der Benutzeroberfläche benachrichtigt wird (bislang noch nicht vorhanden). Der integrierte Smartcardanbieter ist ein gutes Beispiel hierfür. Nach der Initialisierung steigt die Anzahl verfügbarer Anmeldeinformationen, wenn eine Karte eingeführt wird. Sobald eine Karte entfernt wird, sinkt diese Anzahl. Wenn dies geschieht, wird die Anmeldebenutzeroberfläche über diese Methode benachrichtigt:
ICredentialProviderEvents : public IUnknown
{
    HRESULT STDMETHODCALLTYPE CredentialsChanged( 
       /* [in] */ UINT_PTR upAdviseContext);
};
Aus Gründen der Einfachheit verarbeitet der Hybrid-Anmeldeinformationsanbieter das Einführen und Entnehmen von Karten nicht dynamisch. Deshalb überwacht er nicht die ICredentialProviderEvents-Schnittstelle, die ihm über Advise übergeben wird.
Der nächste Schnittstellenaufruf durch die Anmeldebenutzeroberfläche geht an Provider::GetCredentialCount (Schritt 9 in Abbildung 5). Für den Fall, dass Hybrid-Anmeldeinformationen erstellt wurden (weil eine Smartcard eingeführt wurde), führt der Hybrid-Anmeldeinformationsanbieter mehrere Aktionen aus. Als Erstes wird der GetCredentialCount-Ausgabeparameter „*pdwCount“ auf 1 gesetzt. Dies entspricht der Anzahl von Anmeldeinformationselementen, die der Anbieter auflisten möchte. (Der Hybrid-Anmeldeinformationsanbieter kann nur ein Element verarbeiten.) Wenn Sie zuerst Windows Vista installieren und eine Verbindung zu einer Domäne herstellen, können Sie den pdwCount-Wert ermitteln, den der Microsoft-Kennwort-Anmeldeinformationsanbieter auf Grundlage der gerenderten Elemente an die Anmeldebenutzeroberfläche zurückgibt.
Anschließend setzt der Hybrid den GetCredentialCount-Ausgabeparameter „*pdwDefault“ auf Null. Dieser Wert bezeichnet einen nullbasierten Index für ein Array von Anmeldeinformationen, das von jedem Anbieter zu führen ist. Die eigentliche Implementierung, wie ein Anbieter die Anmeldeinformationen überwacht, obliegt dem Implementierer selbst, sofern die Indexe über die Gültigkeitsdauer einer gegebenen Gruppe von Anmeldeinformationsobjekten hinweg geführt werden.
Es ist durchaus möglich, dass mehrere Anbieter Standardanmeldeinformationen auflisten. Im aktuellen Szenario können Sie beispielsweise erwarten, dass der integrierte Kennwort-Anmeldeinformationsanbieter eigene Standardanmeldeinformationen auflistet. Wie fordert die Anmeldebenutzeroberfläche den Benutzer auf, aus mehreren standardmäßigen und nicht standardmäßigen Anmeldeinformationen auszuwählen, ohne Verwirrung zu verursachen? Im Allgemeinen wird dem Benutzer je ein Element für jeden Aspekt der Anmeldeinformationen gezeigt, wobei sich der Fokus auf dem Element mit den standardmäßigen Anmeldeinformationen befindet. Sind mehrere Standardwerte vorhanden, wird der richtige Standardwert durch eine Reihe von Rangfolgeregeln ausgewählt, während die einzelnen Standardanmeldeinformationen aufgelistet werden. Wenn für bestimmte Anmeldeinformationen bereits ein Standardwert ohne automatische Anmeldung vorliegt und diese Anmeldeinformationen für die automatische Anmeldung herangezogen werden, werden diese Anmeldeinformationen als Standardwert festgelegt. Falls diese Anmeldeinformationen vom LLO-Anbieter (last-logged-on, zuletzt angemeldet) stammen und noch kein Standardwert mit automatischer Anmeldung vorhanden ist, werden diese Anmeldeinformationen zum Standardwert. Wenn noch kein Standardwert existiert, werden diese Anmeldeinformationen zum Standardwert. Im Licht dieser Aspekte erübrigt sich eine weitere Diskussion – dank der vom Hybrid-Anmeldeinformationsanbieter gebotenen Semantik für die automatische Anmeldung. Solange die aufgelisteten Hybrid-Anmeldeinformationen gültige Anmeldeinformationen enthalten, werden dem Benutzer keine Elemente angezeigt. Hierzu eine kurze Erklärung.
Im Zusammenhang mit den Rangfolgeregeln wurde der LLO-Anbieter erwähnt. Dabei ist jedoch zu beachten, dass die Bedeutung des LLO davon abhängig ist, ob der Benutzer sich gerade angemeldet oder ob die Anmeldung bereits erfolgt ist, beispielsweise bei einer Desktopsperre oder einer Kennwortänderung. Bei der Anmeldung gilt der letzte Anbieter, über den die letzte Anmeldung bei der Konsole vorgenommen wurde, als LLO-Anbieter. Nach der Anmeldung ist der LLO-Anbieter der Anbieter, über den die Anmeldung für die aktuelle Sitzung lief. Wenn Sie sich stets mit Ihrer Smartcard anmelden, erhält das Standardelement des Smartcard-Anmeldeinformationsanbieters bei Neustarts in jedem Fall den Vorrang. Falls Sie Ihre Smartcard verlieren und sich dann mit Ihrem Kennwort anmelden, erhält das Element des Kennwort-Anmeldeinformationsanbieters Vorrang für die laufende Sitzung, sobald Sie die Sperre aufheben.
Der Hybrid-Anmeldeinformationsanbieter setzt den Ausgabeparameter „*pbAutoLogonWithDefault“ stets auf TRUE. Damit wird die Anmeldebenutzeroberfläche informiert, dass die Standardanmeldeinformationen dieses Anbieters sofort nach Anmeldeinformationen abgefragt werden sollen und dass es nicht nötig ist, zuerst eine entsprechende Aufforderung für den Benutzer auszugeben. Beachten Sie, dass der integrierte Kennwort-Anmeldeinformationsanbieter dieselbe Funktion über die optionalen Kennwortinformationen der automatischen Anmeldung bietet, die in der Registrierung gespeichert werden können. Wenn Windows Vista erkennt, dass auch nur ein Benutzer am Computer noch kein Kennwort besitzt, ist dies außerdem das Standardverhalten. Wenn mehrere Anmeldeinformationsanbieter den Parameter „*pbAutoLogonWithDefault“ auf TRUE setzen, ist das Verhalten der Anmeldebenutzeroberfläche nicht näher definiert.
Nach GetCredentialCount wird Provider::GetCredentialAt durch die Anmeldebenutzeroberfläche aufgerufen. Für den Hybrid-Anmeldeinformationsanbieter wird diese Routine höchstens einmal aufgerufen, entsprechend der maximalen Anzahl von Anmeldeinformationen für diesen Anbieter. Der Anbieter gibt den ICredentialProviderCredential-Zeiger für die Anmeldeinformationsinstanz zurück, die dem angeforderten Index entspricht.
Anschließend wird Provider::GetFieldDescriptorCount durch die Anmeldebenutzeroberfläche aufgerufen. Hiermit gibt der Anbieter die maximale Anzahl von Elementen der Benutzeroberfläche zurück, die in seinen Anmeldeinformationen vorliegen. Der Beispiel-Kennwort-Anmeldeinformationsanbieter enthält beispielsweise fünf Felder: eine Bitmap, ein Eingabefeld für den Benutzernamen, ein Eingabefeld für das Kennwort, eine Schaltfläche zum Senden sowie ein Eingabefeld für den Domänennamen. Beachten Sie, dass dieselben Elemente auch im Hybrid-Anmeldeinformationsanbieter festgehalten sind, obwohl sie nie gerendert werden. Damit ist Schritt 11 in Abbildung 5 abgeschlossen.
Anschließend wird Provider::GetFieldDescriptorAt nacheinander für jedes Element der Benutzeroberfläche einmal durch die Anmeldebenutzeroberfläche aufgerufen, um so den Typ dieser Elemente zu ermitteln. Als Reaktion auf den Aufruf für den Index der Bitmap wird im Beispiel das CREDENTIAL_PROVIDER_FIELD_TYPE CPFT_TILE_IMAGE zurückgegeben. Ein Feature, das nicht im Hybrid-Anmeldeinformationsanbieter verwendet wird, betrifft den Unterschied zwischen nicht schreibgeschützten und schreibgeschützten Textfeldern. Wenn der Hybrid-Anmeldeinformationsanbieter so geändert werden würde, dass der Benutzer aufgefordert wird, eine PIN für die Smartcard einzugeben, würde dies mit CPFT_PASSWORD_TEXT realisiert werden. Der von der Smartcard gelesene Benutzername kann angezeigt werden und bietet damit einen Kontext für diese Eingabeaufforderung. Technisch gesehen sollte der Benutzername als schreibgeschützt gelten, weil dieser Name mit dem Kennwort verbunden ist, das ebenfalls auf der Karte gespeichert ist. Daher dürfte der Feldtyp CPFT_LARGE_TEXT (im Gegensatz zu CPFT_EDIT_TEXT) Verwendung finden. (Eine vollständige Liste der Optionen finden Sie unter credentialprovider.h.)
Nach der Auflistung der Felddeskriptoren führt die Anmeldebenutzeroberfläche eine Reihe von Aufrufen an den Anmeldeinformationsanbieter durch, abhängig vom Typ der einzelnen Anmeldeinformationsfelder. Beim Feldtyp CPFT_TILE_IMAGE folgt die Anmeldebenutzeroberfläche mit einem Aufruf an Credential::GetBitmapValue. Für Textwerte wie CPFT_LARGE_TEXT (für das Bearbeitungsfeld des Benutzernamens) folgen weitere Aufrufe an Credential::GetStringValue und Credential::GetFieldState.
Weil alle erforderlichen Anmeldeinformationen (Benutzername, Kennwort und Domänenname) für den Hybrid-Anmeldeinformationsanbieter bereits von der Smartcard gelesen wurden, sind die Zeichenfolgen für die einzelnen Textfelder bereits verfügbar und werden über den Ausgabeparameter „ppwz“ von GetStringValue zurückgegeben. Andere Anbieter geben zu diesem Zeitpunkt wahrscheinlich in Antwort auf GetStringValue eine NULL-Zeichenfolge zurück, weil der Benutzer noch keine Gelegenheit hatte, etwas einzugeben. Behalten Sie diesen u. U. verwirrenden Punkt im Gedächtnis: Der Name des Textfelds wird über GetFieldDescriptorAt abgerufen, der aktuelle Textwert im Feld dagegen über GetStringValue. (Der Name oder die Beschriftung des Felds werden als Hinweistext in einem leeren Bearbeitungssteuerelement angezeigt.)
Sobald die verschiedenen Elemente der Benutzeroberfläche vollständig beschrieben wurden, ruft die Anmeldebenutzeroberfläche Credential::Advise auf. (Siehe Schritt 26 in Abbildung 5.) Dies dient einem ähnlichen Zweck wie die zuvor aufgerufene Schnittstelle „Provider::Advise“. Alle Anmeldeinformationen können die Anmeldebenutzeroberfläche asynchron über relevante Änderungen am Zustand der Elemente der Benutzeroberfläche benachrichtigen. Der Beispiel-Kennwort-Anmeldeinformationsanbieter nutzt diese Methode beispielsweise immer dann, wenn die Auswahl eines der Anmeldeinformationselemente aufgehoben wird. In diesem Fall wird das Kennwortfeld mittels ICredentialProviderCredentialEvents SetFieldString (siehe Abbildung 6) durch das Anmeldeinformationsobjekt geleert. Das Gleiche würde geschehen, wenn Sie nur einen Teil Ihres Kennworts im Anmeldebildschirm von Windows XP eingeben und dann eine Pause einlegen. Schließlich tritt ein Timeout für das Anmeldedialogfeld ein, und der Text wird gelöscht.
ICredentialProviderCredentialEvents : public IUnknown
{
    HRESULT STDMETHODCALLTYPE SetFieldState( 
        /* [in] */ ICredentialProviderCredential *pcpc,
        /* [in] */ DWORD dwFieldID,
        /* [in] */ CREDENTIAL_PROVIDER_FIELD_STATE cpfs);
    
    HRESULT STDMETHODCALLTYPE SetFieldInteractiveState( 
        /* [in] */ ICredentialProviderCredential *pcpc,
        /* [in] */ DWORD dwFieldID,
        /* [in] */ CREDENTIAL_PROVIDER_FIELD_INTERACTIVE_STATE cpfis);
    
    HRESULT STDMETHODCALLTYPE SetFieldString( 
        /* [in] */ ICredentialProviderCredential *pcpc,
        /* [in] */ DWORD dwFieldID,
        /* [unique][string][in] */ LPCWSTR psz);
    
    HRESULT STDMETHODCALLTYPE SetFieldCheckbox( 
        /* [in] */ ICredentialProviderCredential *pcpc,
        /* [in] */ DWORD dwFieldID,
        /* [in] */ BOOL bChecked,
        /* [in] */ LPCWSTR pszLabel);
    
    HRESULT STDMETHODCALLTYPE SetFieldBitmap( 
        /* [in] */ ICredentialProviderCredential *pcpc,
        /* [in] */ DWORD dwFieldID,
        /* [in] */ HBITMAP hbmp);
    
    HRESULT STDMETHODCALLTYPE SetFieldComboBoxSelectedItem( 
        /* [in] */ ICredentialProviderCredential *pcpc,
        /* [in] */ DWORD dwFieldID,
        /* [in] */ DWORD dwSelectedItem);
    
    HRESULT STDMETHODCALLTYPE DeleteFieldComboBoxItem( 
        /* [in] */ ICredentialProviderCredential *pcpc,
        /* [in] */ DWORD dwFieldID,
        /* [in] */ DWORD dwItem);
    
    HRESULT STDMETHODCALLTYPE AppendFieldComboBoxItem( 
        /* [in] */ ICredentialProviderCredential *pcpc,
        /* [in] */ DWORD dwFieldID,
        /* [string][in] */ LPCWSTR pszItem);
    
    HRESULT STDMETHODCALLTYPE SetFieldSubmitButton( 
        /* [in] */ ICredentialProviderCredential *pcpc,
        /* [in] */ DWORD dwFieldID,
        /* [in] */ DWORD dwAdjacentTo);
    
    HRESULT STDMETHODCALLTYPE OnCreatingWindow( 
        /* [out] */ HWND *phwndOwner);
};

Im Hinblick auf den Abschluss einer Benutzerauthentifizierung ist der nächste Aufruf am interessantesten. Nachdem der GetCredentialCount-Parameter „*pbAutoLogonWithDefault“ auf TRUE gesetzt wurde, „weiß“ die Anmeldebenutzeroberfläche, dass die Standardanmeldeinformationen bereits genügend Daten für die Authentifizierung des Benutzers enthalten müssen (obwohl noch keine Elemente der Benutzeroberfläche gerendert und somit auch noch keine Eingaben des Benutzers erfasst wurden). In diesem Fall wird die Routine „Credential::GetSerialization“ aufgerufen, um den Benutzernamen, das Kennwort und den optionalen Domänennamen abzurufen. Der Anmeldeinformationsanbieter erstellt den Rückgabewert für diese Routine, indem die drei Elemente in das von Kerberos erwartete Format gemarshallt werden. Sobald die serialisierten Anmeldeinformationen vorbereitet sind, informiert der Anmeldeinformationsanbieter die Anmeldebenutzeroberfläche mittels des Ausgabeparameters CREDENTIAL_PROVIDER_GET_SERIALIZATION_RESPONSE darüber, dass vollständige Anmeldeinformationen zurückgegeben werden. Die entsprechende Unterscheidung wird durch den Wert CPGSR_RETURN_CREDENTIAL_FINISHED getroffen. Beachten Sie wiederum die Angaben unter credentialprovider.h sowie die Implementierung von GetSerialization im Beispielcode. Damit ist Schritt 27 in Abbildung 5 abgeschlossen.
Nach GetSerialization übergibt die Anmeldebenutzeroberfläche die gemarshallten Anmeldeinformationen an die Windows-Anmeldung, die wiederum die Benutzeranmeldung aufruft und die Informationen an die lokale Sicherheitsinstanz (LSA) übergibt. Zuvor werden Credential::UnAdvise und Provider::UnAdvise durch die Anmeldebenutzeroberfläche aufgerufen, sodass beide Entitäten darüber informiert werden, dass Benachrichtigungen bei der jeweiligen Ereignisschnittstelle nicht angenommen werden. Änderungen an der Benutzeroberfläche würden keinen Sinn haben, wenn gerade ein Anmeldevorgang läuft. (Im Idealfall soll der Benutzer als Nächstes den Desktop sehen.)Weitere Ressourcen
Sobald die Windows-Anmeldung das Ergebnis der Benutzeranmeldung erhält, wird dieses Ergebnis an die Anmeldebenutzeroberfläche zurückgegeben. Anschließend wird die Anmeldeinformationsinstanz informiert (die Instanz, die noch den Fokus von GetSerialization besitzt). Bevor jedoch die Anmeldeinformationen den von der Benutzeranmeldung zurückgegebenen Statuscode empfangen, wird wiederum eine Rückrufschnittstelle angeboten, über die Änderungen an den Elementen der Benutzeroberfläche vorgenommen werden können. (Siehe Schritt 31 in Abbildung 5.)
Das Ergebnis des Authentifizierungsversuchs wird über die Routine „Credential::ReportResult“ an die Anmeldeinformationen zurückgegeben. Warum ist das Ergebnis des Authentifizierungsversuchs für einen Anbieter (oder seine Anmeldeinformationsobjekte) überhaupt relevant, und warum sollte der Anbieter an diesem Punkt anfangen, Änderungen an der Benutzeroberfläche vorzunehmen?
Viele der interessantesten ReportResult-Szenarios stammen von fehlgeschlagenen Authentifizierungen. Eines der Standardbeispiele ist der Ablauf des Benutzerkennworts. Wenn das Ablaufdatum für das Kennwort des Benutzers näher rückt, wird dies durch den Authentifizierungsunterstatus, der über den ReportResult-Parameter „ntsSubstatus“ zurückgegeben wird, angezeigt. Daraufhin erinnert der integrierte Kennwort-Anmeldeinformationsanbieter den Benutzer daran, das Kennwort zu ändern. Diese Eingabeaufforderung und das Dialogfeld zur Kennwortänderung erfordern verschiedene Benutzeroberflächenelemente. Folglich löst der Kennwort-Anmeldeinformationsanbieter die erforderlichen Änderungen an den Aufforderungsfeldern über den ICredentialProviderCredentialEvents-Schnittstellenzeiger aus.
Ein Anmeldeinformationsanbieter kann in Reaktion auf eine erfolgreiche Authentifizierung interessante Aktionen ausführen. Beim integrierten Smartcard-Anmeldeinformationsanbieter ist diese Erfolgsbenachrichtigung beispielsweise ein Auslöser für ein Verfahren, mit dem überwacht wird, ob die für die Authentifizierung verwendete Karte entfernt wurde. Hiermit soll die optionale Richtlinie zum Sperren der Sitzung beim Entfernen der Karte durchgesetzt werden. Sobald die Sequenz der Anmeldeinformationsverarbeitung abgeschlossen ist, benachrichtigt die Anmeldebenutzeroberfläche den Anmeldeinformationsanbieter mittels Credential::UnAdvise, dass der Verweis auf den ICredentialProviderCredentialEvents-Schnittstellenzeiger heruntergesetzt werden soll.

Der Hybrid-Anmeldeinformationsanbieter
Sie haben nun gesehen, wie die neue Anmeldeinformationsanbieter-Architektur aufgebaut ist und wie sie verwendet wird. Im Folgenden soll der Entwurf des Beispiel-Hybrid-Anmeldeinformationsanbieters ausführlicher betrachtet werden. Erinnern Sie sich an das High-Level-Layout der interaktiven Windows Vista-Anmeldearchitektur, die in Abbildung 2 dargestellt ist. Abbildung 7 ergänzt dieses Diagramm um den Windows-Smartcard-API-Stapel und verlagert den Schwerpunkt auf den neuen Anmeldeinformationsanbieter.
Der wichtigste Aspekt in Abbildung 7 besteht darin, dass der Hybrid-Anmeldeinformationsanbieter mit der Windows-Smartcard-API sowohl eine direkte als auch eine indirekte Schnittstelle bildet. Die direkte Schnittstelle erfolgt über öffentliche Routinen (z. B. SCardEstablishContext und SCardListReaders), die die Erkennung einer Smartcard ermöglichen. Die indirekte Schnittstelle läuft über die Kartenmodul-API, mit der der Anmeldeinformationsanbieter eine Datei mit Benutzeranmeldeinformationen problemlos von der Karte lesen kann, ohne kartenspezifische Low-Level-Befehle verwenden zu müssen. Im Beispiel wird nahezu die gesamte smartcardbezogene Logik durch die Hilfsprogrammbibliothek „ScHelp.lib“ abstrahiert. (Dieser Punkt wird später im Abschnitt über die Implementierung näher besprochen.) Das Verhalten des Hybrid-Anmeldeinformationsanbieters bei der automatischen Anmeldung bietet interessante Einblicke in die Möglichkeiten und Feinheiten der Anmeldeinformationsanbieter-Architektur als Ganzes.
Abbildung 7 Hybrid-Anmeldeinformationsanbieter 
Was bedeutet eigentlich „automatische Anmeldung“? Dies bedeutet, dass der Benutzer keinerlei Aufforderung erhält, wenn bereits Hybrid-Anmeldeinformationen verfügbar sind (wie bereits dargelegt). In diesem Fall wird stattdessen automatisch ein Authentifizierungsversuch gestartet, sobald der Benutzer die Tastenkombination Strg+Alt+Entf drückt.
Das implementierte Verhalten für die automatische Anmeldung könnte einige Benutzer verwirren. Dieses Verfahren ist beispielsweise nicht identisch mit dem integrierten Kennwortanbieter, der Elemente standardmäßig immer rendert, selbst dann, wenn keine Verbindung zu einer Domäne besteht und der Benutzer nicht zur Eingabe eines Kennworts aufgefordert wird.
Der Hybrid-Anmeldeinformationsanbieter ließe sich problemlos so überarbeiten, dass der Benutzer stets aufgefordert wird, auf ein Element zu klicken. Beachten Sie, dass der Parameter „*pbAutoLogonWithDefault“ bei der aktuellen Implementierung von GetCredentialCount auf TRUE gesetzt ist. Setzen Sie diesen Parameter stattdessen auf FALSE. Nun erhält der Anbieter in jedem Fall die Gelegenheit, mindestens ein Element darzustellen (es sei denn, ein anderer Anbieter setzt eine automatische Anmeldung ohne jegliche Elemente durch).
Wenn der Benutzer auf das Element klickt, ruft die Anmeldebenutzeroberfläche die Methode „ICredentialProviderCredential::SetSelected“ des Anbieters auf. Daraufhin wird die Anmeldeinformationsklasse auf *pbAutoLogon = TRUE gesetzt, wodurch ICredentialProviderCredential::GetSerialization durch die Anmeldebenutzeroberfläche aufgerufen und ein Authentifizierungsversuch gestartet wird, ohne zuerst etwaige Änderungen an den Elementen der Benutzeroberfläche zu rendern. Anders gesagt: Wenn die Authentifizierung erfolgreich ist, sieht der Benutzer als Nächstes den Desktop.

Die Hybrid-Implementierung
Zur Umwandlung des Kennwort-Anmeldeinformationsanbieters in die Hybrid-Implementierung waren nur minimale Änderungen notwendig. Vergleichen Sie die Datei „CSampleProvider.cpp“ aus dem SDK mithilfe eines grafischen Vergleichstools (z. B. windiff.exe) mit meiner Datei „CHybridProvider.cpp“, und vergleichen Sie anschließend die Dateien „CSampleCredential.cpp“ und „CHybridProvider.cpp“. In den meisten geänderten Codezeilen wurde lediglich „Sample“ (Beispiel) durch „Hybrid“ ersetzt.
Die wichtigste Änderung von CHybridProvider.cpp liegt in der Verarbeitung von SetUsageScenario. In Reaktion auf diesen Aufruf versucht der Anbieter, Anmeldeinformationen von einer Smartcard zu lesen. Hierzu wird die Routine „ScHelpInit“ in der ScHelp-Bibliothek herangezogen, mit der der größte Teil der Smartcardlogik abstrahiert wird. ScHelpInit stellt eine Verbindung zum Smartcardsubsystem her, sucht nach der ersten eingeführten Karte, analysiert die Anmeldeinformationen (sofern vorhanden) und gibt die darin enthaltenen Zeichenfolgen zurück.
Die wichtigste Änderung in CHybridCredential.cpp besteht darin, dass die optionale Domänennamen-Zeichenfolge als Teil der Anmeldeinformationsdatei auf der eingeführten Smartcard gelesen wird. Wenn bei GetSerialization ein Domänenname von der Karte gelesen wird, wird er in den serialisierten Authentifizierungsdaten verwendet, um an Kerberos übergeben zu werden. Ansonsten wird das Ergebnis aus dem Aufruf der öffentlichen Routine „GetComputerName“ genutzt.
Die Änderungen, mit denen das PropCert-Beispiel in die ScHelp-Bibliothek konvertiert wurde, waren deutlich umfangreicher. Einige Aspekte dieser Hilfsprogrammbibliothek sind es wert, näher betrachtet zu werden.
Beispielsweise wird die Hauptthreadroutine „PropCert“ nunmehr synchron aufgerufen. Es wird kein getrennter Thread verwendet. Kartenbezogene Vorgänge sollten jedoch asynchron durchgeführt werden, und diese Änderung ließe sich verhältnismäßig einfach vornehmen. Diese Änderung wäre von großer Bedeutung, wenn der Hybrid-Anmeldeinformationsanbieter so erweitert würde, dass der Benutzer unter mehreren Anmeldeinformationselementen auswählen könnte. In diesem Fall sollte der Anbieter sofort ein Element auflisten, während die Smartcarddaten im Hintergrund gelesen werden, weil E/A-Vorgänge bei einigen älteren Karten relativ langsam ablaufen. Um die Implementierung dieser Änderung abzuschließen, beachten Sie, dass eine Benachrichtigungsmethode eingerichtet werden muss, damit der Smartcardthread den Anbieter über Änderungen an der Verfügbarkeit der Anmeldeinformationen informieren kann. Der Anbieter könnte dann die Anmeldebenutzeroberfläche entsprechend mittels CredentialsChanged benachrichtigen.
Die verbleibende Logik in der Datei „ScHelp.lib“ umfasst die Routinen „_ReadCreds“, „_Connect“, „_UnpackCred“ (siehe Abbildung 8) und „ScHelpPackCred“. Die letzten beiden Routinen deserialisieren und serialisieren die Kennwort-Anmeldeinformationsdatei, die auf einer Smartcard gespeichert ist. Die ersten beiden Routinen implementieren die bereits kurz beschriebene Logik: Auflisten von Smartcard-Lesern und Karten, Aktivieren einer Lesesperre für die erste aufgelistete Karte, Binden an das Kartenmodul für diese Karte und Lesen der Anmeldeinformationsdatei (sofern vorhanden) von der Karte.
// Break down the credential byte array
DWORD WINAPI UnpackCred(
    __in_bcount(cbCred)     PBYTE pbCred,
    __in                    DWORD cbCred,
    __out                   LPWSTR *ppwszUserName,
    __out                   LPWSTR *ppwszPassword,
    __out                   LPWSTR *ppwszDomainName)
{
    DWORD status = ERROR_SUCCESS;
    DWORD cbUserName = 0;
    DWORD cbPassword = 0;
    DWORD cbDomainName = 0;
    DWORD cbCurrent = 0;

    *ppwszUserName = NULL;
    *ppwszPassword = NULL;
    *ppwszDomainName = NULL;

    try
    {
        // Read the user name
        cbUserName = (DWORD) sizeof(WCHAR) * (1 + wcslen(
            (LPWSTR) (pbCred + cbCurrent)));
        if (cbUserName > cbCred - cbCurrent)
        {
            status = ERROR_INVALID_PARAMETER;
            leave;
        }

        if (NULL == (*ppwszUserName = (LPWSTR) Alloc(cbUserName)))
        {
            status = ERROR_NOT_ENOUGH_MEMORY;
            leave;
        }

        if (FAILED(StringCbCopy(
            *ppwszUserName, cbUserName, (LPWSTR) (pbCred + cbCurrent))))
        {
            status = ERROR_INSUFFICIENT_BUFFER;
            leave;
        }

        cbCurrent += cbUserName;

        // Read the password
        cbPassword = (DWORD) sizeof(WCHAR) * (1 + wcslen(
            (LPWSTR) (pbCred + cbCurrent)));
        if (cbPassword > cbCred - cbCurrent)
        {
            status = ERROR_INVALID_PARAMETER;
            leave;
        }

        if (NULL == (*ppwszPassword = (LPWSTR) Alloc(cbPassword)))
        {
            status = ERROR_NOT_ENOUGH_MEMORY;
            leave;
        }

        if (FAILED(StringCbCopy(
            *ppwszPassword, cbPassword, (LPWSTR) (pbCred + cbCurrent))))
        {
            status = ERROR_INSUFFICIENT_BUFFER;
            leave;
        }

        cbCurrent += cbPassword;

        // Read the domain name (if any)
        cbDomainName = (DWORD) sizeof(WCHAR) * (1 + wcslen(
            (LPWSTR) (pbCred + cbCurrent)));
        if (sizeof(WCHAR) == cbDomainName)leave;
        else if (cbDomainName > cbCred - cbCurrent)
        {
            status = ERROR_INVALID_PARAMETER;
            leave;
        }

        if (NULL == (*ppwszDomainName = (LPWSTR) Alloc(cbDomainName)))
        {
            status = ERROR_NOT_ENOUGH_MEMORY;
            leave;
        }

        if (FAILED(StringCbCopy(*ppwszDomainName, cbDomainName, 
            (LPWSTR) (pbCred + cbCurrent))))
        {
            status = ERROR_INSUFFICIENT_BUFFER;
            leave;
        }
    }
    finally {}

    return status;
}

Das Ende von GINA
Warum gab es für GINA keine Zukunft mehr? Diese Frage ist komplexer, als es den Anschein haben mag. Microsoft kann auf eine ansehnliche Erfolgsgeschichte bei der Unterstützung von Entwicklern bei Drittanbietern zurückblicken, und die Entscheidung, den Support für eine öffentliche Schnittstelle einzustellen, wird nicht leichtfertig gefällt. Dennoch gab es einige zwingende Argumente, von GINA Abschied zu nehmen, und letztlich stellte Microsoft fest, dass dies die richtige Entscheidung ist.
Beispielsweise ist die mehrstufige Authentifizierung heutzutage viel stärker gefragt als in den Anfangszeiten von Windows NT®. Smartcards, Biometrie und Lösungen mit Einmalkennwörtern werden in immer mehr Unternehmen eingeführt. Jede Entwicklung bei der Authentifizierungstechnologie stellt eine größere Belastung für die Abstraktionsschicht zwischen dem Hauptmodul für Windows-Anmeldeinformationen und der Benutzeroberfläche dar, durch die der Benutzer auf bestimmte Weise Eingabeaufforderungen erhält. (Um beispielsweise den Benutzer aufzufordern, ein Kennwort einzugeben, sind andere bildliche Hinweise und Bildschirmelemente erforderlich, als wenn der Benutzer einen Finger auf einen Fingerabdruckleser legen soll.) Gleichzeitig muss Microsoft für einen möglichst konsistenten Aufbau sorgen, damit die Benutzer nicht verwirrt werden.
Außerdem wurde der Prozess der Windows-Anmeldung (winlogon.exe) in Windows Vista vollständig überarbeitet. Eine wichtige Anforderung bei diesem Unterfangen bestand darin, Plug-Ins so weit wie möglich aus dem Prozessbereich der Windows-Anmeldung herauszunehmen. Diese Anforderung ergab sich aus Gründen der Zuverlässigkeit. Wenn beispielsweise eine unzulänglich verfasste GINA in die Instanz von winlogon.exe geladen wird, die in Sitzung Null auf einem Server ausgeführt wird, könnte ein Softwarefehler diesen kritischen Prozess abbrechen und damit auch den Computer selbst zum Absturz bringen. Selbst wenn die GINA so hätte angepasst werden können, dass sie außerhalb des Prozesses ausgeführt werden kann, bestünde immer noch das Problem, dass GINA nicht für konsistente, kontrollierte Verfahren in komplexen, interaktiven Szenarios vorgesehen war, in denen Anmeldeinformationen erfasst werden.


Verbesserungsmöglichkeiten
Meiner Meinung nach erfüllt mein Entwurf für den Hybrid-Anmeldeinformationsanbieter zusammen mit dem beträchtlichen Grad der Wiederverwendung von Code, den ich bei der Implementierung erreichen konnte, durchaus die Voraussetzungen, die ich weiter oben beschrieben habe. Dennoch bewirken einige Aspekte der Implementierung, dass der Anbieter in seinem aktuellen Zustand für die Bereitstellung nicht bereit ist. Eine solche Unzulänglichkeit hatte ich bereits angesprochen: Die Daten sollten asynchron von der Smartcard gelesen werden. Im Folgenden sollen geordnet nach abnehmender Bedeutsamkeit die weiteren Punkte erläutert werden, die aus meiner Sicht verbessert werden müssen.
Der wichtigste Punkt: Die Benutzeranmeldeinformationen sind nicht sicher auf der Smartcard gespeichert. Im Idealfall sollte die Datei auf der Karte, in der sich das Benutzerkennwort befindet, nur dann gelesen werden können, wenn die richtige PIN eingegeben wurde. Eine Einschränkung in der aktuellen Kartenmodulschnittstelle erschwert jedoch die Umsetzung dieses Punkts. Der Satz vordefinierter Bedingungen für den Zugriff auf die Kartendatei umfasst keine Option, die dafür sorgt, dass die Karte nur durch den autorisierten Benutzer gelesen werden kann. (Dies dürfte übrigens ein Paradebeispiel ausgleichender Gerechtigkeit sein: Ich war an den Entscheidungen beim Kartenmodulentwurf beteiligt, die zu dieser Einschränkung führten, und nun bereiten sie mir Schwierigkeiten!) Es bleibt zu hoffen, dass das Produktteam die Kartenmodulschnittstelle in einer späteren Version entsprechend erweitert.
In der Zwischenzeit sollte die Smartcard-Kennwortdatei so verschlüsselt werden, dass bei einem Diebstahl der Karte in jedem Fall die PIN erforderlich ist, um das Kennwort zu entschlüsseln. Dies lässt sich problemlos über Crypto API sowie über das neue Windows Vista CNG (Crypto API der nächsten Generation) erreichen. Ein RSA-Schlüsselpaar, das auf der Karte gespeichert ist, würde schon genügen. Statt jedoch die Kennwortdatei direkt mit dem öffentlichen RSA-Schlüssel zu verschlüsseln, sollten Sie gemäß den Best Practices der Kryptografie einen symmetrischen Schlüssel und Algorithmus verwenden, beispielsweise AES (Advanced Encryption Standard). Der RSA-Schlüssel würde stattdessen den symmetrischen Schlüssel verschlüsseln.
Als runden Abschluss für den Entwurf einer verschlüsselten Kennwortdatei könnten Sie das vorhandene Format der Datei so ändern, dass der zugeordnete verschlüsselte Schlüssel enthalten ist. Beachten Sie hierbei jedoch die Schwierigkeiten bei der Versionskontrolle, die sich aus einem Verschlüsselungsalgorithmus und der Schlüsselgröße ergeben. Nehmen Sie einmal an, der gewählte Algorithmus würde eines Tages durch Hacker geknackt werden. Der bislang erörterte Entwurf umfasst außerdem noch keine kryptografisch sichere Datenintegritätsprüfung. Dies scheint nur ein nebensächlicher Punkt zu sein, weil ein Angreifer theoretisch die PIN kennen muss, um Daten auf der Karte ändern zu können. Meiner Meinung nach ist ein solches Feature jedoch ein notwendiger Aspekt der Tiefenverteidigung.
Die zweite Einschränkung der aktuellen Implementierung liegt darin, dass nur ein einziger Satz von Anmeldeinformationen pro Karte unterstützt wird. Betrachten Sie die Routine „ScHelp.cpp!_UnpackCred“ aus dem Beispielcode in Abbildung 8. Mit ihr wird eine einfache Deserialisierung der Kennwortdatei durchgeführt, von der angenommen wird, dass sie von der Karte gelesen wurde. Die Gesamtlogik zur Analyse der Anmeldeinformationen verarbeitet nur einen einzigen Satz von Anmeldeinformationen pro Karte. Einige Benutzer brauchen jedoch mehrere Domänenanmeldeinformationen, um ihre Arbeit erledigen zu können. Würden Sie den Anbieter entsprechend erweitern, oder würden Sie den Benutzern mehrere Karten zur Verfügung stellen? Bei der ersten Möglichkeit wird die Implementierung komplexer, bei der zweiten die Bereitstellungsverwaltung.
Als kleinen Exkurs möchte ich die sicheren Pufferanalyseverfahren in _UnpackCred vorstellen. (Beachten Sie wiederum Abbildung 8.) Angenommen, ein Angreifer erstellt eine schädliche Smartcard und führt diese in eine Arbeitsstation in Ihrem Netzwerk ein. Auf Anwendungsebene besteht die primäre Gegenmaßnahme bei dieser Bedrohung darin, dass keine Annahmen über die Gültigkeit der Daten aufgestellt werden, die von der Karte gelesen werden. Gehen Sie nicht davon aus, dass die eingebetteten Zeichenanzahlen korrekt oder die Zeichenfolgen wohlgeformt sind. Stellen Sie einfach sicher, dass die Elementanzahl zu Beginn jeder Zeichenfolge die Länge des nicht analysierten Teils der Anmeldeinformationen nicht überschreitet und dass die tatsächliche Länge jeder Zeichenfolge nicht die Größe des zugeordneten Puffers übertrifft.
Die letzte Einschränkung beim aktuellen Hybrid-Anmeldeinformationsanbieter, die an dieser Stelle diskutiert werden soll, liegt darin, dass nur ein Smartcard-Leser pro Computer unterstützt wird. Wird beispielsweise ein System hochgefahren, an das zwei Smartcard-Leser angeschlossen sind, in die jeweils eine Smartcard mit anderen Anmeldeinformationen eingelegt ist, ergibt sich die Rangfolge der Karten aus der Reihenfolge, in der die Lesegeräte durch das Smartcardsubsystem aufgelistet werden. Zur Behebung dieses Problems muss die Semantik der in ScHelp.h definierten Struktur SCHELP_CONTEXT geändert werden:
typedef struct _SCHELP_CONTEXT
{
    LPWSTR wszUserName;
    LPWSTR wszPassword;
    LPWSTR wszDomainName;
} SCHELP_CONTEXT, *PSCHELP_CONTEXT;
Die Struktur SCHELP_CONTEXT definiert den Datenaustausch zwischen dem Anmeldeinformationsanbieter-Code und dem ScHelp-Code. Die Struktur unterstützt ganz offensichtlich nur einen einzigen Satz von Anmeldeinformationen. Ein einfaches Array oder eine einzeln verknüpfte Liste würde schon ausreichen, um diesen Punkt zu optimieren. Wenn Sie dieses Feature umsetzen möchten, denken Sie daran, auch die Verarbeitung des Mitglieds „_rgpCredentials“ von CSampleProvider zu ändern, weil dieses Mitglied derzeit so hartcodiert ist, dass nur ein Satz von Anmeldeinformationen pro Anbieterinstanz unterstützt wird.

Testen und Debuggen
Wie bereits erwähnt, waren die Tests des Anmeldeinformationsanbieters verhältnismäßig unkompliziert. Meine Teststrategie bestand darin, einen Benutzermodusdebugger an die Anmeldebenutzeroberfläche anzuhängen, um so die größtmögliche Flexibilität zu erzielen, sowohl für das Livedebuggen meines Beispielcodes als auch für das Generieren der bereits erwähnten Ablaufverfolgungsdaten. Weil die Anmeldebenutzeroberfläche als System ausgeführt wird und das interaktive Anmeldungsverfahren, auf das ich abzielte, nur über den sicheren Desktop zugänglich ist, wäre es äußerst angebracht, ein einfaches, eigenständiges Testprogramm zu schreiben, das die verschiedenen COM-Schnittstellen des Anmeldeinformationsanbieters auf die Probe stellt. Aufgrund meiner Erfahrungen mit der Technologie entschied ich mich jedoch, kein Testprogramm zu schreiben, sondern stattdessen das Livedebuggen durchzuführen. Diese Vorgehensweise ist allerdings nicht empfehlenswert.
Da ich kein Testprogramm geschrieben habe, stand ich unter noch mehr Druck, eine stabile Debugumgebung einzurichten. Wenn Sie sich nicht ständig mit der Kernelmodusentwicklung beschäftigen, kann die richtige Konfiguration dieser debuggerbasierten Testumgebung ein recht frustrierender Vorgang sein. Im Folgenden soll das dabei vorzuziehende Verfahren auf höherer Ebene erläutert werden.
Richten Sie zunächst zwei Computer in einer standardmäßigen Kerneldebugkonfiguration ein. Ein Computer sollte ein zuverlässiges Entwicklungssystem umfassen (den Debugger), der andere das Windows Vista-Testsystem (die zu debuggende Komponente). Die beiden Computer sollten über ein serielles Kabel miteinander verbunden sein.
Denken Sie daran, die zu debuggende Komponente mit einer sicheren Startpartition zu versehen, auf der Windows XP geladen ist. Die richtige Konfiguration (insbesondere für die Kommunikation der beiden Computer über das serielle Kabel) kann einige Zeit in Anspruch nehmen. Wie lässt sich die serielle Verbindung am besten testen? Starten Sie beide Computer mit Windows XP, und führen Sie HyperTerminal aus. (Wählen Sie dazu Folgendes: Start | Alle Programme | Zubehör | Kommunikation | HyperTerminal.) Geben Sie auf beiden Computern jeweils die verwendete serielle Schnittstelle an, und wählen Sie die Datenrate aus, die an den Debugger übergeben wird. Wenn die Zeichen, die Sie im HyperTerminal-Fenster von einem der Computer eingeben, auch auf dem anderen Computer angezeigt werden, sind Sie fertig. Ansonsten wiederholen Sie den Vorgang mit einer anderen seriellen Schnittstelle, einer anderen Verbindungsgeschwindigkeit oder einem anderen Kabel.
Um den zweiten Grund für die Konfiguration eines sicheren Startvorgangs zu beschreiben, muss ich etwas vorgreifen. Wenn der zu testende Anmeldeinformationsanbieter dazu führt, dass der Prozess der Anmeldebenutzeroberfläche auf dem Host abgebrochen wird oder steckenbleibt, können Sie sich nicht mehr bei Windows Vista anmelden. Dies ist mir selbst mit meiner ersten Buildkonfiguration für HybridCredProv.dll passiert. Als Laufzeitbibliothek verwendete ich die weitervertreibbare Datei „msvcr80.dll“. Mein erster Fehler war, dass ich vergessen hatte, diese Binärdatei in das system32-Verzeichnis der zu debuggenden Komponente zu kopieren. Die Konsequenz war allerdings nur, dass der Anmeldeinformationsanbieter nicht geladen wurde.
Beim nächsten Neustart von Windows Vista war ich völlig verwirrt. Diesmal sah ich, wie die Anmeldebenutzeroberfläche gestartet und mein Anmeldeinformationsanbieter geladen wurde, aber es wurde keine Benutzeroberfläche geöffnet. Im Debugger erkannte ich, dass der Startcode von msvcr80.dll bei der Sperre des Prozessladeprogramms mit einem anderen Thread steckengeblieben war. Ich kümmerte mich nicht um weitere Details, sondern überarbeitete die Buildkonfiguration von HybridCredProv.dll so, dass eine statisch verknüpfte Laufzeitbibliothek verwendet wurde.
Kurz gesagt, bietet eine sichere Startpartition eine nützliche Möglichkeit zum Beheben von Konfigurationsproblemen beim Testen des Anmeldeinformationsanbieters.
Zurück zur Debugkonfiguration. Im nächsten Schritt wird das neueste Debuggerpaket von Microsoft (mit ntsd.exe und i386kd.exe) auf beiden Systemen installiert. Installieren Sie die öffentlichen Debugsymbole für Windows Vista. (Es wird empfohlen, eine vollständige lokale Kopie der Symbole auf der zu debuggenden Komponente zu speichern.) Dieser Schritt mag unnötig erscheinen, aber nichts ist ärgerlicher, als wenn Sie mitten im Livedebuggen mit unvollständigen Daten zu kämpfen haben, weil Symbole fehlen. Aus meiner Erfahrung kann ich die folgenden Punkte nennen:
  • Eine saubere Stapelverfolgung ist immer wichtig.
  • Unter Umständen sind Symboldateien aus dem Betriebssystem erforderlich (beispielsweise ntdll.pdb), um auch nur eine annähernde Stapelverfolgung zu erzielen.
  • Bestimmte Debugszenarios auf Systemebene können den Netzwerkzugriff auf unvorhergesehene Weise blockieren, weshalb lokale Symbole erforderlich sind.
Konfigurieren Sie LogonUI.exe mithilfe von Image File Execution Options in der Systemregistrierung so, dass diese Anwendung innerhalb des Benutzermodusdebuggers (ntsd.exe) startet. Dieser Debugger leitet die Ausgabe wiederum zum Kerneldebugger um. Sie brauchen diesen Debugger, weil die Anwendung sonst nur über den sicheren Desktop sichtbar ist, sodass Sie von der Konsole des Testcomputers aus keine zuverlässige Möglichkeit besitzen, mit der Anwendung selbst und mit einem angeschlossenen Debugger zu interagieren.
Der Pfad zum Laden von Symbolen in die Datei „ntsd.exe“ lässt sich auf zwei Arten angeben: über die Befehlszeilenoption „-y“ oder über die Umgebungsvariable _NT_SYMBOL_PATH. Beim Konfigurieren von Image File Execution Options wird die erste Option empfohlen. Ich nehme allerdings lieber die zweite Option, weil ich die Variable damit einmalig und systemweit auf meinen Testcomputern festlegen kann.
Zu guter Letzt ist noch eines zu beachten: Falls der Anmeldeinformationsanbieter ausfällt und Sie wieder Zugriff auf den Computer erlangen müssen, starten Sie im abgesicherten Modus. In diesem Fall lädt Windows Vista nur den Kennwortanbieter. Wenn Sie im abgesicherten Modus mit Netzwerkverbindung starten, wird zusätzlich der Smartcard-Anmeldeinformationsanbieter geladen. (Für extrem sicherheitsbewusste Benutzer gibt es eine Richtlinie, mit der sich dieses Fallbackverhalten deaktivieren lässt.) Nach dem Starten im abgesicherten Modus können Sie die Registrierung nach Bedarf bearbeiten.

Smartcards und Initialisierung
Für alle wichtigen Tests des Hybrid-Anmeldeinformationsanbieters sind natürlich Anmeldeinformationen erforderlich. Bereiten Sie also eine Smartcard mit einer entsprechend formatierten Anmeldeinformationsdatei vor, die den Benutzernamen, das Kennwort und den optionalen Domänennamen enthält. Der Beispielcodedownload umfasst das Testdienstprogramm „WriteCred.exe“, mit dem Sie diesen Schritt ausführen können. Mit den folgenden Befehlszeilenoptionen initialisieren Sie eine Smartcard, die in das Standardlesegerät eingelegt ist, mit Ihren Anmeldeinformationen:
WriteCred.exe -p <PIN> -u <UserName> -d <DomainName> -w <PassWord>
Im Unterschied zum Hybrid-Anmeldeinformationsanbieter wird bei der aktuellen Implementierung von WriteCred angenommen, dass der Parameter für den Domänennamen obligatorisch ist (also nicht optional). Beachten Sie außerdem, dass der Kennwortparameter das Windows-Anmeldekennwort für den jeweiligen Benutzernamen umfassen sollte. Der PIN-Parameter ist zudem erforderlich, um die Anmeldeinformationsdatei auf die Smartcard zu schreiben.
Im Hinblick auf die Tests sei abschließend noch vermerkt, dass das WriteCred-Tool und der Hybrid-Anmeldeinformationsanbieter selbst nur Smartcards unterstützen, die mit dem Microsoft BasisSmartcard-Kryptografieanbieter kompatibel sind. Am besten suchen Sie im Internet nach einer aktuellen Liste kompatibler Smartcards. Ein geeigneter Suchausdruck wäre beispielsweise „Kartenmodul-Smartcards“ oder (auf Englisch) „card module smart cards“.

Dan Greif ist als Softwaresicherheitsberater in Seattle (Washington, USA) tätig. Zuvor arbeitete er sieben Jahre bei Microsoft im Windows-Sicherheitsentwicklungsteam. Die Adresse seiner Homepage lautet www.jwsecure.com. Dan Greif möchte Brian McNeill und Eric Perlin bei Microsoft auf diesem Weg für ihr Feedback zu diesem Artikel danken.

Page view tracker