使用 Windows Hello 配套 (IoT )裝置進行 Windows 解除鎖定

Windows Hello 配套裝置是一種可以與您的 Windows 10 桌面配合使用,以增強使用者身份驗證體驗的裝置。 使用 Windows Hello 配套裝置框架,即使生物特徵識別不可用 (例如,如果 Windows 10 桌面缺少用於面部識別的攝像頭或指紋閱讀器裝置 ),配套裝置也可以為 Windows Hello 提供豐富的體驗。

注意

在 Windows 10 的 2004 年版本中,Windows Hello 配套裝置框架的 API 已不被使用。

簡介

有關代碼示例,請參閱 Windows Hello 配套裝置框架的 GitHub 儲存庫。

使用案例

使用 Windows Hello 配套裝置框架,可以通過多種方式來建立與配套裝置一起的出色的 Windows 解除鎖定體驗。 例如,使用者可以:

  • 將他們的配套裝置通過 USB 連接到電腦,觸摸配套裝置上的按鈕,並自動解除鎖定他們的電腦。
  • 將一部已與電腦通過藍牙配對的手機放在口袋中。 當他們在電腦上按下空格鍵時,他們的手機會收到一條通知。 批准後,電腦將自動解除鎖定。
  • 將他們的配套裝置輕觸到 NFC 讀卡器上,快速解除鎖定他們的電腦。
  • 戴著一款已經對佩戴者進行身份驗證的健身手環。 當接近電腦並進行特殊手勢 (例如拍手 )時,電腦即可解除鎖定。

支持生物識別的 Windows Hello 配套裝置

如果配套裝置支持生物識別,某些情況下,Windows 生物識別框架可能比 Windows Hello 配套裝置框架更適合。

解決方案的組成部分

下面的圖表描述了解決方案的組成部分以及負責構建它們的人。

框架概述

Windows Hello 配套裝置框架是作為一個在 Windows 上執行的服務實現的 (在本文中稱為 Companion Authentication Service )。 這個服務負責匯出一個解除鎖定憑證,該憑證需要由存儲在 Windows Hello 配套裝置上的 HMAC 密鑰保護。 這確保了只有 Windows Hello 配套裝置在場時才能造訪解除鎖定憑證。 對於每個 (PC、Windows 使用者 )組合,將有一個唯一的解除鎖定憑證。

與 Windows Hello Companion Device Framework 整合需要:

  • 一個從 Windows 應用商店下載的 Universal Windows Platform (UWP) Windows Hello 配套裝置應用程式,用於配套裝置。
  • 在 Windows Hello 配套裝置上建立兩個256位的 HMAC 密鑰並使用它匯出 HMAC (使用 SHA-256 )。
  • 在 Windows 10 桌面上正確設定的安全設定。 在任何 Windows Hello 配套裝置可插入之前,Companion Authentication Service 將需要設定此 PIN。 使用者必須通過「設定」>「帳戶」>「登入選項」來設定 PIN。

除了上述要求外,Windows Hello 配套裝置應用程式還負責:

  • Windows Hello 配套裝置的初始註冊和後續取消註冊的使用者體驗和品牌化。
  • 在後台執行,發現 Windows Hello 配套裝置,與 Windows Hello 配套裝置和 Companion Authentication Service 進行通訊。
  • 錯誤處理

通常,配套裝置會附帶一個用於初始設定的應用程式,就像第一次設定健身手環一樣。 此文檔中描述的功能可以是該應用程式的一部分,不需要另外的應用程式。

使用者訊號

每個 Windows Hello 配套裝置應該與一個支持三個使用者訊號的應用程式相結合。 這些訊號可以是以動作或手勢形式表現的。

  • 意圖訊號:允許使用者透過點選 Windows Hello 配套裝置上的按鈕等方式來表明解除鎖定意圖。 必須在 Windows Hello 配套裝置端收集意圖訊號。
  • 使用者存在訊號:證明使用者存在。 例如,Windows Hello 配套裝置可能需要 PIN 碼才能用於解除鎖定 PC (不要與 PC PIN 碼混淆 ),或者可能需要按下按鈕。
  • 消除歧義訊號:當 Windows Hello 配套裝置有多個選項可用時,消除使用者想要解除鎖定的 Windows 10 桌面的歧義。

任意數量的這些使用者訊號可以組合成一個。 每次使用時都必須要求使用者存在和意圖訊號。

PC 和 Windows Hello 配套裝置之間的註冊和未來通訊

在將 Windows Hello 配套裝置插入 Windows Hello 配套裝置框架之前,需要向該框架註冊。 註冊體驗完全由 Windows Hello 配套裝置應用程式擁有。

Windows Hello 配套裝置與 Windows 10 桌面裝置之間的關係可以是一對多 (即,一台配套裝置可用於多台 Windows 10 桌面裝置 )。 但是,每個 Windows Hello 配套裝置只能供每個 Windows 10 桌面裝置上的一個使用者使用。

在 Windows Hello 配套裝置可以與 PC 通訊之前,它們需要就使用的傳輸達成協議。 這種選擇留給 Windows Hello 配套裝置應用程式; Windows Hello 配套裝置框架不會對 Windows Hello 配套裝置與 Windows 10 桌面裝置上的 Windows Hello 配套裝置應用程式之間所使用的傳輸類型 (USB、NFC、WiFi、BT、BLE 等 )或協定施加任何限制邊。 然而,它確實建議了傳輸層的某些安全注意事項,如本文檔的「安全要求」部分所述。 裝置提供者有責任提供這些要求。 該框架不會為您提供這些。

