auth_framework/protocols/
fido1.rs1use crate::errors::{AuthError, Result};
7use base64::Engine;
8use serde::{Deserialize, Serialize};
9use sha2::{Digest, Sha256};
10use std::collections::HashMap;
11
12const U2F_VERSION: &str = "U2F_V2";
14
15#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct U2fRegistrationRequest {
18 pub app_id: String,
19 pub challenge: String,
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct U2fRegistrationResponse {
25 pub registration_data: Vec<u8>,
26 pub client_data: Vec<u8>,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
31pub struct U2fRegistration {
32 pub key_handle: Vec<u8>,
33 pub public_key: Vec<u8>,
34 pub attestation_cert: Vec<u8>,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct U2fSignRequest {
40 pub app_id: String,
41 pub challenge: String,
42 pub key_handle: Vec<u8>,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize)]
47pub struct U2fSignResponse {
48 pub signature_data: Vec<u8>,
49 pub client_data: Vec<u8>,
50 pub key_handle: Vec<u8>,
51}
52
53#[derive(Debug, Clone, Serialize, Deserialize)]
55pub struct U2fClientData {
56 pub typ: String,
57 pub challenge: String,
58 pub origin: String,
59}
60
61pub struct U2fManager {
63 app_id: String,
64 registrations: HashMap<String, Vec<U2fRegistration>>,
65}
66
67impl U2fManager {
68 pub fn new(app_id: &str) -> Result<Self> {
72 if app_id.is_empty() {
73 return Err(AuthError::validation("App ID cannot be empty"));
74 }
75 Ok(Self {
76 app_id: app_id.to_string(),
77 registrations: HashMap::new(),
78 })
79 }
80
81 pub fn generate_registration_challenge(&self) -> Result<U2fRegistrationRequest> {
83 let challenge = generate_challenge()?;
84 Ok(U2fRegistrationRequest {
85 app_id: self.app_id.clone(),
86 challenge,
87 })
88 }
89
90 pub fn verify_registration(
106 &mut self,
107 user_id: &str,
108 request: &U2fRegistrationRequest,
109 response: &U2fRegistrationResponse,
110 ) -> Result<U2fRegistration> {
111 let client_data: U2fClientData = serde_json::from_slice(&response.client_data)
113 .map_err(|e| AuthError::validation(format!("Invalid client data: {e}")))?;
114
115 if client_data.typ != "navigator.id.finishEnrollment" {
116 return Err(AuthError::validation(
117 "Invalid client data type for registration",
118 ));
119 }
120 if client_data.challenge != request.challenge {
121 return Err(AuthError::validation("Challenge mismatch"));
122 }
123
124 let data = &response.registration_data;
126 if data.len() < 67 {
127 return Err(AuthError::validation("Registration data too short"));
128 }
129 if data[0] != 0x05 {
130 return Err(AuthError::validation(
131 "Invalid reserved byte (expected 0x05)",
132 ));
133 }
134
135 let public_key = data[1..66].to_vec();
136 let key_handle_len = data[66] as usize;
137
138 if data.len() < 67 + key_handle_len {
139 return Err(AuthError::validation("Registration data truncated"));
140 }
141
142 let key_handle = data[67..67 + key_handle_len].to_vec();
143 let attestation_cert = data[67 + key_handle_len..].to_vec();
144
145 let registration = U2fRegistration {
146 key_handle,
147 public_key,
148 attestation_cert,
149 };
150
151 self.registrations
152 .entry(user_id.to_string())
153 .or_default()
154 .push(registration.clone());
155
156 Ok(registration)
157 }
158
159 pub fn generate_sign_challenge(&self, user_id: &str) -> Result<Vec<U2fSignRequest>> {
161 let regs = self
162 .registrations
163 .get(user_id)
164 .ok_or_else(|| AuthError::validation("No registrations found for user"))?;
165
166 let challenge = generate_challenge()?;
167
168 Ok(regs
169 .iter()
170 .map(|reg| U2fSignRequest {
171 app_id: self.app_id.clone(),
172 challenge: challenge.clone(),
173 key_handle: reg.key_handle.clone(),
174 })
175 .collect())
176 }
177
178 pub fn verify_authentication(
185 &self,
186 user_id: &str,
187 request: &U2fSignRequest,
188 response: &U2fSignResponse,
189 ) -> Result<u32> {
190 let client_data: U2fClientData = serde_json::from_slice(&response.client_data)
192 .map_err(|e| AuthError::validation(format!("Invalid client data: {e}")))?;
193
194 if client_data.typ != "navigator.id.getAssertion" {
195 return Err(AuthError::validation(
196 "Invalid client data type for authentication",
197 ));
198 }
199 if client_data.challenge != request.challenge {
200 return Err(AuthError::validation("Challenge mismatch"));
201 }
202
203 let regs = self
205 .registrations
206 .get(user_id)
207 .ok_or_else(|| AuthError::validation("No registrations found"))?;
208
209 let _reg = regs
210 .iter()
211 .find(|r| r.key_handle == response.key_handle)
212 .ok_or_else(|| AuthError::validation("Unknown key handle"))?;
213
214 if response.signature_data.len() < 5 {
216 return Err(AuthError::validation("Signature data too short"));
217 }
218
219 let user_presence = response.signature_data[0];
220 if user_presence & 0x01 == 0 {
221 return Err(AuthError::validation("User presence not asserted"));
222 }
223
224 let counter = u32::from_be_bytes([
225 response.signature_data[1],
226 response.signature_data[2],
227 response.signature_data[3],
228 response.signature_data[4],
229 ]);
230
231 let app_param = self.app_param();
235 let client_data_hash: [u8; 32] = {
236 let mut hasher = Sha256::new();
237 hasher.update(&response.client_data);
238 hasher.finalize().into()
239 };
240
241 let mut signed_data = Vec::with_capacity(69);
242 signed_data.extend_from_slice(&app_param);
243 signed_data.push(user_presence);
244 signed_data.extend_from_slice(&response.signature_data[1..5]);
245 signed_data.extend_from_slice(&client_data_hash);
246
247 let signature = &response.signature_data[5..];
248 let public_key = ring::signature::UnparsedPublicKey::new(
249 &ring::signature::ECDSA_P256_SHA256_ASN1,
250 &_reg.public_key,
251 );
252 public_key
253 .verify(&signed_data, signature)
254 .map_err(|_| AuthError::crypto("ECDSA P-256 signature verification failed"))?;
255
256 Ok(counter)
257 }
258
259 pub fn get_registrations(&self, user_id: &str) -> Option<&Vec<U2fRegistration>> {
261 self.registrations.get(user_id)
262 }
263
264 pub fn remove_registration(&mut self, user_id: &str, key_handle: &[u8]) -> bool {
266 if let Some(regs) = self.registrations.get_mut(user_id) {
267 let before = regs.len();
268 regs.retain(|r| r.key_handle != key_handle);
269 regs.len() < before
270 } else {
271 false
272 }
273 }
274
275 pub fn app_param(&self) -> [u8; 32] {
277 let mut hasher = Sha256::new();
278 hasher.update(self.app_id.as_bytes());
279 hasher.finalize().into()
280 }
281
282 pub fn version(&self) -> &'static str {
284 U2F_VERSION
285 }
286}
287
288fn generate_challenge() -> Result<String> {
290 use ring::rand::{SecureRandom, SystemRandom};
291 let rng = SystemRandom::new();
292 let mut buf = [0u8; 32];
293 rng.fill(&mut buf)
294 .map_err(|_| AuthError::crypto("Failed to generate challenge".to_string()))?;
295 Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(buf))
296}
297
298#[cfg(test)]
299mod tests {
300 use super::*;
301
302 #[test]
303 fn test_u2f_manager_creation() {
304 let mgr = U2fManager::new("https://example.com").unwrap();
305 assert_eq!(mgr.app_id, "https://example.com");
306 assert_eq!(mgr.version(), "U2F_V2");
307 }
308
309 #[test]
310 fn test_empty_app_id_rejected() {
311 assert!(U2fManager::new("").is_err());
312 }
313
314 #[test]
315 fn test_registration_challenge_generation() {
316 let mgr = U2fManager::new("https://example.com").unwrap();
317 let req = mgr.generate_registration_challenge().unwrap();
318 assert_eq!(req.app_id, "https://example.com");
319 assert!(!req.challenge.is_empty());
320 }
321
322 #[test]
323 fn test_challenge_uniqueness() {
324 let mgr = U2fManager::new("https://example.com").unwrap();
325 let c1 = mgr.generate_registration_challenge().unwrap();
326 let c2 = mgr.generate_registration_challenge().unwrap();
327 assert_ne!(c1.challenge, c2.challenge);
328 }
329
330 #[test]
331 fn test_verify_registration_invalid_reserved_byte() {
332 let mut mgr = U2fManager::new("https://example.com").unwrap();
333 let req = mgr.generate_registration_challenge().unwrap();
334 let client_data = serde_json::json!({
335 "typ": "navigator.id.finishEnrollment",
336 "challenge": req.challenge,
337 "origin": "https://example.com"
338 });
339 let response = U2fRegistrationResponse {
340 registration_data: vec![0x04; 100], client_data: serde_json::to_vec(&client_data).unwrap(),
342 };
343 assert!(mgr.verify_registration("user1", &req, &response).is_err());
344 }
345
346 #[test]
347 fn test_verify_registration_valid() {
348 let mut mgr = U2fManager::new("https://example.com").unwrap();
349 let req = mgr.generate_registration_challenge().unwrap();
350
351 let mut reg_data = vec![0x05]; reg_data.extend_from_slice(&[0xAA; 65]); reg_data.push(4); reg_data.extend_from_slice(&[0xBB; 4]); reg_data.extend_from_slice(&[0xCC; 10]); let client_data = serde_json::json!({
359 "typ": "navigator.id.finishEnrollment",
360 "challenge": req.challenge,
361 "origin": "https://example.com"
362 });
363
364 let response = U2fRegistrationResponse {
365 registration_data: reg_data,
366 client_data: serde_json::to_vec(&client_data).unwrap(),
367 };
368
369 let reg = mgr.verify_registration("user1", &req, &response).unwrap();
370 assert_eq!(reg.key_handle, vec![0xBB; 4]);
371 assert_eq!(reg.public_key.len(), 65);
372 assert!(mgr.get_registrations("user1").is_some());
373 }
374
375 #[test]
376 fn test_sign_challenge_no_registrations() {
377 let mgr = U2fManager::new("https://example.com").unwrap();
378 assert!(mgr.generate_sign_challenge("unknown").is_err());
379 }
380
381 #[test]
382 fn test_verify_auth_user_presence() {
383 let mut mgr = U2fManager::new("https://example.com").unwrap();
384 let req = mgr.generate_registration_challenge().unwrap();
385
386 let mut reg_data = vec![0x05];
388 reg_data.extend_from_slice(&[0xAA; 65]);
389 reg_data.push(4);
390 reg_data.extend_from_slice(&[0xBB; 4]);
391 reg_data.extend_from_slice(&[0xCC; 10]);
392
393 let client_data = serde_json::json!({
394 "typ": "navigator.id.finishEnrollment",
395 "challenge": req.challenge,
396 "origin": "https://example.com"
397 });
398 let reg_response = U2fRegistrationResponse {
399 registration_data: reg_data,
400 client_data: serde_json::to_vec(&client_data).unwrap(),
401 };
402 mgr.verify_registration("user1", &req, ®_response)
403 .unwrap();
404
405 let sign_reqs = mgr.generate_sign_challenge("user1").unwrap();
407 let sign_req = &sign_reqs[0];
408
409 let mut sig_data = vec![0x00]; sig_data.extend_from_slice(&[0, 0, 0, 1]); sig_data.extend_from_slice(&[0xFF; 10]); let auth_client_data = serde_json::json!({
415 "typ": "navigator.id.getAssertion",
416 "challenge": sign_req.challenge,
417 "origin": "https://example.com"
418 });
419 let sign_response = U2fSignResponse {
420 signature_data: sig_data,
421 client_data: serde_json::to_vec(&auth_client_data).unwrap(),
422 key_handle: vec![0xBB; 4],
423 };
424
425 assert!(
426 mgr.verify_authentication("user1", sign_req, &sign_response)
427 .is_err()
428 );
429 }
430
431 #[test]
432 fn test_verify_auth_success() {
433 use ring::rand::SystemRandom;
434 use ring::signature::{ECDSA_P256_SHA256_ASN1_SIGNING, EcdsaKeyPair, KeyPair};
435
436 let rng = SystemRandom::new();
437 let pkcs8 = EcdsaKeyPair::generate_pkcs8(&ECDSA_P256_SHA256_ASN1_SIGNING, &rng).unwrap();
438 let key_pair =
439 EcdsaKeyPair::from_pkcs8(&ECDSA_P256_SHA256_ASN1_SIGNING, pkcs8.as_ref(), &rng)
440 .unwrap();
441 let public_key_bytes = key_pair.public_key().as_ref().to_vec(); let mut mgr = U2fManager::new("https://example.com").unwrap();
444 let req = mgr.generate_registration_challenge().unwrap();
445
446 let mut reg_data = vec![0x05];
447 reg_data.extend_from_slice(&public_key_bytes);
448 reg_data.push(4);
449 reg_data.extend_from_slice(&[0xBB; 4]);
450 reg_data.extend_from_slice(&[0xCC; 10]);
451
452 let client_data = serde_json::json!({
453 "typ": "navigator.id.finishEnrollment",
454 "challenge": req.challenge,
455 "origin": "https://example.com"
456 });
457 let reg_resp = U2fRegistrationResponse {
458 registration_data: reg_data,
459 client_data: serde_json::to_vec(&client_data).unwrap(),
460 };
461 mgr.verify_registration("user1", &req, ®_resp).unwrap();
462
463 let sign_reqs = mgr.generate_sign_challenge("user1").unwrap();
464 let sign_req = &sign_reqs[0];
465
466 let app_param = mgr.app_param();
468 let auth_client = serde_json::json!({
469 "typ": "navigator.id.getAssertion",
470 "challenge": sign_req.challenge,
471 "origin": "https://example.com"
472 });
473 let auth_client_bytes = serde_json::to_vec(&auth_client).unwrap();
474 let client_data_hash: [u8; 32] = {
475 let mut hasher = Sha256::new();
476 hasher.update(&auth_client_bytes);
477 hasher.finalize().into()
478 };
479
480 let user_presence: u8 = 0x01;
481 let counter_bytes: [u8; 4] = 5u32.to_be_bytes();
482
483 let mut signed_data = Vec::with_capacity(69);
484 signed_data.extend_from_slice(&app_param);
485 signed_data.push(user_presence);
486 signed_data.extend_from_slice(&counter_bytes);
487 signed_data.extend_from_slice(&client_data_hash);
488
489 let signature = key_pair.sign(&rng, &signed_data).unwrap();
490
491 let mut sig_data = vec![user_presence];
492 sig_data.extend_from_slice(&counter_bytes);
493 sig_data.extend_from_slice(signature.as_ref());
494
495 let sign_resp = U2fSignResponse {
496 signature_data: sig_data,
497 client_data: auth_client_bytes,
498 key_handle: vec![0xBB; 4],
499 };
500
501 let counter = mgr
502 .verify_authentication("user1", sign_req, &sign_resp)
503 .unwrap();
504 assert_eq!(counter, 5);
505 }
506
507 #[test]
508 fn test_remove_registration() {
509 let mut mgr = U2fManager::new("https://example.com").unwrap();
510 let req = mgr.generate_registration_challenge().unwrap();
511
512 let mut reg_data = vec![0x05];
513 reg_data.extend_from_slice(&[0xAA; 65]);
514 reg_data.push(4);
515 reg_data.extend_from_slice(&[0xBB; 4]);
516 reg_data.extend_from_slice(&[0xCC; 10]);
517
518 let client_data = serde_json::json!({
519 "typ": "navigator.id.finishEnrollment",
520 "challenge": req.challenge,
521 "origin": "https://example.com"
522 });
523 let resp = U2fRegistrationResponse {
524 registration_data: reg_data,
525 client_data: serde_json::to_vec(&client_data).unwrap(),
526 };
527 mgr.verify_registration("user1", &req, &resp).unwrap();
528
529 assert!(mgr.remove_registration("user1", &[0xBB; 4]));
530 assert_eq!(mgr.get_registrations("user1").unwrap().len(), 0);
531 }
532
533 #[test]
534 fn test_app_param() {
535 let mgr = U2fManager::new("https://example.com").unwrap();
536 let param = mgr.app_param();
537 assert_eq!(param.len(), 32);
538 let param2 = mgr.app_param();
540 assert_eq!(param, param2);
541 }
542}