1use crate::SecBytes;
8use crate::status::StatusCode;
9
10use alloc::string::String;
11use alloc::vec::Vec;
12
13use serde::{Deserialize, Serialize};
14
15pub const SOFTWARE_PROVIDER_ID: &[u8; 11] = b"software-v1";
17
18pub const MAX_PROVIDER_ID_LENGTH: usize = 64;
20
21#[derive(Clone, Debug, PartialEq, Eq, Hash)]
23pub struct CredentialKeyProviderId(Vec<u8>);
24
25impl CredentialKeyProviderId {
26 pub fn new(id: &[u8]) -> Self {
27 debug_assert!(
28 id.len() <= MAX_PROVIDER_ID_LENGTH,
29 "provider ID exceeds MAX_PROVIDER_ID_LENGTH ({MAX_PROVIDER_ID_LENGTH} bytes)"
30 );
31 Self(id.to_vec())
32 }
33
34 pub fn software() -> Self {
35 Self(SOFTWARE_PROVIDER_ID.to_vec())
36 }
37
38 pub fn as_bytes(&self) -> &[u8] {
39 &self.0
40 }
41
42 pub fn is_software(&self) -> bool {
43 self.0 == SOFTWARE_PROVIDER_ID.as_slice()
44 }
45}
46
47impl Serialize for CredentialKeyProviderId {
48 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
49 where
50 S: serde::Serializer,
51 {
52 serde_bytes::serialize(&self.0, serializer)
53 }
54}
55
56impl<'de> Deserialize<'de> for CredentialKeyProviderId {
57 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
58 where
59 D: serde::Deserializer<'de>,
60 {
61 let bytes: Vec<u8> = serde_bytes::deserialize(deserializer)?;
62 if bytes.len() > MAX_PROVIDER_ID_LENGTH {
63 return Err(serde::de::Error::custom(alloc::format!(
64 "provider ID exceeds MAX_PROVIDER_ID_LENGTH ({} bytes)",
65 MAX_PROVIDER_ID_LENGTH
66 )));
67 }
68 Ok(Self(bytes))
69 }
70}
71
72#[derive(Clone, Debug, PartialEq, Eq)]
77pub struct CredentialKey {
78 pub provider: CredentialKeyProviderId,
80
81 pub format_version: u16,
83
84 pub material: SecBytes,
86}
87
88impl CredentialKey {
89 pub fn new(provider: CredentialKeyProviderId, format_version: u16, material: SecBytes) -> Self {
90 Self {
91 provider,
92 format_version,
93 material,
94 }
95 }
96
97 pub fn software(material: SecBytes) -> Self {
98 Self {
99 provider: CredentialKeyProviderId::software(),
100 format_version: 1,
101 material,
102 }
103 }
104}
105
106impl Serialize for CredentialKey {
107 fn serialize<S>(&self, serializer: S) -> core::result::Result<S::Ok, S::Error>
108 where
109 S: serde::Serializer,
110 {
111 use serde::ser::SerializeMap;
112 let mut map = serializer.serialize_map(None)?;
113 map.serialize_entry("provider", &self.provider)?;
114 map.serialize_entry("format_version", &self.format_version)?;
115 map.serialize_entry(
116 "material",
117 &serde_bytes::Bytes::new(self.material.as_slice()),
118 )?;
119 map.end()
120 }
121}
122
123impl<'de> Deserialize<'de> for CredentialKey {
124 fn deserialize<D>(deserializer: D) -> core::result::Result<Self, D::Error>
125 where
126 D: serde::Deserializer<'de>,
127 {
128 let value = crate::cbor::Value::deserialize(deserializer)?;
129 let map = match value {
130 crate::cbor::Value::Map(m) => m,
131 _ => return Err(serde::de::Error::custom("expected map")),
132 };
133
134 let mut provider: Option<CredentialKeyProviderId> = None;
135 let mut format_version: Option<u16> = None;
136 let mut material: Option<SecBytes> = None;
137
138 for (k, v) in map {
139 let key_str = match k {
140 crate::cbor::Value::Text(s) => s,
141 _ => continue,
142 };
143 match key_str.as_str() {
144 "provider" => {
145 let p: CredentialKeyProviderId =
146 crate::cbor::from_value(&v).map_err(serde::de::Error::custom)?;
147 provider = Some(p);
148 }
149 "format_version" => {
150 if let crate::cbor::Value::Integer(i) = v {
151 if i < 0 || i > u16::MAX as i128 {
152 return Err(serde::de::Error::custom(
153 "format_version out of range for u16",
154 ));
155 }
156 format_version = Some(i as u16);
157 }
158 }
159 "material" => {
160 if let crate::cbor::Value::Bytes(b) = v {
161 material = Some(SecBytes::from_slice(&b));
162 }
163 }
164 _ => {}
165 }
166 }
167
168 Ok(CredentialKey {
169 provider: provider.ok_or_else(|| serde::de::Error::missing_field("provider"))?,
170 format_version: format_version
171 .ok_or_else(|| serde::de::Error::missing_field("format_version"))?,
172 material: material.ok_or_else(|| serde::de::Error::missing_field("material"))?,
173 })
174 }
175}
176
177#[derive(Debug, PartialEq, Eq)]
179pub struct GeneratedCredentialKey {
180 pub key: CredentialKey,
182
183 pub cose_public_key: Vec<u8>,
185}
186
187#[derive(Debug, Clone, PartialEq, Eq)]
189pub enum CredentialKeyError {
190 UnsupportedAlgorithm,
191 UnsupportedProvider,
192 UnsupportedFormatVersion,
193 InvalidKeyMaterial,
194 KeyNotFound,
195 AuthorizationDenied,
196 Timeout,
197 TransientFailure(String),
198 PermanentFailure(String),
199}
200
201impl From<CredentialKeyError> for StatusCode {
202 fn from(err: CredentialKeyError) -> Self {
203 match err {
204 CredentialKeyError::UnsupportedAlgorithm => StatusCode::UnsupportedAlgorithm,
205 CredentialKeyError::UnsupportedProvider => StatusCode::InvalidCredential,
206 CredentialKeyError::UnsupportedFormatVersion => StatusCode::InvalidCredential,
207 CredentialKeyError::InvalidKeyMaterial => StatusCode::InvalidCredential,
208 CredentialKeyError::KeyNotFound => StatusCode::NoCredentials,
209 CredentialKeyError::AuthorizationDenied => StatusCode::OperationDenied,
210 CredentialKeyError::Timeout => StatusCode::UserActionTimeout,
211 CredentialKeyError::TransientFailure(_) => StatusCode::Other,
212 CredentialKeyError::PermanentFailure(_) => StatusCode::Other,
213 }
214 }
215}
216
217pub trait CredentialKeyProvider {
223 fn provider_id(&self) -> CredentialKeyProviderId;
225
226 fn supports_algorithm(&self, algorithm: i32) -> bool;
228
229 fn generate(
231 &self,
232 algorithm: i32,
233 ) -> core::result::Result<GeneratedCredentialKey, CredentialKeyError>;
234
235 fn sign(
237 &self,
238 key: &CredentialKey,
239 algorithm: i32,
240 message: &[u8],
241 ) -> core::result::Result<Vec<u8>, CredentialKeyError>;
242
243 fn delete(&self, _key: &CredentialKey) -> core::result::Result<(), CredentialKeyError> {
245 Ok(())
246 }
247}
248
249pub struct SoftwareCredentialKeyProvider;
254
255impl CredentialKeyProvider for SoftwareCredentialKeyProvider {
256 fn provider_id(&self) -> CredentialKeyProviderId {
257 CredentialKeyProviderId::software()
258 }
259
260 fn supports_algorithm(&self, algorithm: i32) -> bool {
261 matches!(algorithm, -7 | -8 | -19)
262 }
263
264 fn generate(
265 &self,
266 algorithm: i32,
267 ) -> core::result::Result<GeneratedCredentialKey, CredentialKeyError> {
268 if !self.supports_algorithm(algorithm) {
269 return Err(CredentialKeyError::UnsupportedAlgorithm);
270 }
271
272 match algorithm {
273 -8 | -19 => {
274 let (sk, pk) = soft_fido2_crypto::eddsa::generate_keypair();
275 let key = CredentialKey::software(SecBytes::from_slice(&sk[..]));
276 Ok(GeneratedCredentialKey {
277 key,
278 cose_public_key: pk,
279 })
280 }
281 _ => {
282 let (sk, pk) = soft_fido2_crypto::ecdsa::generate_keypair();
283 let key = CredentialKey::software(SecBytes::from_slice(&sk[..]));
284 Ok(GeneratedCredentialKey {
285 key,
286 cose_public_key: pk,
287 })
288 }
289 }
290 }
291
292 fn sign(
293 &self,
294 key: &CredentialKey,
295 algorithm: i32,
296 message: &[u8],
297 ) -> core::result::Result<Vec<u8>, CredentialKeyError> {
298 if !key.provider.is_software() {
299 return Err(CredentialKeyError::UnsupportedProvider);
300 }
301
302 let key_bytes = key.material.as_slice();
303 if key_bytes.len() != 32 {
304 return Err(CredentialKeyError::InvalidKeyMaterial);
305 }
306
307 let mut arr = [0u8; 32];
308 arr.copy_from_slice(key_bytes);
309 let priv_key = zeroize::Zeroizing::new(arr);
310
311 match algorithm {
312 -8 | -19 => soft_fido2_crypto::eddsa::sign(&priv_key, message)
313 .map_err(|e| CredentialKeyError::TransientFailure(alloc::format!("{:?}", e))),
314 _ => soft_fido2_crypto::ecdsa::sign(&priv_key, message)
315 .map_err(|e| CredentialKeyError::TransientFailure(alloc::format!("{:?}", e))),
316 }
317 }
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn test_software_provider_id() {
326 let id = CredentialKeyProviderId::software();
327 assert!(id.is_software());
328 assert_eq!(id.as_bytes(), SOFTWARE_PROVIDER_ID.as_slice());
329 }
330
331 #[test]
332 fn test_custom_provider_id() {
333 let id = CredentialKeyProviderId::new(b"tpm-v1");
334 assert!(!id.is_software());
335 }
336
337 #[test]
338 fn test_software_generate_es256() {
339 let provider = SoftwareCredentialKeyProvider;
340 let result = provider.generate(-7).unwrap();
341 assert!(result.key.provider.is_software());
342 assert_eq!(result.key.material.as_slice().len(), 32);
343 assert_eq!(result.cose_public_key.len(), 65);
344 assert_eq!(result.cose_public_key[0], 0x04);
345 }
346
347 #[test]
348 fn test_software_generate_eddsa() {
349 let provider = SoftwareCredentialKeyProvider;
350 let result = provider.generate(-8).unwrap();
351 assert!(result.key.provider.is_software());
352 assert_eq!(result.key.material.as_slice().len(), 32);
353 assert_eq!(result.cose_public_key.len(), 32);
354 }
355
356 #[test]
357 fn test_software_sign_and_verify_es256() {
358 let provider = SoftwareCredentialKeyProvider;
359 let generated = provider.generate(-7).unwrap();
360 let message = b"test message";
361 let signature = provider.sign(&generated.key, -7, message).unwrap();
362
363 assert!(
364 soft_fido2_crypto::ecdsa::verify(&generated.cose_public_key, message, &signature)
365 .is_ok()
366 );
367 }
368
369 #[test]
370 fn test_software_sign_and_verify_eddsa() {
371 let provider = SoftwareCredentialKeyProvider;
372 let generated = provider.generate(-8).unwrap();
373 let message = b"test message";
374 let signature = provider.sign(&generated.key, -8, message).unwrap();
375
376 assert!(
377 soft_fido2_crypto::eddsa::verify(&generated.cose_public_key, message, &signature)
378 .is_ok()
379 );
380 }
381
382 #[test]
383 fn test_software_sign_rejects_non_software_key() {
384 let provider = SoftwareCredentialKeyProvider;
385 let key = CredentialKey::new(
386 CredentialKeyProviderId::new(b"other"),
387 1,
388 SecBytes::new(vec![0u8; 32]),
389 );
390 let result = provider.sign(&key, -7, b"test");
391 assert_eq!(result, Err(CredentialKeyError::UnsupportedProvider));
392 }
393
394 #[test]
395 fn test_software_sign_rejects_wrong_length() {
396 let provider = SoftwareCredentialKeyProvider;
397 let key = CredentialKey::software(SecBytes::new(vec![0u8; 16]));
398 let result = provider.sign(&key, -7, b"test");
399 assert_eq!(result, Err(CredentialKeyError::InvalidKeyMaterial));
400 }
401
402 #[test]
403 fn test_software_supports_algorithm() {
404 let provider = SoftwareCredentialKeyProvider;
405 assert!(provider.supports_algorithm(-7));
406 assert!(provider.supports_algorithm(-8));
407 assert!(provider.supports_algorithm(-19));
408 assert!(!provider.supports_algorithm(-257));
409 }
410
411 #[test]
412 fn test_credential_key_error_to_status_code() {
413 assert_eq!(
414 StatusCode::from(CredentialKeyError::UnsupportedAlgorithm),
415 StatusCode::UnsupportedAlgorithm
416 );
417 assert_eq!(
418 StatusCode::from(CredentialKeyError::InvalidKeyMaterial),
419 StatusCode::InvalidCredential
420 );
421 assert_eq!(
422 StatusCode::from(CredentialKeyError::KeyNotFound),
423 StatusCode::NoCredentials
424 );
425 assert_eq!(
426 StatusCode::from(CredentialKeyError::AuthorizationDenied),
427 StatusCode::OperationDenied
428 );
429 }
430
431 #[test]
432 fn test_credential_key_software_constructor() {
433 let key = CredentialKey::software(SecBytes::new(vec![0u8; 32]));
434 assert!(key.provider.is_software());
435 assert_eq!(key.format_version, 1);
436 }
437
438 #[test]
439 fn test_credential_key_serde_roundtrip() {
440 let key = CredentialKey::software(SecBytes::new(vec![42u8; 32]));
441 let mut buf = Vec::new();
442 crate::cbor::into_writer(&key, &mut buf).unwrap();
443 let restored: CredentialKey = crate::cbor::decode(&buf).unwrap();
444 assert_eq!(restored.provider, key.provider);
445 assert_eq!(restored.format_version, key.format_version);
446 assert_eq!(restored.material.as_slice(), key.material.as_slice());
447 }
448
449 #[test]
450 fn test_software_generate_rejects_unsupported_algorithm() {
451 let provider = SoftwareCredentialKeyProvider;
452 let result = provider.generate(-257);
453 assert_eq!(result, Err(CredentialKeyError::UnsupportedAlgorithm));
454 }
455
456 #[test]
457 fn test_provider_id_deserialization_rejects_oversized() {
458 use crate::cbor::Value;
459
460 let oversized = Value::Bytes(vec![0u8; MAX_PROVIDER_ID_LENGTH + 1]);
461 let mut buf = Vec::new();
462 crate::cbor::into_writer(&oversized, &mut buf).unwrap();
463 let result: core::result::Result<CredentialKeyProviderId, _> = crate::cbor::decode(&buf);
464 assert!(result.is_err());
465 }
466
467 #[test]
468 fn test_credential_key_deserialization_rejects_out_of_range_format_version() {
469 use crate::cbor::Value;
470
471 let map = Value::Map(vec![
472 (
473 Value::Text("provider".to_string()),
474 Value::Bytes(b"software-v1".to_vec()),
475 ),
476 (
477 Value::Text("format_version".to_string()),
478 Value::Integer(u16::MAX as i128 + 1),
479 ),
480 (
481 Value::Text("material".to_string()),
482 Value::Bytes(vec![0u8; 32]),
483 ),
484 ]);
485 let mut buf = Vec::new();
486 crate::cbor::into_writer(&map, &mut buf).unwrap();
487 let result: core::result::Result<CredentialKey, _> = crate::cbor::decode(&buf);
488 assert!(result.is_err());
489 }
490
491 #[cfg(test)]
492 mod mock_external_provider {
493 use super::*;
494 use alloc::collections::BTreeMap;
495
496 #[cfg(feature = "std")]
497 use std::sync::Mutex;
498
499 #[cfg(not(feature = "std"))]
500 use spin::Mutex;
501
502 struct MockExternalProvider {
503 #[allow(clippy::type_complexity)]
504 keys: Mutex<BTreeMap<Vec<u8>, (Vec<u8>, Vec<u8>)>>,
505 next_handle: Mutex<u64>,
506 }
507
508 impl MockExternalProvider {
509 fn new() -> Self {
510 Self {
511 keys: Mutex::new(BTreeMap::new()),
512 next_handle: Mutex::new(1),
513 }
514 }
515 }
516
517 impl CredentialKeyProvider for MockExternalProvider {
518 fn provider_id(&self) -> CredentialKeyProviderId {
519 CredentialKeyProviderId::new(b"mock-external-v1")
520 }
521
522 fn supports_algorithm(&self, algorithm: i32) -> bool {
523 algorithm == -7
524 }
525
526 fn generate(
527 &self,
528 algorithm: i32,
529 ) -> core::result::Result<GeneratedCredentialKey, CredentialKeyError> {
530 if algorithm != -7 {
531 return Err(CredentialKeyError::UnsupportedAlgorithm);
532 }
533
534 let (sk, pk) = soft_fido2_crypto::ecdsa::generate_keypair();
535
536 let handle = {
537 let mut h = self.next_handle.lock().unwrap();
538 let val = *h;
539 *h += 1;
540 val
541 };
542
543 let opaque_handle = handle.to_be_bytes().to_vec();
544
545 #[cfg(feature = "std")]
546 self.keys
547 .lock()
548 .unwrap()
549 .insert(opaque_handle.clone(), (sk.to_vec(), pk.clone()));
550
551 #[cfg(not(feature = "std"))]
552 self.keys
553 .lock()
554 .insert(opaque_handle.clone(), (sk.to_vec(), pk.clone()));
555
556 let key =
557 CredentialKey::new(self.provider_id(), 1, SecBytes::from_slice(&opaque_handle));
558
559 Ok(GeneratedCredentialKey {
560 key,
561 cose_public_key: pk,
562 })
563 }
564
565 fn sign(
566 &self,
567 key: &CredentialKey,
568 algorithm: i32,
569 message: &[u8],
570 ) -> core::result::Result<Vec<u8>, CredentialKeyError> {
571 if key.provider != self.provider_id() {
572 return Err(CredentialKeyError::UnsupportedProvider);
573 }
574
575 if key.material.as_slice().len() != 8 {
576 return Err(CredentialKeyError::InvalidKeyMaterial);
577 }
578
579 let handle = key.material.as_slice().to_vec();
580
581 #[cfg(feature = "std")]
582 let keys = self.keys.lock().unwrap();
583 #[cfg(not(feature = "std"))]
584 let keys = self.keys.lock();
585
586 let (sk_bytes, _) = keys.get(&handle).ok_or(CredentialKeyError::KeyNotFound)?;
587
588 if sk_bytes.len() != 32 {
589 return Err(CredentialKeyError::InvalidKeyMaterial);
590 }
591
592 let mut arr = [0u8; 32];
593 arr.copy_from_slice(sk_bytes);
594 let priv_key = zeroize::Zeroizing::new(arr);
595
596 match algorithm {
597 -7 => soft_fido2_crypto::ecdsa::sign(&priv_key, message).map_err(|e| {
598 CredentialKeyError::TransientFailure(alloc::format!("{:?}", e))
599 }),
600 _ => Err(CredentialKeyError::UnsupportedAlgorithm),
601 }
602 }
603 }
604
605 #[test]
606 fn test_mock_provider_generate_returns_opaque_handle() {
607 let provider = MockExternalProvider::new();
608 let generated = provider.generate(-7).unwrap();
609
610 assert!(!generated.key.provider.is_software());
611 assert_eq!(generated.key.provider.as_bytes(), b"mock-external-v1");
612 assert_ne!(generated.key.material.as_slice().len(), 32);
613 assert_eq!(generated.key.material.as_slice().len(), 8);
614 assert_eq!(generated.cose_public_key.len(), 65);
615 }
616
617 #[test]
618 fn test_mock_provider_sign_and_verify() {
619 let provider = MockExternalProvider::new();
620 let generated = provider.generate(-7).unwrap();
621 let message = b"test message for mock provider";
622 let signature = provider.sign(&generated.key, -7, message).unwrap();
623
624 assert!(
625 soft_fido2_crypto::ecdsa::verify(&generated.cose_public_key, message, &signature)
626 .is_ok()
627 );
628 }
629
630 #[test]
631 fn test_mock_provider_rejects_unsupported_algorithm() {
632 let provider = MockExternalProvider::new();
633 let result = provider.generate(-8);
634 assert_eq!(result, Err(CredentialKeyError::UnsupportedAlgorithm));
635 }
636
637 #[test]
638 fn test_mock_provider_rejects_wrong_provider_id() {
639 let provider = MockExternalProvider::new();
640 let key = CredentialKey::new(
641 CredentialKeyProviderId::new(b"wrong-provider"),
642 1,
643 SecBytes::new(vec![0u8; 8]),
644 );
645 let result = provider.sign(&key, -7, b"test");
646 assert_eq!(result, Err(CredentialKeyError::UnsupportedProvider));
647 }
648
649 #[test]
650 fn test_mock_provider_rejects_invalid_handle_length() {
651 let provider = MockExternalProvider::new();
652 let key = CredentialKey::new(provider.provider_id(), 1, SecBytes::new(vec![0u8; 16]));
653 let result = provider.sign(&key, -7, b"test");
654 assert_eq!(result, Err(CredentialKeyError::InvalidKeyMaterial));
655 }
656
657 #[test]
658 fn test_mock_provider_rejects_unknown_handle() {
659 let provider = MockExternalProvider::new();
660 let key = CredentialKey::new(provider.provider_id(), 1, SecBytes::new(vec![0xFFu8; 8]));
661 let result = provider.sign(&key, -7, b"test");
662 assert_eq!(result, Err(CredentialKeyError::KeyNotFound));
663 }
664
665 #[test]
666 fn test_mock_provider_multiple_keys() {
667 let provider = MockExternalProvider::new();
668
669 let gen1 = provider.generate(-7).unwrap();
670 let gen2 = provider.generate(-7).unwrap();
671
672 assert_ne!(gen1.key.material.as_slice(), gen2.key.material.as_slice());
673
674 let msg1 = b"message 1";
675 let msg2 = b"message 2";
676
677 let sig1 = provider.sign(&gen1.key, -7, msg1).unwrap();
678 let sig2 = provider.sign(&gen2.key, -7, msg2).unwrap();
679
680 assert!(soft_fido2_crypto::ecdsa::verify(&gen1.cose_public_key, msg1, &sig1).is_ok());
681 assert!(soft_fido2_crypto::ecdsa::verify(&gen2.cose_public_key, msg2, &sig2).is_ok());
682
683 assert!(soft_fido2_crypto::ecdsa::verify(&gen1.cose_public_key, msg2, &sig1).is_err());
684 }
685
686 #[test]
687 fn test_mock_provider_with_authenticator() {
688 use crate::authenticator::{Authenticator, AuthenticatorConfig};
689 use crate::test_utils::MockCallbacks;
690
691 let provider = MockExternalProvider::new();
692 let config = AuthenticatorConfig::new();
693 let auth = Authenticator::new_with_key_provider(config, MockCallbacks, provider);
694
695 assert_eq!(
696 auth.key_provider().provider_id().as_bytes(),
697 b"mock-external-v1"
698 );
699 assert!(auth.key_provider().supports_algorithm(-7));
700 assert!(!auth.key_provider().supports_algorithm(-8));
701 }
702 }
703}