使用者互動模型

Windows Hello 配套裝置應用程式探索、安裝與首次註冊

典型的使用者工作流程如下:

  • 使用者在她想要使用該 Windows Hello 配套裝置解除鎖定的每台目標 Windows 10 桌面裝置上設定 PIN。
  • 使用者在其 Windows 10 桌面裝置上執行 Windows Hello 配套裝置應用,以將其 Windows Hello 配套裝置註冊至 Windows 10 桌面。

注意:

  • 我們建議簡化Windows Hello 配套裝置應用程式的發現、下載和啟動流程,並在可能的情況下實現自動化 (例如,可以透過在Windows 10 桌面裝置端的NFC 讀取器上點選Windows Hello 配套裝置來下載該應用程式). 不過,這是 Windows Hello 配套裝置和 Windows Hello 配套裝置應用程式的責任。
  • 在企業環境中,可以透過 MDM 設定 Windows Hello 配套裝置應用程式。
  • Windows Hello 配套裝置應用程式負責向使用者顯示註冊過程中發生的任何錯誤訊息。

註冊和註銷協議

下圖說明了 Windows Hello 配套裝置在註冊期間如何與配對身分驗證服務互動。

註冊流程圖。

我們的協定中使用了兩個密鑰:

  • 裝置金鑰 (devicekey):用於保護 PC 解除鎖定 Windows 所需的解除鎖定憑證。
  • 驗證金鑰 (authkey):用於對 Windows Hello 配套裝置和配套驗證服務進行相互驗證。

裝置金鑰和驗證金鑰在註冊時在 Windows Hello 配套裝置應用程式和 Windows Hello 配套裝置之間交換。 因此,Windows Hello 配套裝置應用程式和 Windows Hello 配套裝置必須使用安全傳輸來保護金鑰。

另外,請注意,雖然上圖顯示了在 Windows Hello 配套裝置上產生的兩個 HMAC 金鑰,但應用程式也可以產生它們並將它們傳送到 Windows Hello 配套裝置進行儲存。

啟動身份驗證流程

使用者可以透過兩種方式使用 Windows Hello 配套裝置框架啟動 Windows 10 桌面登入流程 (即提供意圖訊號 ):

  • 開啟筆記型電腦上的蓋子,或在電腦上按空白鍵或向上滑動。
  • 在 Windows Hello 配套裝置端執行手勢或操作。

Windows Hello 配套裝置可以選擇哪一個作為起點。 當選項一發生時,Windows Hello 配套裝置框架將通知配套裝置應用程式。 對於選項二,Windows Hello 配套裝置應用程式應查詢配套裝置以查看是否已擷取該事件。 這可確保 Windows Hello 配套裝置在解除鎖定成功之前收集意圖訊號。

Windows Hello 配套裝置憑證提供者

Windows 10 中有一個新的憑證提供程序,可以處理所有 Windows Hello 配套裝置。

Windows Hello 配套裝置憑證業者負責透過啟動觸發器展開配套裝置後台任務。 當 PC 喚醒並顯示鎖定螢幕時,首次設定觸發器。 第二次是當 PC 進入登入 UI 並且 Windows Hello 配套裝置憑證提供者是所選磁貼。

Windows Hello 配套裝置應用程式的說明程式庫將偵聽鎖定畫面狀態變更並傳送與 Windows Hello 配套裝置後台任務對應的事件。

如果有多個 Windows Hello 配套裝置後台任務,則第一個完成驗證程序的後台任務將解除鎖定電腦。 配套裝置身份驗證服務將忽略任何剩餘的身份驗證喚醒。

Windows Hello 配套裝置端的體驗由 Windows Hello 配套裝置應用程式擁有和管理。 Windows Hello 配套裝置框架無法控制這部分使用者體驗。 更具體地說,配套身份驗證提供者會通知Windows Hello 配套裝置應用程式 (透過其後台應用程式 )有關登入UI 中的狀態變更 (例如,鎖定螢幕剛剛下降,或者使用者剛剛透過按空格鍵解除鎖定螢幕 ),並且它Windows Hello 配套裝置應用程式有責任圍繞此構建體驗 (例如,當使用者按空格鍵並解除解除鎖定螢幕時,開始透過 USB 查找裝置 )。

Windows Hello 配套裝置框架將提供大量 (在地化) 文字和錯誤訊息,供 Windows Hello 配套裝置應用程式選擇。 這些將顯示在鎖定畫面頂端 (或登入 UI)。 有關更多詳細資訊,請參閱處理訊息和錯誤部分。

驗證通訊協定

一旦觸發啟動與 Windows Hello 配套裝置應用程式關聯的後台任務,它就會負責要求 Windows Hello 配套裝置驗證由 Companion Authentication Service 計算的 HMAC 值,並協助計算兩個 HMAC 值:

  • 驗證服務 HMAC = HMAC(驗證金鑰, 服務隨機數 || 裝置隨機數 || 會話隨機數)。
  • 使用隨機數計算裝置金鑰的 HMAC。
  • 使用第一個 HMAC 值與伴隨身分驗證服務產生的隨機數連線來計算驗證金鑰的 HMAC。

服務使用第二個計算值來驗證裝置並防止傳輸通道中的重播攻擊。

更新後的註冊流程圖。

生命週期管理

一次註冊,到處皆可使用

如果沒有後端伺服器,使用者必須分別向每個 Windows 10 桌面裝置註冊其 Windows Hello 配套裝置。

