1pub const AAGUID_NAME_YUBIKEY_5_SERIES: &str = "YubiKey 5 Series";
32pub const AAGUID_NAME_YUBIKEY_5_NFC: &str = "YubiKey 5 Series with NFC";
34pub const AAGUID_NAME_YUBIKEY_5C: &str = "YubiKey 5 Series with Lightning";
36pub const AAGUID_NAME_YUBIKEY_5_FIPS: &str = "YubiKey 5 FIPS Series";
38pub const AAGUID_NAME_YUBIKEY_5_FIPS_NFC: &str = "YubiKey 5 FIPS Series with NFC";
40pub const AAGUID_NAME_YUBIKEY_BIO: &str = "YubiKey Bio Series - FIDO Edition";
42pub const AAGUID_NAME_SECURITY_KEY_YUBICO: &str = "Security Key by Yubico";
44pub const AAGUID_NAME_SECURITY_KEY_YUBICO_NFC: &str = "Security Key NFC by Yubico";
46pub const AAGUID_NAME_FEITIAN_EPASS_FIDO2: &str = "Feitian ePass FIDO2 Authenticator";
48pub const AAGUID_NAME_FEITIAN_EPASS_FIDO2_NFC: &str = "Feitian ePass FIDO2-NFC Authenticator";
50pub const AAGUID_NAME_FEITIAN_BIOPASS_PRO: &str = "Feitian BioPass FIDO2 Pro Authenticator";
52pub const AAGUID_NAME_SOLO_SECP256R1: &str = "SoloKeys Solo Secp256R1 FIDO2 Authenticator";
54pub const AAGUID_NAME_SOLO_TAP: &str = "SoloKeys Solo Tap Secp256R1 FIDO2 Authenticator";
56pub const AAGUID_NAME_NITROKEY_3: &str = "Nitrokey 3 AM";
58pub const AAGUID_NAME_GOOGLE_TITAN_V2: &str = "Google Titan Security Key v2";
60pub const AAGUID_NAME_APPLE_ICLOUD_KEYCHAIN: &str = "Apple iCloud Keychain (Managed)";
62pub const AAGUID_NAME_APPLE_PASSWORDS: &str = "Apple Passwords";
64pub const AAGUID_NAME_GOOGLE_PASSWORD_MANAGER: &str = "Google Password Manager";
67pub const AAGUID_NAME_CHROME_ON_MAC: &str = "Chrome on Mac";
69pub const AAGUID_NAME_WINDOWS_HELLO_HARDWARE: &str = "Windows Hello Hardware Authenticator";
71pub const AAGUID_NAME_WINDOWS_HELLO_SOFTWARE: &str = "Windows Hello Software Authenticator";
73pub const AAGUID_NAME_WINDOWS_HELLO_VBS: &str = "Windows Hello VBS Hardware Authenticator";
76pub const AAGUID_NAME_ONEPASSWORD: &str = "1Password";
78pub const AAGUID_NAME_BITWARDEN: &str = "Bitwarden";
80pub const AAGUID_NAME_DASHLANE: &str = "Dashlane";
82pub const AAGUID_NAME_KEEPER: &str = "Keeper";
84pub const AAGUID_NAME_KEEPASSXC: &str = "KeePassXC";
86pub const AAGUID_NAME_SAMSUNG_PASS: &str = "Samsung Pass";
88
89const KNOWN_AAGUIDS: &[([u8; 16], &str)] = &[
92 (
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 (
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 (
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 (
149 uuid("2cd2f727-f6ca-44da-8f48-5c2e5da000a2"),
150 AAGUID_NAME_NITROKEY_3,
151 ),
152 (
154 uuid("42b4fb4a-2866-43b2-9bf7-6c6669c2e5d3"),
155 AAGUID_NAME_GOOGLE_TITAN_V2,
156 ),
157 (
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 (
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#[allow(clippy::panic, clippy::indexing_slicing)]
221const fn uuid(s: &str) -> [u8; 16] {
222 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 _ => 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 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
257pub 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#[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 #[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 #[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 #[test]
316 fn uuid_helper_layout() {
317 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 #[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 #[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 #[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}