WebAuthn 無密碼登入不等於 Passkey
2 |
入手新玩具 - 大名鼎鼎的 YubiKey 5:
最近在研究 WebAuthn/Passkey,手邊的土砲 USB 金鑰只支援 GPG,雖然另有開源專案 u2f-token,但只有 U2F,不支援 FIDO2。評估之後,決定一咬牙買支 YubiKey 玩玩,YubiKey 5 應屬當前實體金鑰市場支援度最廣、配套軟體、參考資源最完整的產品,雖然價格不斐(一支相當於 30 支 ST-LINK V2 呀),但是該看看傳說中的正規武器增長見識,以免淪為只會土砲的土包子。
在 .NET 要實作 WebAuthn/Passkey 登入,一般都是用 .NET Foundation 旗下的 FIDO2 .NET Library 開發,官方範例提供了兩種無密碼登入情境,一種是 Passwordless,一種是 Usernameless:
兩種都可以做到無密碼登入,但奇怪的是 Yubico Authenticator 軟體只會看得到 Usernameless 註冊產生的信物,Passwordless 註冊後 Passkeys 區不會增加項目:
這讓我有些疑惑,我一直以為 WebAuthn/Passkey 就等於無密碼登入,會為每個網站產生專屬信物,但感覺事實與我想像不同。
從 Yubico 的 WebAuthn 開發者指南我學到這些存在實體金鑰上的信物,專業術語叫 Disconverable Credential (又名 Resident Key),就是我原本認知為每個網站特別建立的專屬信物(Credential),可以支援 Username-less Flow,但其中有段說明讓我十分迷惑:
Discoverable Credential means that the private key and associated metadata is stored in persistent memory on the authenticator, instead of encrypted and stored on the relying party server. If the credentials were stored on the server, then the server would need to return that to the authenticator before the authenticator could decrypt and use it. This would mean that the user would need to provide a username to identify which credential to provide, and usually also a password to verify their identity.
言下之意,FIDO2 .NET Library 範例的 Passwordless 情境,私鑰跟信物是存在伺服器端?這不就失去使用實體金鑰的優勢?
爬文找到解答:
- How do non-"resident" keys work in WebAuthn? (所以不只我有這個疑惑)
- Discoverable vs. non-discoverable
- WebAuthn Resident Key: Discoverable Credentials as Passkeys
先釐清名詞:我們將 FIDO2 .NET Library Passwordless 範例使用的信物姑且命名為 Non-Discoverable Credential,它不像 Discoverable Credential(Resident Key) 會被存在實體金鑰內部(實際上實體金鑰的儲存空間也有限,YubiKey 5 可放 25 個、SoloKey 50、NitroKeys 3 10 個)。
Non-Discoverable Credential 的簽章金鑰匙是透過實體金鑰上的 Master Key 與 Credential Id 產生,因此在註冊時,實體金鑰產生公私鑰,將公鑰及 Credential Id (內含金鑰種子)傳送給伺服器端保存;當需要登入時,伺服器端由客戶端提供使用者帳號、名稱等資訊查到 Credential Id 傳至客戶端,實體金鑰析 Credential Id 取出種子,還原私鑰進行電子簽名。
Yubico U2F 的演算法範例如下:(各廠商實作可能有所不同)
實體金鑰有內建一把 32 Byte Master HMAC-SHA256 Secret Key k,HMAC-SHA256k 為 Keyed-Hash Message Authentictaion 演算法,在計算時會結合 k。
註冊
- 產生隨機 32 Byte Token t
- 種子 s = HMAC-SHA256k(0x01 || t || rpId)
- 由種子 s 產生 ECDSA (NIST P-256) 金鑰對 (sk, pk)
- 計算 Authenticqation Tag a = HMAC-SHA256k(0x02 || t || rpId)
- 上傳 Credential Id (64 Byte) = t || a 與 pk
身分驗證
- 檢查 Credential Id 需為 64 Byte,拆成兩個 32 Byte,分別為 t 及 a
- 檢查 a == HMAC-SHA256k(0x02 || t || rpId),若不相等拒絕執行
- 計算 s = HMAC-SHA256k(0x01 || t || rpId)
- 由種子 s 產生 ECDSA (NIST P-256) 金鑰對 (sk, pk)
- 使用 sk 對 Challenge 做數位簽章
由於 k 只存在實體金鑰內部不會外流,故只有同一把金鑰有能力算出 s 及還原 sk。
由以上原理,可知在 Non-Discoverable Credential 情境下,私鑰由伺服器傳回的 Credential Id 還原,故實體金鑰上不需保存信物;而使用者必須持有當初註冊的實體金鑰才能完成身分驗證(不然無法用 HMAC-SHA256k 還原私鑰),一樣可以實現無密碼登入且具備一定安全性。但依據 FIDO 規範,Passkey 基於 Discoverable Credential,WebAuthn Credential 必須為 Discoverable 才算是 Passkey,所謂 Discoverable 指不需先向伺服器提供身分識別代碼(User Handle,例如:帳號、姓名、Email、手機號碼... 等)即能完成身分驗證,因此信物必須存在驗證器上,登入時再依網域名稱找出對應金鑰。參考
使用 Non-Discoverable Credential 的好處是能與 FIDO 1.0 U2F 規格的驗證器相容,且不受限驗證器儲存 Passkey 數量上限,雖然使用體驗不如 Passkey,但仍有其適用場合。
最後,實測一下 WebAuthn 傳輸驗證這點,下圖分別為 Passwordless 與 Usernameless 登入時網站傳回的 Assertion Options 資料,最大的差異便在於前者必須傳回 Credential Id,而 Passkey 不用。收工。
Comments
# by longer
RequireResidentKey已經過時不再是boolean, 取代為ResidentKey:(google的解釋) //discouraged(最好使用伺服器端憑證,但會接受客戶端可發現的憑證), //preferred(首選:依賴方強烈首選用戶端可發現憑證,但會接受伺服器端憑證), //required(依賴方需要用戶端可發現的憑證,並且如果無法建立該憑證,則準備好接收錯誤) 原來的RequireResidentKey如果是false,是否就等於discouraged?還是很難意會。
# by Wayne
原來的RequireResidentKey如果是false,是否就等於discouraged? -> 是 且依目前Apple的實作,就算從rp server接收到ResidentKey:ResidentKey也一定會做出discoverable credential做為passkey給其生態系使用 而Andoird則是會做出non-discoverable creedential 可以從兩種authenticator的GetAssertion中的response.userHandle是否為空來驗證