配套裝置供應商或 OEM 可以實施 Web 服務,以在使用者 Windows 10 桌面或行動裝置之間漫遊註冊狀態。 有關更多詳細資訊,請參閱漫遊、撤銷和篩選服務部分。

PIN 管理

在使用配套裝置之前,需要在 Windows 10 桌面裝置上設定 PIN。 這可確保使用者在其 Windows Hello 配套裝置無法運作時擁有備份。 PIN 碼由 Windows 管理,應用程式永遠看不到。 若要變更它,使用者導航至「設定」>「帳戶」>「登入」選項。

管理與政策

使用者可以透過在桌面裝置上執行 Windows Hello 配套裝置應用程式來從 Windows 10 桌面中移除 Windows Hello 配套裝置。

企業有兩種控制 Windows Hello 配套裝置框架的選項:

  • 開啟或關閉該功能
  • 設定允許使用 Windows 應用程式儲物櫃的 Windows Hello 配套裝置的白名單

Windows Hello 配套裝置框架不支援任何集中方式來保存可用配套裝置的清單,也不支援進一步篩選允許的 Windows Hello 配套裝置類型執行個體的方法 (例如,僅序號介於X 和 Y 是允許的 )。 但是,應用程式開發人員可以建立服務來提供此類功能。 有關更多詳細資訊,請參閱漫遊、撤銷和篩選服務部分。

撤銷

Windows Hello 配套裝置框架不支援從特定 Windows 10 桌面裝置遠端移除配套裝置。 相反,使用者可以透過在 Windows 10 桌面上執行的 Windows Hello 配套裝置應用程式刪除 Windows Hello 配套裝置。

然而,配套裝置供應商可以設定服務來提供遠端撤銷功能。 有關更多詳細資訊,請參閱漫遊、撤銷和篩選服務部分。

漫遊和篩選服務

配套裝置供應商可實現可用於下列場景的網頁服務:

  • 企業篩選服務:企業可以將可在其環境中工作的 Windows Hello 配套裝置限制為來自特定供應商的少數裝置。 例如,Contoso 公司可以從供應商 X 訂購 10,000 個 Model Y 配套裝置,並確保只有這些裝置可以在 Contoso 網域中運作 (而不是來自供應商 X 的任何其他裝置型號 )。
  • 庫存:企業可以確定企業環境中使用的現有配套裝置的清單。
  • 即時撤銷:如果員工報告其配套裝置遺失或被盜,則可以使用 Web 服務撤銷該裝置。
  • 漫遊:使用者只需註冊其配套裝置一次,即可在所有 Windows 10 桌面和行動裝置上執行。

實現這些功能需要 Windows Hello 配套裝置應用程式在註冊和使用時檢查 Web 服務。 Windows Hello 配套裝置應用程式可針對快取登入場景進行最佳化,例如需要每天僅檢查一次 Web 服務 (代價是將撤銷時間延長至一天 )。

Windows Hello 配套裝置框架 API 模型

概觀

Windows Hello 配套裝置應用程式應包含兩個元件:一個帶有負責註冊和取消註冊裝置的 UI 的前台應用程式,以及一個處理身分驗證的後台任務。

整體API流程如下:

  1. 註冊 Windows Hello 配套裝置
    • 確保裝置在附近並查詢其功能 (如果需要 )
    • 產生兩個 HMAC 金鑰 (在配套裝置端或應用程式端 )
    • 喚醒 RequestStartRegisteringDeviceAsync
    • 喚醒 FinishRegisteringDeviceAsync
    • 確保 Windows Hello 配套裝置應用儲存 HMAC 金鑰 (如果支援 )並且 Windows Hello 配套裝置應用程式丟棄其副本
  2. 註冊背景工作
  3. 在後台任務中等待正確的事件
    • WaitingForUserConfirmation:如果需要 Windows Hello 配套裝置端的使用者操作/手勢來啟動身分驗證流程,則等待此事件
    • CollectingCredential:如果 Windows Hello 配套裝置依賴 PC 端的使用者操作/手勢來啟動身分驗證流程 (例如,透過按空白鍵 ),請等待此事件
    • 其他觸發器,例如智慧卡:確保查詢目前驗證狀態以喚醒正確的 API。
  4. 透過喚醒 ShowNotificationMessageAsync 讓使用者了解錯誤訊息或所需的後續步驟。 僅在收集到意圖訊號後喚醒此 API
  5. 解除鎖定
    • 確保收集意圖和使用者存在訊號
    • 喚醒 StartAuthenticationAsync
    • 與配套裝置通訊以執行所需的 HMAC 操作
    • 喚醒 FinishAuthenticationAsync
  6. 當使用者要求時取消註冊 Windows Hello 配套裝置 (例如,如果他們遺失配套裝置)
    • 透過 FindAllRegisteredDeviceInfoAsync 列舉登入使用者的 Windows Hello 配套裝置
    • 使用 UnregisterDeviceAsync 取消註冊

註冊和註銷

註冊需要對配套身份驗證服務進行兩次 API 喚醒:RequestStartRegisteringDeviceAsync 和 FinishRegisteringDeviceAsync。

在進行任何這些喚醒之前,Windows Hello 配套裝置應用程式必須確保 Windows Hello 配套裝置可用。 如果 Windows Hello 配套裝置負責產生 HMAC 金鑰 (驗證和裝置金鑰 ),則 Windows Hello 配套裝置應用程式也應要求配套裝置在進行上述兩個喚醒之前產生它們。 如果 Windows Hello 配套裝置應用程式負責產生 HMAC 金鑰,則它應該在喚醒上述兩個喚醒之前執行此操作。

