Skip to main content

webauthn_kit/
aaguid.rs

1//! Well-known authenticator AAGUIDs (informational registry).
2//!
3//! The AAGUID (Authenticator Attestation Globally Unique Identifier) is the
4//! 16-byte model identifier embedded in attested credential data during
5//! registration (CTAP2 §6.1). [`known_aaguid`] maps a small set of widely
6//! deployed AAGUIDs to human-readable authenticator names for display and
7//! inventory purposes.
8//!
9//! # This is NOT a metadata service
10//!
11//! This table is **informational only**. Real authenticator policy — attestation
12//! root certificates, authenticator capabilities, security notices, revocation
13//! — requires the [FIDO Metadata Service](https://fidoalliance.org/metadata/)
14//! (MDS), whose signed blob is versioned and refreshed continuously. MDS
15//! lookup is future work (see THREAT-MODEL.md OPEN-3); an AAGUID absent from
16//! this table is *unknown*, not *untrusted*, and an entry here confers no
17//! trust beyond the attestation verification that already happened.
18//!
19//! # Sources
20//!
21//! - Hardware security keys: FIDO Metadata Service (MDS3) metadata
22//!   statements, `https://mds3.fidoalliance.org/` (retrieved 2026-09).
23//! - Platform authenticators (Apple/Google/Microsoft) and password managers:
24//!   the community-maintained
25//!   [`passkey-authenticator-aaguids`](https://github.com/passkeydeveloper/passkey-authenticator-aaguids)
26//!   registry (retrieved 2026-09).
27//! - Windows Hello hardware/software/VBS AAGUIDs additionally match
28//!   Microsoft's published documentation.
29
30/// YubiKey 5 Series (USB-A/C, firmware 5.x). Source: FIDO MDS3.
31pub const AAGUID_NAME_YUBIKEY_5_SERIES: &str = "YubiKey 5 Series";
32/// YubiKey 5 Series with NFC (firmware 5.x). Source: FIDO MDS3.
33pub const AAGUID_NAME_YUBIKEY_5_NFC: &str = "YubiKey 5 Series with NFC";
34/// YubiKey 5 Series with Lightning connector (5Ci). Source: FIDO MDS3.
35pub const AAGUID_NAME_YUBIKEY_5C: &str = "YubiKey 5 Series with Lightning";
36/// YubiKey 5 FIPS Series (USB). Source: FIDO MDS3.
37pub const AAGUID_NAME_YUBIKEY_5_FIPS: &str = "YubiKey 5 FIPS Series";
38/// YubiKey 5 FIPS Series with NFC. Source: FIDO MDS3.
39pub const AAGUID_NAME_YUBIKEY_5_FIPS_NFC: &str = "YubiKey 5 FIPS Series with NFC";
40/// YubiKey Bio Series — FIDO Edition (fingerprint). Source: FIDO MDS3.
41pub const AAGUID_NAME_YUBIKEY_BIO: &str = "YubiKey Bio Series - FIDO Edition";
42/// Security Key by Yubico (USB). Source: FIDO MDS3.
43pub const AAGUID_NAME_SECURITY_KEY_YUBICO: &str = "Security Key by Yubico";
44/// Security Key NFC by Yubico. Source: FIDO MDS3.
45pub const AAGUID_NAME_SECURITY_KEY_YUBICO_NFC: &str = "Security Key NFC by Yubico";
46/// Feitian ePass FIDO2 (USB). Source: FIDO MDS3.
47pub const AAGUID_NAME_FEITIAN_EPASS_FIDO2: &str = "Feitian ePass FIDO2 Authenticator";
48/// Feitian ePass FIDO2-NFC. Source: FIDO MDS3.
49pub const AAGUID_NAME_FEITIAN_EPASS_FIDO2_NFC: &str = "Feitian ePass FIDO2-NFC Authenticator";
50/// Feitian BioPass FIDO2 Pro (fingerprint). Source: FIDO MDS3.
51pub const AAGUID_NAME_FEITIAN_BIOPASS_PRO: &str = "Feitian BioPass FIDO2 Pro Authenticator";
52/// SoloKeys Solo (Secp256R1 build). Source: FIDO MDS3.
53pub const AAGUID_NAME_SOLO_SECP256R1: &str = "SoloKeys Solo Secp256R1 FIDO2 Authenticator";
54/// SoloKeys Solo Tap. Source: FIDO MDS3.
55pub const AAGUID_NAME_SOLO_TAP: &str = "SoloKeys Solo Tap Secp256R1 FIDO2 Authenticator";
56/// Nitrokey 3 (AM build). Source: FIDO MDS3.
57pub const AAGUID_NAME_NITROKEY_3: &str = "Nitrokey 3 AM";
58/// Google Titan Security Key v2. Source: FIDO MDS3.
59pub const AAGUID_NAME_GOOGLE_TITAN_V2: &str = "Google Titan Security Key v2";
60/// Apple iCloud Keychain synced passkeys. Source: passkey-authenticator-aaguids.
61pub const AAGUID_NAME_APPLE_ICLOUD_KEYCHAIN: &str = "Apple iCloud Keychain (Managed)";
62/// Apple Passwords app. Source: passkey-authenticator-aaguids.
63pub const AAGUID_NAME_APPLE_PASSWORDS: &str = "Apple Passwords";
64/// Google Password Manager (Android/Chrome synced passkeys).
65/// Source: passkey-authenticator-aaguids.
66pub const AAGUID_NAME_GOOGLE_PASSWORD_MANAGER: &str = "Google Password Manager";
67/// Chrome profile-bound passkeys on macOS. Source: passkey-authenticator-aaguids.
68pub const AAGUID_NAME_CHROME_ON_MAC: &str = "Chrome on Mac";
69/// Windows Hello hardware authenticator (TPM). Source: Microsoft documentation / FIDO MDS3.
70pub const AAGUID_NAME_WINDOWS_HELLO_HARDWARE: &str = "Windows Hello Hardware Authenticator";
71/// Windows Hello software authenticator. Source: Microsoft documentation / FIDO MDS3.
72pub const AAGUID_NAME_WINDOWS_HELLO_SOFTWARE: &str = "Windows Hello Software Authenticator";
73/// Windows Hello VBS (virtualization-based security) hardware authenticator.
74/// Source: Microsoft documentation / FIDO MDS3.
75pub const AAGUID_NAME_WINDOWS_HELLO_VBS: &str = "Windows Hello VBS Hardware Authenticator";
76/// 1Password. Source: passkey-authenticator-aaguids.
77pub const AAGUID_NAME_ONEPASSWORD: &str = "1Password";
78/// Bitwarden. Source: passkey-authenticator-aaguids.
79pub const AAGUID_NAME_BITWARDEN: &str = "Bitwarden";
80/// Dashlane. Source: passkey-authenticator-aaguids.
81pub const AAGUID_NAME_DASHLANE: &str = "Dashlane";
82/// Keeper. Source: passkey-authenticator-aaguids.
83pub const AAGUID_NAME_KEEPER: &str = "Keeper";
84/// KeePassXC. Source: passkey-authenticator-aaguids.
85pub const AAGUID_NAME_KEEPASSXC: &str = "KeePassXC";
86/// Samsung Pass. Source: passkey-authenticator-aaguids.
87pub const AAGUID_NAME_SAMSUNG_PASS: &str = "Samsung Pass";
88
89/// AAGUID → name table entry (the 16-byte AAGUID is written in UUID order,
90/// exactly as it appears in attested credential data).
91const KNOWN_AAGUIDS: &[([u8; 16], &str)] = &[
92    // --- Yubico (FIDO MDS3) ---
93    (
94        uuid("cb69481e-8ff7-4039-93ec-0a2729a154a8"),
95        AAGUID_NAME_YUBIKEY_5_SERIES,
96    ),
97    (
98        uuid("fa2b99dc-9e39-4257-8f92-4a30d23c4118"),
99        AAGUID_NAME_YUBIKEY_5_NFC,
100    ),
101    (
102        uuid("a02167b9-ae71-4ac7-9a07-06432ebb6f1c"),
103        AAGUID_NAME_YUBIKEY_5C,
104    ),
105    (
106        uuid("57f7de54-c807-4eab-b1c6-1c9be7984e92"),
107        AAGUID_NAME_YUBIKEY_5_FIPS,
108    ),
109    (
110        uuid("c1f9a0bc-1dd2-404a-b27f-8e29047a43fd"),
111        AAGUID_NAME_YUBIKEY_5_FIPS_NFC,
112    ),
113    (
114        uuid("d8522d9f-575b-4866-88a9-ba99fa02f35b"),
115        AAGUID_NAME_YUBIKEY_BIO,
116    ),
117    (
118        uuid("f8a011f3-8c0a-4d15-8006-17111f9edc7d"),
119        AAGUID_NAME_SECURITY_KEY_YUBICO,
120    ),
121    (
122        uuid("b7d3f68e-88a6-471e-9ecf-2df26d041ede"),
123        AAGUID_NAME_SECURITY_KEY_YUBICO_NFC,
124    ),
125    // --- Feitian (FIDO MDS3) ---
126    (
127        uuid("833b721a-ff5f-4d00-bb2e-bdda3ec01e29"),
128        AAGUID_NAME_FEITIAN_EPASS_FIDO2,
129    ),
130    (
131        uuid("ee041bce-25e5-4cdb-8f86-897fd6418464"),
132        AAGUID_NAME_FEITIAN_EPASS_FIDO2_NFC,
133    ),
134    (
135        uuid("4c0cf95d-2f40-43b5-ba42-4c83a11c04ba"),
136        AAGUID_NAME_FEITIAN_BIOPASS_PRO,
137    ),
138    // --- SoloKeys (FIDO MDS3) ---
139    (
140        uuid("8876631b-d4a0-427f-5773-0ec71c9e0279"),
141        AAGUID_NAME_SOLO_SECP256R1,
142    ),
143    (
144        uuid("8976631b-d4a0-427f-5773-0ec71c9e0279"),
145        AAGUID_NAME_SOLO_TAP,
146    ),
147    // --- Nitrokey (FIDO MDS3) ---
148    (
149        uuid("2cd2f727-f6ca-44da-8f48-5c2e5da000a2"),
150        AAGUID_NAME_NITROKEY_3,
151    ),
152    // --- Google hardware (FIDO MDS3) ---
153    (
154        uuid("42b4fb4a-2866-43b2-9bf7-6c6669c2e5d3"),
155        AAGUID_NAME_GOOGLE_TITAN_V2,
156    ),
157    // --- Platform authenticators (passkey-authenticator-aaguids / Microsoft) ---
158    (
159        uuid("dd4ec289-e01d-41c9-bb89-70fa845d4bf2"),
160        AAGUID_NAME_APPLE_ICLOUD_KEYCHAIN,
161    ),
162    (
163        uuid("fbfc3007-154e-4ecc-8c0b-6e020557d7bd"),
164        AAGUID_NAME_APPLE_PASSWORDS,
165    ),
166    (
167        uuid("ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4"),
168        AAGUID_NAME_GOOGLE_PASSWORD_MANAGER,
169    ),
170    (
171        uuid("adce0002-35bc-c60a-648b-0b25f1f05503"),
172        AAGUID_NAME_CHROME_ON_MAC,
173    ),
174    (
175        uuid("08987058-cadc-4b81-b6e1-30de50dcbe96"),
176        AAGUID_NAME_WINDOWS_HELLO_HARDWARE,
177    ),
178    (
179        uuid("9ddd1817-af5a-4672-a2b9-3e3dd95000a9"),
180        AAGUID_NAME_WINDOWS_HELLO_SOFTWARE,
181    ),
182    (
183        uuid("6028b017-b1d4-4c02-b4b3-afcdafc96bb2"),
184        AAGUID_NAME_WINDOWS_HELLO_VBS,
185    ),
186    // --- Password managers (passkey-authenticator-aaguids) ---
187    (
188        uuid("bada5566-a7aa-401f-bd96-45619a55120d"),
189        AAGUID_NAME_ONEPASSWORD,
190    ),
191    (
192        uuid("d548826e-79b4-db40-a3d8-11116f7e8349"),
193        AAGUID_NAME_BITWARDEN,
194    ),
195    (
196        uuid("531126d6-e717-415c-9320-3d9aa6981239"),
197        AAGUID_NAME_DASHLANE,
198    ),
199    (
200        uuid("0ea242b4-43c4-4a1b-8b17-dd6d0b6baec6"),
201        AAGUID_NAME_KEEPER,
202    ),
203    (
204        uuid("fdb141b2-5d84-443e-8a35-4698c205a502"),
205        AAGUID_NAME_KEEPASSXC,
206    ),
207    (
208        uuid("53414d53-554e-4700-0000-000000000000"),
209        AAGUID_NAME_SAMSUNG_PASS,
210    ),
211];
212
213/// Parse the canonical textual UUID form into its 16 big-endian bytes
214/// (the layout used in attested credential data).
215///
216/// Const-friendly: malformed input hits a `const`-context panic, which is
217/// a compile error when invoked from a `const`/`static` initializer — the
218/// only intended use. The Tier A panics/indexing lints are allowed for this
219/// module because the whole function only ever runs at compile time.
220#[allow(clippy::panic, clippy::indexing_slicing)]
221const fn uuid(s: &str) -> [u8; 16] {
222    // hex nibble → value, const-friendly.
223    const fn nibble(c: u8) -> u8 {
224        match c {
225            b'0'..=b'9' => c - b'0',
226            b'a'..=b'f' => c - b'a' + 10,
227            b'A'..=b'F' => c - b'A' + 10,
228            // Reachable only from a malformed constant; the const evaluator
229            // turns this panic into a compile error at the use site.
230            _ => panic!("invalid hex digit in AAGUID constant"),
231        }
232    }
233    let bytes = s.as_bytes();
234    let mut out = [0u8; 16];
235    let mut out_i = 0;
236    let mut i = 0;
237    // Walk the canonical 8-4-4-4-12 form, skipping '-' separators.
238    while i < bytes.len() {
239        let c = bytes[i];
240        if c == b'-' {
241            i += 1;
242            continue;
243        }
244        if out_i >= 16 || i + 1 >= bytes.len() {
245            panic!("AAGUID constant is not a 16-byte UUID");
246        }
247        out[out_i] = (nibble(c) << 4) | nibble(bytes[i + 1]);
248        out_i += 1;
249        i += 2;
250    }
251    if out_i != 16 {
252        panic!("AAGUID constant is not a 16-byte UUID");
253    }
254    out
255}
256
257/// Look up a known authenticator model by its AAGUID.
258///
259/// Returns the human-readable authenticator name for well-known AAGUIDs, or
260/// `None` if the AAGUID is not in this build's registry. An unknown AAGUID is
261/// *not* evidence of a bad registration — the vast majority of authenticators
262/// are not in this deliberately small informational table. Security policy
263/// must be based on attestation verification, not on this lookup.
264///
265/// # Requirements
266/// REQ-WA-131
267pub fn known_aaguid(aaguid: &[u8; 16]) -> Option<&'static str> {
268    KNOWN_AAGUIDS
269        .iter()
270        .find(|(known, _)| known == aaguid)
271        .map(|(_, name)| *name)
272}
273
274// Tests exercise failure paths and invariants directly; unwrap/expect,
275// slicing, and panicking asserts are acceptable here — violations
276// surface as test failures, not production panics.
277#[allow(
278    clippy::unwrap_used,
279    clippy::expect_used,
280    clippy::indexing_slicing,
281    clippy::panic
282)]
283#[cfg(test)]
284mod tests {
285    use super::*;
286
287    /// REQ-WA-131: well-known AAGUIDs resolve to their authenticator names.
288    #[test]
289    fn known_entries_resolve() {
290        let yubikey5 = uuid("cb69481e-8ff7-4039-93ec-0a2729a154a8");
291        assert_eq!(known_aaguid(&yubikey5), Some(AAGUID_NAME_YUBIKEY_5_SERIES));
292
293        let windows_hello = uuid("08987058-cadc-4b81-b6e1-30de50dcbe96");
294        assert_eq!(
295            known_aaguid(&windows_hello),
296            Some(AAGUID_NAME_WINDOWS_HELLO_HARDWARE)
297        );
298
299        let gpm = uuid("ea9b8d66-4d01-1d21-3ce4-b6b48cb575d4");
300        assert_eq!(
301            known_aaguid(&gpm),
302            Some(AAGUID_NAME_GOOGLE_PASSWORD_MANAGER)
303        );
304    }
305
306    /// REQ-WA-131: unknown and all-zero AAGUIDs return `None` (never a name).
307    #[test]
308    fn unknown_aaguid_returns_none() {
309        assert_eq!(known_aaguid(&[0u8; 16]), None);
310        assert_eq!(known_aaguid(&[0xEE; 16]), None);
311    }
312
313    /// The `uuid` const helper must produce the big-endian byte layout used
314    /// in attested credential data (UUID text form → raw bytes).
315    #[test]
316    fn uuid_helper_layout() {
317        // 53414d53-554e-4700-0000-000000000000 is ASCII "SAMSUNG" — a
318        // self-checking vector for byte order.
319        let samsung = uuid("53414d53-554e-4700-0000-000000000000");
320        assert_eq!(&samsung[0..7], b"SAMSUNG");
321        assert_eq!(samsung[7], 0x00);
322        assert_eq!(&samsung[8..], &[0u8; 8]);
323    }
324
325    /// An odd number of hex digits (after dash removal) must panic with
326    /// the crate's message — not an index-out-of-bounds from reading the
327    /// missing second nibble.
328    #[test]
329    #[should_panic(expected = "AAGUID constant is not a 16-byte UUID")]
330    fn uuid_rejects_odd_length_with_named_panic() {
331        let _ = uuid("53414d5");
332    }
333
334    /// Uppercase hex digits must be accepted (case-insensitive UUID text).
335    #[test]
336    fn uuid_accepts_uppercase_hex() {
337        let lower = uuid("cb69481e-8ff7-4039-93ec-0a2729a154a8");
338        let upper = uuid("CB69481E-8FF7-4039-93EC-0A2729A154A8");
339        assert_eq!(lower, upper);
340    }
341
342    /// Non-hex characters hit the const-eval panic (a compile error for
343    /// legitimate const use; a runtime panic otherwise).
344    #[test]
345    #[should_panic(expected = "invalid hex digit")]
346    fn uuid_rejects_non_hex_digit() {
347        let _ = uuid("zz69481e-8ff7-4039-93ec-0a2729a154a8");
348    }
349
350    #[test]
351    #[should_panic(expected = "not a 16-byte UUID")]
352    fn uuid_rejects_overlong_input() {
353        let _ = uuid("cb69481e-8ff7-4039-93ec-0a2729a154a8ff");
354    }
355
356    #[test]
357    #[should_panic(expected = "not a 16-byte UUID")]
358    fn uuid_rejects_empty_input() {
359        let _ = uuid("");
360    }
361}