1use crate::sec_bytes::{SecBytes, SecPinHash};
7
8use alloc::string::{String, ToString};
9use alloc::vec::Vec;
10
11#[cfg(feature = "std")]
12use std::time::{SystemTime, UNIX_EPOCH};
13
14use serde::{Deserialize, Serialize};
15
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
20pub struct RelyingParty {
21 pub id: String,
23
24 #[serde(skip_serializing_if = "Option::is_none")]
26 pub name: Option<String>,
27}
28
29impl RelyingParty {
30 pub fn new(id: String) -> Self {
32 Self { id, name: None }
33 }
34
35 pub fn with_name(id: String, name: String) -> Self {
37 Self {
38 id,
39 name: Some(name),
40 }
41 }
42}
43
44#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "camelCase")]
49pub struct User {
50 #[serde(with = "serde_bytes")]
52 pub id: Vec<u8>,
53
54 #[serde(skip_serializing_if = "Option::is_none")]
56 pub name: Option<String>,
57
58 #[serde(skip_serializing_if = "Option::is_none")]
60 pub display_name: Option<String>,
61}
62
63impl User {
64 pub fn new(id: Vec<u8>) -> Self {
66 Self {
67 id,
68 name: None,
69 display_name: None,
70 }
71 }
72
73 pub fn with_details(id: Vec<u8>, name: String, display_name: String) -> Self {
75 Self {
76 id,
77 name: Some(name),
78 display_name: Some(display_name),
79 }
80 }
81}
82
83#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87pub struct PublicKeyCredentialDescriptor {
88 #[serde(with = "serde_bytes")]
90 pub id: Vec<u8>,
91
92 pub r#type: String,
94
95 #[serde(skip_serializing_if = "Option::is_none")]
97 pub transports: Option<Vec<String>>,
98}
99
100impl PublicKeyCredentialDescriptor {
101 pub fn new(id: Vec<u8>) -> Self {
103 Self {
104 r#type: "public-key".to_string(),
105 id,
106 transports: None,
107 }
108 }
109
110 pub fn with_transports(id: Vec<u8>, transports: Vec<String>) -> Self {
112 Self {
113 r#type: "public-key".to_string(),
114 id,
115 transports: Some(transports),
116 }
117 }
118}
119
120#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
124pub struct PublicKeyCredentialParameters {
125 #[serde(rename = "type")]
127 pub cred_type: String,
128
129 pub alg: i32,
131}
132
133impl PublicKeyCredentialParameters {
134 pub fn es256() -> Self {
136 Self {
137 cred_type: "public-key".to_string(),
138 alg: -7,
139 }
140 }
141
142 pub fn new(cred_type: String, alg: i32) -> Self {
144 Self { cred_type, alg }
145 }
146}
147
148#[derive(Debug, Clone, Copy, PartialEq, Eq)]
152#[repr(i32)]
153pub enum CoseAlgorithm {
154 ES256 = -7,
156 EdDSA = -8,
158 ES384 = -35,
160 ES512 = -36,
162 RS256 = -257,
164}
165
166impl CoseAlgorithm {
167 pub fn to_i32(self) -> i32 {
169 self as i32
170 }
171
172 pub fn from_i32(value: i32) -> Option<Self> {
174 match value {
175 -7 => Some(Self::ES256),
176 -8 => Some(Self::EdDSA),
177 -35 => Some(Self::ES384),
178 -36 => Some(Self::ES512),
179 -257 => Some(Self::RS256),
180 _ => None,
181 }
182 }
183}
184
185#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
189pub struct AuthenticatorOptions {
190 #[serde(skip_serializing_if = "Option::is_none")]
192 pub rk: Option<bool>,
193
194 #[serde(skip_serializing_if = "Option::is_none")]
196 pub up: Option<bool>,
197
198 #[serde(skip_serializing_if = "Option::is_none")]
200 pub uv: Option<bool>,
201}
202
203impl AuthenticatorOptions {
204 pub fn new() -> Self {
206 Self::default()
207 }
208
209 pub fn with_rk(mut self, rk: bool) -> Self {
211 self.rk = Some(rk);
212 self
213 }
214
215 pub fn with_up(mut self, up: bool) -> Self {
217 self.up = Some(up);
218 self
219 }
220
221 pub fn with_uv(mut self, uv: bool) -> Self {
223 self.uv = Some(uv);
224 self
225 }
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
232#[repr(u8)]
233pub enum CredProtect {
234 UserVerificationOptional = 0x01,
236 UserVerificationOptionalWithCredentialIdList = 0x02,
238 UserVerificationRequired = 0x03,
240}
241
242impl CredProtect {
243 pub fn to_u8(self) -> u8 {
245 self as u8
246 }
247
248 pub fn from_u8(value: u8) -> Option<Self> {
250 match value {
251 0x01 => Some(Self::UserVerificationOptional),
252 0x02 => Some(Self::UserVerificationOptionalWithCredentialIdList),
253 0x03 => Some(Self::UserVerificationRequired),
254 _ => None,
255 }
256 }
257}
258
259#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
263pub struct Credential {
264 #[serde(with = "serde_bytes")]
266 pub id: Vec<u8>,
267
268 pub rp_id: String,
270
271 pub created: i64,
273
274 pub rp_name: Option<String>,
276
277 #[serde(with = "serde_bytes")]
279 pub user_id: Vec<u8>,
280
281 pub algorithm: i32,
283
284 pub user_name: Option<String>,
286
287 pub sign_count: u32,
289
290 pub private_key: SecBytes,
297
298 pub cred_protect: u8,
300
301 pub discoverable: bool,
303
304 pub user_display_name: Option<String>,
306}
307
308impl Credential {
309 #[allow(clippy::too_many_arguments)]
311 pub fn new(
312 id: Vec<u8>,
313 rp_id: String,
314 rp_name: Option<String>,
315 user_id: Vec<u8>,
316 user_name: Option<String>,
317 user_display_name: Option<String>,
318 algorithm: i32,
319 private_key: SecBytes,
320 discoverable: bool,
321 ) -> Self {
322 Self {
323 id,
324 rp_id,
325 created: current_timestamp(),
326 rp_name,
327 user_id,
328 algorithm,
329 user_name,
330 sign_count: 0,
331 private_key,
332 cred_protect: CredProtect::UserVerificationOptional.to_u8(),
333 discoverable,
334 user_display_name,
335 }
336 }
337}
338
339#[cfg(feature = "std")]
341fn current_timestamp() -> i64 {
342 SystemTime::now()
343 .duration_since(UNIX_EPOCH)
344 .unwrap_or_default()
345 .as_secs() as i64
346}
347
348#[cfg(not(feature = "std"))]
350fn current_timestamp() -> i64 {
351 0
354}
355
356#[derive(Debug, Clone, Copy, PartialEq, Eq)]
358pub enum AuthenticatorTransport {
359 Usb,
361 Nfc,
363 Ble,
365 Internal,
367}
368
369impl AuthenticatorTransport {
370 pub fn as_str(&self) -> &'static str {
372 match self {
373 Self::Usb => "usb",
374 Self::Nfc => "nfc",
375 Self::Ble => "ble",
376 Self::Internal => "internal",
377 }
378 }
379
380 pub fn parse(s: &str) -> Option<Self> {
382 match s {
383 "usb" => Some(Self::Usb),
384 "nfc" => Some(Self::Nfc),
385 "ble" => Some(Self::Ble),
386 "internal" => Some(Self::Internal),
387 _ => None,
388 }
389 }
390}
391
392pub const MAX_PIN_RETRIES: u8 = 8;
394
395pub const MAX_UV_RETRIES: u8 = 3;
397
398pub const DEFAULT_MIN_PIN_LENGTH: u8 = 4;
400
401#[derive(Debug, Clone, Serialize, Deserialize)]
403pub struct PinState {
404 pub pin_hash: Option<SecPinHash>,
406
407 pub retries: u8,
409
410 #[serde(default = "default_uv_retries")]
415 pub uv_retries: u8,
416
417 pub min_pin_length: u8,
419
420 pub version: u64,
422
423 pub force_pin_change: bool,
425}
426
427fn default_uv_retries() -> u8 {
429 MAX_UV_RETRIES
430}
431
432impl Default for PinState {
433 fn default() -> Self {
434 Self::new()
435 }
436}
437
438impl PinState {
439 pub fn new() -> Self {
441 Self {
442 pin_hash: None,
443 retries: MAX_PIN_RETRIES,
444 uv_retries: MAX_UV_RETRIES,
445 min_pin_length: DEFAULT_MIN_PIN_LENGTH,
446 version: 0,
447 force_pin_change: false,
448 }
449 }
450
451 pub fn is_pin_set(&self) -> bool {
453 self.pin_hash.is_some()
454 }
455
456 pub fn is_blocked(&self) -> bool {
458 self.retries == 0
459 }
460
461 pub fn is_uv_blocked(&self) -> bool {
463 self.uv_retries == 0
464 }
465
466 pub fn increment_version(&mut self) {
468 self.version = self.version.saturating_add(1);
469 }
470}
471
472#[cfg(test)]
473mod tests {
474 use super::*;
475
476 #[test]
477 fn test_relying_party() {
478 let rp = RelyingParty::new("example.com".to_string());
479 assert_eq!(rp.id, "example.com");
480 assert_eq!(rp.name, None);
481
482 let rp = RelyingParty::with_name("example.com".to_string(), "Example".to_string());
483 assert_eq!(rp.name, Some("Example".to_string()));
484 }
485
486 #[test]
487 fn test_user() {
488 let user = User::new(vec![1, 2, 3, 4]);
489 assert_eq!(user.id, vec![1, 2, 3, 4]);
490 assert_eq!(user.name, None);
491
492 let user = User::with_details(
493 vec![1, 2, 3, 4],
494 "john@example.com".to_string(),
495 "John Doe".to_string(),
496 );
497 assert_eq!(user.name, Some("john@example.com".to_string()));
498 assert_eq!(user.display_name, Some("John Doe".to_string()));
499 }
500
501 #[test]
502 fn test_credential_descriptor() {
503 let desc = PublicKeyCredentialDescriptor::new(vec![1, 2, 3]);
504 assert_eq!(desc.r#type, "public-key");
505 assert_eq!(desc.id, vec![1, 2, 3]);
506 assert_eq!(desc.transports, None);
507
508 let desc =
509 PublicKeyCredentialDescriptor::with_transports(vec![1, 2, 3], vec!["usb".to_string()]);
510 assert_eq!(desc.transports, Some(vec!["usb".to_string()]));
511 }
512
513 #[test]
514 fn test_cose_algorithm() {
515 assert_eq!(CoseAlgorithm::ES256.to_i32(), -7);
516 assert_eq!(CoseAlgorithm::from_i32(-7), Some(CoseAlgorithm::ES256));
517 assert_eq!(CoseAlgorithm::from_i32(999), None);
518 }
519
520 #[test]
521 fn test_cred_protect() {
522 assert_eq!(CredProtect::UserVerificationRequired.to_u8(), 0x03);
523 assert_eq!(
524 CredProtect::from_u8(0x03),
525 Some(CredProtect::UserVerificationRequired)
526 );
527 assert_eq!(CredProtect::from_u8(0xFF), None);
528 }
529
530 #[test]
531 fn test_authenticator_options() {
532 let opts = AuthenticatorOptions::new().with_rk(true).with_uv(true);
533 assert_eq!(opts.rk, Some(true));
534 assert_eq!(opts.uv, Some(true));
535 assert_eq!(opts.up, None);
536 }
537
538 #[test]
539 fn test_credential_creation() {
540 let cred = Credential::new(
541 vec![1, 2, 3],
542 "example.com".to_string(),
543 Some("Example".to_string()),
544 vec![4, 5, 6],
545 Some("user@example.com".to_string()),
546 Some("User Name".to_string()),
547 -7,
548 SecBytes::new(vec![0u8; 32]),
549 true,
550 );
551
552 assert_eq!(cred.id, vec![1, 2, 3]);
553 assert_eq!(cred.rp_id, "example.com");
554 assert_eq!(cred.sign_count, 0);
555 assert!(cred.discoverable);
556 }
557
558 #[test]
559 fn test_authenticator_transport() {
560 assert_eq!(AuthenticatorTransport::Usb.as_str(), "usb");
561 assert_eq!(
562 AuthenticatorTransport::parse("usb"),
563 Some(AuthenticatorTransport::Usb)
564 );
565 assert_eq!(AuthenticatorTransport::parse("invalid"), None);
566 }
567
568 #[test]
569 fn test_cbor_serialization() {
570 let rp = RelyingParty::with_name("example.com".to_string(), "Example".to_string());
571 let mut buf = Vec::new();
572 let result = crate::cbor::into_writer(&rp, &mut buf);
573 assert!(result.is_ok());
574 }
575
576 #[test]
577 fn test_pin_state_uv_retries() {
578 let state = PinState::new();
580 assert_eq!(state.uv_retries, MAX_UV_RETRIES);
581 assert!(!state.is_uv_blocked());
582
583 let mut state = PinState::new();
585 state.uv_retries = 0;
586 assert!(state.is_uv_blocked());
587 }
588
589 #[test]
590 fn test_pin_state_cbor_round_trip() {
591 let mut state = PinState::new();
593 state.uv_retries = 1; state.retries = 5;
595 state.version = 42;
596
597 let mut buf = Vec::new();
599 crate::cbor::into_writer(&state, &mut buf).expect("CBOR serialization failed");
600
601 let restored: PinState = crate::cbor::decode(&buf).expect("CBOR deserialization failed");
603
604 assert_eq!(restored.uv_retries, 1, "UV retries should be preserved");
605 assert_eq!(restored.retries, 5);
606 assert_eq!(restored.version, 42);
607 }
608}