此外,作為第一個 API 喚醒 (RequestStartRegisteringDeviceAsync) 的一部分,Windows Hello 配套裝置應用程式必須決定裝置功能並準備將其作為 API 喚醒的一部分傳遞;例如,Windows Hello 配套裝置是否支援 HMAC 金鑰的安全儲存。 如果同一個 Windows Hello 配套裝置套用用於管理相同配套裝置的多個版本,且這些功能變更 (並且需要裝置​​查詢來決定),我們建議在進行第一次 API 喚醒之前進行此查詢。

第一個 API (RequestStartRegisteringDeviceAsync) 將會傳回第二個 API (FinishRegisteringDeviceAsync) 使用的句柄。 第一次註冊時將啟動 PIN 提示以確保使用者在場。 如果未設定 PIN,則此通話將失敗。 Windows Hello 配套裝置應用程式也可以透過 KeyCredentialManager.IsSupportedAsync 喚醒查詢是否設定了 PIN。 如果策略已停用 Windows Hello 配套裝置,RequestStartRegisteringDeviceAsync 喚醒也可能會失敗。

第一次喚醒的結果透過 secondaryAuthenticationFactorRegistrationStatus 列舉回傳:

{
	Failed = 0, 		// Something went wrong in the underlying components
	Started,     		// First call succeeded
	CanceledByUser,  	// User cancelled PIN prompt
	PinSetupRequired,	// PIN is not set up
	DisabledByPolicy,	// Companion device framework or this app is disabled
}

第二次喚醒 (FinishRegisteringDeviceAsync) 完成註冊。 作為註冊過程的一部分,Windows Hello 配套裝置應用程式可以使用配套身份驗證服務儲存配套裝置設定資料。 此數據有 4K 大小限制。 此資料將在身份驗證時可供 Windows Hello 配套裝置套用。 例如,可以使用此資料像 MAC 位址一樣連接到 Windows Hello 配套裝置,或者如果 Windows Hello 配套裝置沒有儲存並且配套裝置想要使用 PC 進行存儲,則可以使用設定資料。 請注意,作為設定資料一部分儲存的任何敏感資料都必須使用只有 Windows Hello 配套裝置知道的金鑰進行加密。 此外,由於設定資料由 Windows 服務存儲,因此 Windows Hello 配套裝置應用程式可以跨使用者組態設定檔使用該資料。

Windows Hello 配套裝置應用程式可以喚醒 AbortRegisteringDeviceAsync 來取消註冊並傳入錯誤代碼。 伴隨身份驗證服務將在遙測資料中記錄錯誤。 此喚醒的一個很好的例子是,當 Windows Hello 配套裝置出現問題且無法完成註冊時 (例如,它無法儲存 HMAC 金鑰或 BT 連線遺失 )。

Windows Hello 配套裝置應用程式必須為使用者提供從 Windows 10 桌面取消註冊其 Windows Hello 配套裝置的選項 (例如,如果他們遺失了配套裝置或購買了更新版本 )。 當使用者選擇該選項時,Windows Hello 配套裝置應用程式必須喚醒 UnregisterDeviceAsync。 Windows Hello 配套裝置應用程式的此喚醒將觸發配套裝置驗證服務,從 PC 端刪除與喚醒方應用程式的特定裝置 Id 和 AppId 對應的所有資料 (包括 HMAC 金鑰 )。 此 API 喚醒不會嘗試從 Windows Hello 配套裝置應用程式或配套裝置端刪除 HMAC 金鑰。 這留給 Windows Hello 配套裝置應用程式來實現。

Windows Hello 配套裝置應用程式負責顯示註冊和取消註冊階段發生的任何錯誤訊息。

using System;
using Windows.Security.Authentication.Identity.Provider;
using Windows.Storage.Streams;
using Windows.Security.Cryptography;
using Windows.UI.Popups;

namespace SecondaryAuthFactorSample
{
	public class DeviceRegistration
	{

		public void async OnRegisterButtonClick()
		{
			//
			// Pseudo function, the deviceId should be retrieved by the application from the device
			//
			string deviceId = await ReadSerialNumberFromDevice();

			IBuffer deviceKey = CryptographicBuffer.GenerateRandom(256/8);
			IBuffer mutualAuthenticationKey = CryptographicBuffer.GenerateRandom(256/8);

			SecondaryAuthenticationFactorRegistration registrationResult =
				await SecondaryAuthenticationFactorRegistration.RequestStartRegisteringDeviceAsync(
					deviceId,  // deviceId: max 40 wide characters. For example, serial number of the device
					SecondaryAuthenticationFactorDeviceCapabilities.SecureStorage |
						SecondaryAuthenticationFactorDeviceCapabilities.HMacSha256 |
						SecondaryAuthenticationFactorDeviceCapabilities.StoreKeys,
					"My test device 1", // deviceFriendlyName: max 64 wide characters. For example: John's card
					"SAMPLE-001", // deviceModelNumber: max 32 wide characters. The app should read the model number from device.
					deviceKey,
					mutualAuthenticationKey);

			switch(registerResult.Status)
			{
			case SecondaryAuthenticationFactorRegistrationStatus.Started:
				//
				// Pseudo function:
				// The app needs to retrieve the value from device and set into opaqueBlob
				//
				IBuffer deviceConfigData = ReadConfigurationDataFromDevice();

				if (deviceConfigData != null)
				{
					await registrationResult.Registration.FinishRegisteringDeviceAsync(deviceConfigData); //config data limited to 4096 bytes
					MessageDialog dialog = new MessageDialog("The device is registered correctly.");
					await dialog.ShowAsync();
				}
				else
				{
					await registrationResult.Registration.AbortRegisteringDeviceAsync("Failed to connect to the device");
					MessageDialog dialog = new MessageDialog("Failed to connect to the device.");
					await dialog.ShowAsync();
				}
				break;

			case SecondaryAuthenticationFactorRegistrationStatus.CanceledByUser:
				MessageDialog dialog = new MessageDialog("You didn't enter your PIN.");
				await dialog.ShowAsync();
				break;

			case SecondaryAuthenticationFactorRegistrationStatus.PinSetupRequired:
				MessageDialog dialog = new MessageDialog("Please setup PIN in settings.");
				await dialog.ShowAsync();
				break;

			case SecondaryAuthenticationFactorRegistrationStatus.DisabledByPolicy:
				MessageDialog dialog = new MessageDialog("Your enterprise prevents using this device to sign in.");
				await dialog.ShowAsync();
				break;
			}
		}

		public void async UpdateDeviceList()
		{
			IReadOnlyList<SecondaryAuthenticationFactorInfo> deviceInfoList =
				await SecondaryAuthenticationFactorRegistration.FindAllRegisteredDeviceInfoAsync(
					SecondaryAuthenticationFactorDeviceFindScope.User);

			if (deviceInfoList.Count > 0)
			{
				foreach (SecondaryAuthenticationFactorInfo deviceInfo in deviceInfoList)
				{
					//
					// Add deviceInfo.FriendlyName and deviceInfo.DeviceId into a combo box
					//
				}
			}
		}

		public void async OnUnregisterButtonClick()
		{
			string deviceId;
			//
			// Read the deviceId from the selected item in the combo box
			//
			await SecondaryAuthenticationFactorRegistration.UnregisterDeviceAsync(deviceId);
		}
	}
}

驗證

身份驗證需要對配套身份驗證服務進行兩次 API 喚醒:StartAuthenticationAsync 和 FinishAuthencationAsync。

第一個啟動 API 將傳回第二個 API 使用的句柄。 除此之外,第一個喚醒會傳回一個隨機數,一旦與其他事項連接,就需要使用儲存在 Windows Hello 配套裝置上的裝置金鑰進行 HMAC 處理。 第二個喚醒會傳回帶有裝置金鑰的 HMAC 結果,並且可能會成功完成身份驗證 (即使用者將看到他們的桌面 )。

如果政策在初始註冊後停用了該 Windows Hello 配套裝置,則第一個啟動 API (StartAuthenticationAsync) 可能會失敗。 如果 API 喚醒是在 WaitingForUserConfirmation 或 CollectingCredential 狀態之外進行的,它也可能會失敗 (本節稍後將對此進行詳細介紹 )。 如果未註冊的配套裝置應用程式喚醒它,它也可能會失敗。 secondaryAuthenticationFactorAuthenticationStatus Enum 總結了可能的結果:

{
	Failed = 0, 					// Something went wrong in the underlying components
	Started,
	UnknownDevice,    				// Companion device app is not registered with framework
	DisabledByPolicy, 				// Policy disabled this device after registration
	InvalidAuthenticationStage,		// Companion device framework is not currently accepting
									// incoming authentication requests
}

如果第一次喚醒中提供的隨機數已過期 (20 秒 ),第二個 API 喚醒 (FinishAuthencationAsync) 可能會失敗。 secondaryAuthenticationFactorFinishAuthenticationStatus 列舉擷取可能的結果。

{
	Failed = 0, 	// Something went wrong in the underlying components
	Completed,   	// Success
	NonceExpired,   // Nonce is expired
}

兩個 API 喚醒 (StartAuthenticationAsync 和 FinishAuthencationAsync )的時間需要與 Windows Hello 配套裝置收集意圖、使用者存在和消歧訊號的方式保持一致 (有關更多詳細資訊,請參閱使用者訊號 )。 例如,在意圖訊號可用之前不得提交第二次喚醒。 換句話說,如果使用者未表達解除鎖定意圖,則 PC 不應解除鎖定。 為了更清楚地說明這一點,假設藍牙近距離用於 PC 解除鎖定,那麼必須收集明確的意圖訊號,否則,一旦使用者在前往廚房的路上經過他的 PC,PC 就會解除鎖定。 此外,第一次喚醒傳回的隨機數是有時間限制的 (20 秒 ),並且會在一定時間後過期。 因此,只有當 Windows Hello 配套裝置應用程式明確指示配套裝置存在時 (例如,配套裝置插入 USB 連接埠或輕觸 NFC 讀取器 ),才應進行第一次通話。 對於藍牙,在檢查 Windows Hello 配套裝置是否存在時,必須小心避免影響 PC 端的電池或影響此時正在進行的其他藍牙活動。 此外,如果需要提供使用者存在訊號 (例如,透過輸入 PIN ),建議僅在收集該訊號後才進行第一次身份驗證喚醒。

Windows Hello 配套裝置框架透過提供使用者在身分驗證流程中所處位置的完整圖片,協助 Windows Hello 配套裝置應用程式就何時進行上述兩個喚醒做出明智的決定。 Windows Hello 配套裝置框架透過向應用程式後台任務提供鎖定狀態變更通知來提供此功能。

配套裝置流程

每個狀態的詳細資訊如下:

State 描述
WaitingForUserConfirmation 當鎖定畫面關閉時 (例如,使用者按下 Windows + L ),會觸發此狀態變更通知事件。 我們建議不要請求任何與在此狀態下難以找到裝置相關的錯誤訊息。 一般來說,我們建議僅在意圖訊號可用時才顯示訊息。 如果配套裝置收集意圖訊號 (例如,點選NFC 讀取器、按下配套裝置上的按鈕或特定手勢 (如拍手 ) ),則Windows Hello 配套裝置應用程式應在此狀態下進行第一個API 喚醒以進行身份驗證,並且 Windows Hello 配套裝置應用後台任務從配套裝置接收檢測到意圖訊號的指示。 否則,如果 Windows Hello 配套裝置應用依賴電腦啟動驗證流程 (透過讓使用者向上滑動解除鎖定螢幕或按空白鍵 ),則 Windows Hello 配套裝置應用程式需要等待下一個狀態 (CollectingCredential)。
CollectingCredential 當使用者開啟筆記型電腦蓋子、按鍵盤上的任何按鍵或向上滑動到解除鎖定螢幕時,會觸發此狀態變更通知事件。 如果 Windows Hello 配套裝置依賴上述操作來開始收集意圖訊號,則 Windows Hello 配套裝置應用程式應開始收集該訊號 (例如,透過配套裝置上的彈出視窗詢問使用者是否要解除鎖定電腦 ) 。 如果 Windows Hello 配套裝置應用程式需要使用者在配套裝置上提供使用者存在訊號 (例如在 Windows Hello 配套裝置上輸入 PIN ),那麼此時是提供錯誤案例的好時機。
SuspendingAuthentication 當 Windows Hello 配套裝置應用程式收到此狀態時,這表示配套身分驗證服務已停止接受身分驗證要求。
CredentialCollected 這代表著另一個 Windows Hello 配套裝置應用程式已喚醒第二個 API,並且配套身份驗證服務正在驗證提交的內容。 此時,伴隨身分驗證服務不會接受任何其他身分驗證要求,除非目前提交的身份驗證請求未通過驗證。 Windows Hello 配套裝置應用程式應保持調整,直到達到下一個狀態。
CredentialAuthenticated 這代表著提交的憑證有效。 credentialAuthenticated 具有成功的 Windows Hello 配套裝置的裝置 ID。 Windows Hello 配套裝置應用程式應確保對其進行檢查,以查看其關聯裝置是否為獲勝者。 如果不是,則 Windows Hello 配套裝置應用程式應避免顯示任何驗證後流程 (例如配套裝置上的成功訊息或該裝置上的振動 )。 請注意,如果提交的憑證不起作用,狀態將變更為 CollectingCredential 狀態。
StoppingAuthentication 身份驗證成功,使用者看到桌面。 是時候終止你的後台任務了。 在退出後台任務之前,明確取消註冊 StageEvent 處理程序。 這將有助於後台任務快速退出。

Windows Hello 配套裝置應用程式應僅在前兩種狀態下喚醒兩個驗證 API。 Windows Hello 配套裝置應用程式應檢查觸發此事件的場景。 有兩種可能性:解除鎖定或後解除鎖定。 目前僅支援解除鎖定。 在即將發布的版本中,可能會支援解除鎖定後場景。 secondaryAuthenticationFactorAuthenticationScenario 列舉擷取這兩個選項:

{
	SignIn = 0,      	// Running under lock screen mode
	CredentialPrompt, 	// Running post unlock
}

完整程式碼範例:

using System;
using Windows.Security.Authentication.Identity.Provider;
using Windows.Storage.Streams;
using Windows.Security.Cryptography;
using System.Threading;
using Windows.ApplicationModel.Background;

namespace SecondaryAuthFactorSample
{
	public sealed class AuthenticationTask : IBackgroundTask
	{
		private string _deviceId;
		private static AutoResetEvent _exitTaskEvent = new AutoResetEvent(false);
		private static IBackgroundTaskInstance _taskInstance;
		private BackgroundTaskDeferral _deferral;

		private void Authenticate()
		{
			int retryCount = 0;

			while (retryCount < 3)
			{
				//
				// Pseudo code, the svcAuthNonce should be passed to device or generated from device
				//
				IBuffer svcAuthNonce = CryptographicBuffer.GenerateRandom(256/8);

				SecondaryAuthenticationFactorAuthenticationResult authResult = await
					SecondaryAuthenticationFactorAuthentication.StartAuthenticationAsync(
						_deviceId,
						svcAuthNonce);
				if (authResult.Status != SecondaryAuthenticationFactorAuthenticationStatus.Started)
				{
					SecondaryAuthenticationFactorAuthenticationMessage message;
					switch (authResult.Status)
					{
						case SecondaryAuthenticationFactorAuthenticationStatus.DisabledByPolicy:
							message = SecondaryAuthenticationFactorAuthenticationMessage.DisabledByPolicy;
							break;
						case SecondaryAuthenticationFactorAuthenticationStatus.InvalidAuthenticationStage:
							// The task might need to wait for a SecondaryAuthenticationFactorAuthenticationStageChangedEvent
							break;
						default:
							return;
					}

					// Show error message. Limited to 512 characters wide
					await SecondaryAuthenticationFactorAuthentication.ShowNotificationMessageAsync(null, message);
					return;
				}

				//
				// Pseudo function:
				// The device calculates and returns sessionHmac and deviceHmac
				//
				await GetHmacsFromDevice(
					authResult.Authentication.ServiceAuthenticationHmac,
					authResult.Authentication.DeviceNonce,
					authResult.Authentication.SessionNonce,
					out deviceHmac,
					out sessionHmac);
				if (sessionHmac == null ||
					deviceHmac == null)
				{
					await authResult.Authentication.AbortAuthenticationAsync(
						"Failed to read data from device");
					return;
				}

				SecondaryAuthenticationFactorFinishAuthenticationStatus status =
					await authResult.Authentication.FinishAuthencationAsync(deviceHmac, sessionHmac);
				if (status == SecondaryAuthenticationFactorFinishAuthenticationStatus.NonceExpired)
				{
					retryCount++;
					continue;
				}
				else if (status == SecondaryAuthenticationFactorFinishAuthenticationStatus.Completed)
				{
					// The credential data is collected and ready for unlock
					return;
				}
			}
		}

		public void OnAuthenticationStageChanged(
			object sender,
			SecondaryAuthenticationFactorAuthenticationStageChangedEventArgs args)
		{
			// The application should check the args.StageInfo.Stage to determine what to do in next. Note that args.StageInfo.Scenario will have the scenario information (SignIn vs CredentialPrompt).

			switch(args.StageInfo.Stage)
			{
			case SecondaryAuthenticationFactorAuthenticationStage.WaitingForUserConfirmation:
				// Show welcome message
				await SecondaryAuthenticationFactorAuthentication.ShowNotificationMessageAsync(
					null,
					SecondaryAuthenticationFactorAuthenticationMessage.WelcomeMessageSwipeUp);
				break;

			case SecondaryAuthenticationFactorAuthenticationStage.CollectingCredential:
				// Authenticate device
				Authenticate();
				break;

			case SecondaryAuthenticationFactorAuthenticationStage.CredentialAuthenticated:
				if (args.StageInfo.DeviceId = _deviceId)
				{
					// Show notification on device about PC unlock
				}
				break;

			case SecondaryAuthenticationFactorAuthenticationStage.StoppingAuthentication:
				// Quit from background task
				_exitTaskEvent.Set();
				break;
			}

			Debug.WriteLine("Authentication Stage = " + args.StageInfo.AuthenticationStage.ToString());
		}

		//
		// The Run method is the entry point of a background task.
		//
		public void Run(IBackgroundTaskInstance taskInstance)
		{
			_taskInstance = taskInstance;
			_deferral = taskInstance.GetDeferral();

			// Register canceled event for this task
			taskInstance.Canceled += TaskInstanceCanceled;

			// Find all device registred by this application
			IReadOnlyList<SecondaryAuthenticationFactorInfo> deviceInfoList =
				await SecondaryAuthenticationFactorRegistration.FindAllRegisteredDeviceInfoAsync(
					SecondaryAuthenticationFactorDeviceFindScope.AllUsers);

			if (deviceInfoList.Count == 0)
			{
				// Quit the task silently
				return;
			}
			_deviceId = deviceInfoList[0].DeviceId;
			Debug.WriteLine("Use first device '" + _deviceId + "' in the list to signin");

			// Register AuthenticationStageChanged event
			SecondaryAuthenticationFactorRegistration.AuthenticationStageChanged += OnAuthenticationStageChanged;

			// Wait the task exit event
			_exitTaskEvent.WaitOne();

			_deferral.Complete();
		}

		void TaskInstanceCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
		{
			_exitTaskEvent.Set();
		}
	}
}

註冊背景工作

當 Windows Hello 配套裝置應用程式註冊第一個配套裝置時,它還應該註冊其後台任務元件,該元件將在裝置和配套裝置驗證服務之間傳遞身分驗證資訊。

using System;
using Windows.Security.Authentication.Identity.Provider;
using Windows.Storage.Streams;
using Windows.ApplicationModel.Background;

namespace SecondaryAuthFactorSample
{
	public class BackgroundTaskManager
	{
		// Register background task
		public static async Task<IBackgroundTaskRegistration> GetOrRegisterBackgroundTaskAsync(
			string bgTaskName,
			string taskEntryPoint)
		{
			// Check if there's an existing background task already registered
			var bgTask = (from t in BackgroundTaskRegistration.AllTasks
						  where t.Value.Name.Equals(bgTaskName)
						  select t.Value).SingleOrDefault();
			if (bgTask == null)
			{
				BackgroundAccessStatus status =
					BackgroundExecutionManager.RequestAccessAsync().AsTask().GetAwaiter().GetResult();

				if (status == BackgroundAccessStatus.Denied)
				{
					Debug.WriteLine("Background Execution is denied.");
					return null;
				}

				var taskBuilder = new BackgroundTaskBuilder();
				taskBuilder.Name = bgTaskName;
				taskBuilder.TaskEntryPoint = taskEntryPoint;
				taskBuilder.SetTrigger(new SecondaryAuthenticationFactorAuthenticationTrigger());
				bgTask = taskBuilder.Register();
				// Background task is registered
			}

			bgTask.Completed += BgTask_Completed;
			bgTask.Progress += BgTask_Progress;

			return bgTask;
		}
	}
}

錯誤和訊息

Windows Hello 配套裝置框架負責提供使用者登入成功或失敗的回饋。 Windows Hello 配套裝置框架將為 Windows Hello 配套裝置應用程式提供大量 (在地化) 文字和錯誤訊息供選擇。 這些將顯示在登入 UI 中。

配套裝置錯誤

Windows Hello 配套裝置應用程式可使用 ShowNotificationMessageAsync 在登入 UI 中向使用者顯示訊息。 當意圖訊號可用時喚醒此 API。 請注意,必須始終在 Windows Hello 配套裝置端收集意圖訊號。

有兩種類型的消息:引導和錯誤。

引導訊息旨在向使用者展示如何開始解除鎖定過程。 這些訊息僅在首次裝置註冊時在鎖定畫面上向使用者顯示一次,並且不再顯示。 這些訊息將繼續顯示在鎖定畫面下。

錯誤訊息始終會顯示,並會在提供意圖訊號後顯示。 鑑於在向使用者顯示訊息之前必須收集意圖訊號,且使用者將僅使用一台 Windows Hello 配套裝置提供該意圖,因此不得出現多個 Windows Hello 配套裝置競相顯示錯誤訊息的情況。 因此,Windows Hello 配套裝置框架不維護任何佇列。 當喚醒者要求錯誤訊息時,該訊息將顯示 5 秒,並且在這 5 秒內顯示錯誤訊息的所有其他請求都會被丟棄。 一旦過了 5 秒,另一個喚醒者就有機會顯示錯誤訊息。 我們禁止任何喚醒者干擾錯誤頻道。

引導和錯誤訊息如下。 裝置名稱是配套裝置應用程式作為 ShowNotificationMessageAsync 的一部分傳遞的參數。

指引

  • “向上滑動或按空白鍵可使用裝置名稱登入。”
  • 「正在設定您的配套裝置。 請等待或使用其他登入選項。」
  • “在 NFC 讀取器上點選裝置名稱即可登入。”
  • “正在尋找裝置名稱...”
  • “將裝置名稱插入 USB 連接埠即可登入。”

錯誤

  • “有關登入說明,請參閱裝置名稱。”
  • “開啟藍牙以使用裝置名稱登入。”
  • “開啟 NFC 以使用裝置名稱登入。”
  • “連接到 Wi-Fi 網路以使用裝置名稱登入。”
  • “再次點選裝置名稱。”
  • 「您的企業禁止使用裝置名稱登入。 使用其他登入選項。」
  • “點選裝置名稱即可登入。”
  • “將手指放在裝置名稱上即可登入。”
  • “在裝置名稱上滑動手指即可登入。”
  • “無法使用裝置名稱登入。 使用其他登入選項。」
  • 「出了些問題。 使用其他登入選項,然後再次設定裝置名稱。」
  • “再試一次。”
  • “將您的 Spoken Passphrase 說出裝置名稱。”
  • “準備使用裝置名稱登入。”
  • “首先使用其他登入選項,然後您可以使用裝置名稱登入。”

列舉已註冊的裝置

Windows Hello 配套裝置應用程式可透過 FindAllRegisteredDeviceInfoAsync 喚醒列舉已註冊的配套裝置清單。 此 API 支援透過列舉 secondaryAuthenticationFactorDeviceFindScope 設定的兩種查詢類型:

{
	User = 0,
	AllUsers,
}

第一個範圍返回登入使用者的裝置列表。 第二個範圍返回該電腦上所有使用者的裝置列表。 在注銷時必須使用第一個範圍,以避免注銷其他使用者的 Windows Hello 配套裝置。 第二個範圍必須在驗證或註冊時使用:在註冊時,此列舉可以幫助應用程式避免嘗試兩次註冊相同的 Windows Hello 配套裝置。

請注意,即使應用程式不執行此檢查,PC 也會進行檢查,並拒絕同一個 Windows Hello 配套裝置被註冊超過一次。 在認證時,使用 AllUsers 範圍有助於 Windows Hello 配套裝置應用程式支持切換使用者流程:當使用者 B 登入時,登入使用者 A (這需要兩個使用者都安裝了 Windows Hello 配套裝置應用程式,並且使用者 A 已經將他們的配套裝置註冊到了 PC 上,並且 PC 正處於鎖定螢幕 (或登入螢幕 ) )。

安全性需求

Companion Authentication Service 提供以下安全保護措施。

  • 在執行為中等使用者或應用程式容器的 Windows 10 桌面裝置上的惡意軟件無法靜默地使用 Windows Hello 配套裝置來造訪 PC 上作為 Windows Hello 的一部分存儲的使用者憑據密鑰。
  • 在 Windows 10 桌面裝置上,一個惡意使用者無法使用屬於另一個使用者的 Windows Hello 配套裝置來靜默造訪他的使用者憑據密鑰 (在同一台 Windows 10 桌面裝置上 )。
  • Windows Hello 配套裝置上的惡意軟件無法默默地造訪 Windows 10 桌面裝置上的使用者憑據密鑰,包括利用專門為 Windows Hello 配套裝置框架開發的功能或代碼。
  • 惡意使用者無法通過捕獲 Windows Hello 配套裝置和 Windows 10 桌面裝置之間的流量並稍後重放來解除鎖定 Windows 10 桌面裝置。 在我們的協議中使用 nonce、authkey 和 HMAC 保證了對重放攻擊的保護。
  • 惡意軟件或惡意使用者在惡意 PC 上無法使用 Windows Hello 配套裝置來造訪誠實使用者的 PC。 這是通過在我們的協議中使用 authkey 和 HMAC 實現 Companion Authentication Service 和 Windows Hello 配套裝置之間的互相認證來實現的。

實現上述列舉的安全保護的關鍵在於保護 HMAC 密鑰免受未經授權的造訪,並驗證使用者的存在。 更具體地說,它必須滿足這些要求:

  • 提供保護,防止克隆 Windows Hello 配套裝置
  • 在註冊時向 PC 發送 HMAC 密鑰時,提供防竊聽保護
  • 確保使用者存在訊號可用