1use crate::error::{Error, Result};
6use crate::request::PinUvAuthProtocol;
7use crate::transport::Transport;
8
9use soft_fido2_crypto::pin_protocol;
10use soft_fido2_ctap::SecBytes;
11use soft_fido2_ctap::cbor::{MapBuilder, Value};
12
13use p256::elliptic_curve::sec1::ToEncodedPoint;
14use p256::{PublicKey as P256PublicKey, SecretKey as P256SecretKey};
15use rand::rngs::OsRng;
16use zeroize::Zeroizing;
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum PinProtocol {
21 V1,
23 V2,
25}
26
27impl From<PinProtocol> for PinUvAuthProtocol {
28 fn from(protocol: PinProtocol) -> Self {
29 match protocol {
30 PinProtocol::V1 => PinUvAuthProtocol::V1,
31 PinProtocol::V2 => PinUvAuthProtocol::V2,
32 }
33 }
34}
35
36impl From<PinUvAuthProtocol> for PinProtocol {
37 fn from(protocol: PinUvAuthProtocol) -> Self {
38 match protocol {
39 PinUvAuthProtocol::V1 => PinProtocol::V1,
40 PinUvAuthProtocol::V2 => PinProtocol::V2,
41 }
42 }
43}
44
45pub struct PinUvAuthEncapsulation {
47 protocol: PinProtocol,
48 platform_secret: Option<SecBytes>,
50 platform_public: Option<[u8; 65]>,
52 authenticator_key: Option<P256PublicKey>,
54 shared_secret: Option<SecBytes>,
56 pin_token: Option<SecBytes>,
58}
59
60impl PinUvAuthEncapsulation {
61 pub fn new(transport: &mut Transport, protocol: PinProtocol) -> Result<Self> {
68 let mut encap = Self {
69 protocol,
70 platform_secret: None,
71 platform_public: None,
72 authenticator_key: None,
73 shared_secret: None,
74 pin_token: None,
75 };
76
77 encap.initialize(transport)?;
79
80 Ok(encap)
81 }
82
83 pub fn initialize(&mut self, transport: &mut Transport) -> Result<()> {
87 let platform_secret_key = P256SecretKey::random(&mut OsRng);
89 let platform_public_key = platform_secret_key.public_key();
90 let platform_public_point = platform_public_key.to_encoded_point(false);
91
92 let secret_bytes: [u8; 32] = *platform_secret_key.to_bytes().as_ref();
94 self.platform_secret = Some(SecBytes::from_slice(&secret_bytes));
95
96 let public_bytes = platform_public_point.as_bytes();
97 let mut public_array = [0u8; 65];
98 public_array.copy_from_slice(public_bytes);
99 self.platform_public = Some(public_array);
100
101 let protocol_version = match self.protocol {
103 PinProtocol::V1 => 1u8,
104 PinProtocol::V2 => 2u8,
105 };
106
107 let request_bytes = MapBuilder::new()
108 .insert(1, protocol_version) .map_err(|_| Error::Other)?
110 .insert(2, 0x02u8) .map_err(|_| Error::Other)?
112 .build()
113 .map_err(|_| Error::Other)?;
114
115 let response = transport.send_ctap_command(0x06, &request_bytes, 30000)?;
117
118 if response.is_empty() {
120 return Err(Error::Other);
121 }
122
123 let response_value: Value =
125 soft_fido2_ctap::cbor::decode(&response).map_err(|_| Error::Other)?;
126
127 let authenticator_cose_key = match response_value {
129 Value::Map(map) => map
130 .iter()
131 .find(|(k, _)| matches!(k, Value::Integer(i) if *i == 1.into()))
132 .map(|(_, v)| v.clone())
133 .ok_or(Error::Other)?,
134 _ => return Err(Error::Other),
135 };
136
137 let authenticator_public_key = Self::parse_cose_key(&authenticator_cose_key)?;
139
140 use p256::ecdh::diffie_hellman;
142 let shared_secret = diffie_hellman(
143 platform_secret_key.to_nonzero_scalar(),
144 authenticator_public_key.as_affine(),
145 );
146
147 self.authenticator_key = Some(authenticator_public_key);
149 let shared_secret_bytes = shared_secret.raw_secret_bytes();
150 self.shared_secret = Some(SecBytes::from_slice(shared_secret_bytes.as_slice()));
151
152 Ok(())
153 }
154
155 pub fn get_pin_uv_auth_token_using_pin_with_permissions(
166 &mut self,
167 transport: &mut Transport,
168 pin: &str,
169 permissions: u8,
170 rp_id: Option<&str>,
171 ) -> Result<Vec<u8>> {
172 let shared_secret = self.shared_secret.as_ref().ok_or(Error::Other)?;
173
174 let pin_hash = Zeroizing::new({
176 use sha2::{Digest, Sha256};
177 let hash: [u8; 32] = Sha256::digest(pin.as_bytes()).into();
178 hash
179 });
180
181 let (enc_key, _hmac_key) = self.derive_keys_zeroized(shared_secret.as_slice())?;
183
184 let pin_hash_enc =
185 match self.protocol {
186 PinProtocol::V1 => pin_protocol::v1::encrypt(&enc_key, &pin_hash[..16])
187 .map_err(|_| Error::Other)?,
188 PinProtocol::V2 => pin_protocol::v2::encrypt(&enc_key, &pin_hash[..16])
189 .map_err(|_| Error::Other)?,
190 };
191
192 let platform_key_agreement = self.get_key_agreement_cose()?;
194
195 let protocol_version = match self.protocol {
196 PinProtocol::V1 => 1u8,
197 PinProtocol::V2 => 2u8,
198 };
199
200 let mut builder = MapBuilder::new();
201 builder = builder
202 .insert(1, protocol_version)
203 .map_err(|_| Error::Other)?;
204 builder = builder
205 .insert(2, 0x09u8) .map_err(|_| Error::Other)?;
207 builder = builder
208 .insert(3, &platform_key_agreement)
209 .map_err(|_| Error::Other)?;
210 builder = builder
211 .insert_bytes(6, &pin_hash_enc)
212 .map_err(|_| Error::Other)?;
213 builder = builder.insert(9, permissions).map_err(|_| Error::Other)?;
214
215 if let Some(rp_id_str) = rp_id {
216 builder = builder.insert(10, rp_id_str).map_err(|_| Error::Other)?;
217 }
218
219 let request_bytes = builder.build().map_err(|_| Error::Other)?;
220
221 let response = transport.send_ctap_command(0x06, &request_bytes, 30000)?;
222
223 if response.is_empty() {
224 return Err(Error::Other);
225 }
226
227 let response_value: Value =
228 soft_fido2_ctap::cbor::decode(&response).map_err(|_| Error::Other)?;
229
230 let pin_token_enc = match response_value {
231 Value::Map(map) => map
232 .iter()
233 .find(|(k, _)| matches!(k, Value::Integer(i) if *i == 2.into()))
234 .and_then(|(_, v)| match v {
235 Value::Bytes(b) => Some(b.clone()),
236 _ => None,
237 })
238 .ok_or(Error::Other)?,
239 _ => return Err(Error::Other),
240 };
241
242 let pin_token = Zeroizing::new(match self.protocol {
244 PinProtocol::V1 => {
245 let decrypted = pin_protocol::v1::decrypt(&enc_key, &pin_token_enc)
246 .map_err(|_| Error::Other)?;
247 let mut token = [0u8; 32];
248 token.copy_from_slice(&decrypted[..32]);
249 token
250 }
251 PinProtocol::V2 => {
252 let decrypted = pin_protocol::v2::decrypt(&enc_key, &pin_token_enc)
253 .map_err(|_| Error::Other)?;
254 let mut token = [0u8; 32];
255 token.copy_from_slice(&decrypted[..32]);
256 token
257 }
258 });
259
260 self.pin_token = Some(SecBytes::from_slice(&*pin_token));
262
263 Ok(pin_token.to_vec())
264 }
265
266 pub fn get_pin_uv_auth_token_using_uv_with_permissions(
280 &mut self,
281 transport: &mut Transport,
282 permissions: u8,
283 rp_id: Option<&str>,
284 ) -> Result<Vec<u8>> {
285 let platform_key_agreement = self.get_key_agreement_cose()?;
287
288 let protocol_version = match self.protocol {
290 PinProtocol::V1 => 1u8,
291 PinProtocol::V2 => 2u8,
292 };
293
294 let mut builder = MapBuilder::new();
295 builder = builder
296 .insert(1, protocol_version) .map_err(|_| Error::Other)?;
298 builder = builder
299 .insert(2, 0x06u8) .map_err(|_| Error::Other)?;
301 builder = builder
302 .insert(3, &platform_key_agreement) .map_err(|_| Error::Other)?;
304 builder = builder
305 .insert(9, permissions) .map_err(|_| Error::Other)?;
307
308 if let Some(rp_id_str) = rp_id {
309 builder = builder
310 .insert(10, rp_id_str) .map_err(|_| Error::Other)?;
312 }
313
314 let request_bytes = builder.build().map_err(|_| Error::Other)?;
315
316 let response = transport.send_ctap_command(0x06, &request_bytes, 30000)?;
318
319 if response.is_empty() {
321 return Err(Error::Other);
322 }
323
324 let response_value: Value =
326 soft_fido2_ctap::cbor::decode(&response).map_err(|_| Error::Other)?;
327
328 let pin_token_enc = match response_value {
329 Value::Map(map) => map
330 .iter()
331 .find(|(k, _)| matches!(k, Value::Integer(i) if *i == 2.into()))
332 .and_then(|(_, v)| match v {
333 Value::Bytes(b) => Some(b.clone()),
334 _ => None,
335 })
336 .ok_or(Error::Other)?,
337 _ => return Err(Error::Other),
338 };
339
340 let shared_secret = self.shared_secret.as_ref().ok_or(Error::Other)?;
342 let (enc_key, _hmac_key) = self.derive_keys_zeroized(shared_secret.as_slice())?;
343
344 let pin_token = Zeroizing::new(match self.protocol {
346 PinProtocol::V1 => {
347 let decrypted = pin_protocol::v1::decrypt(&enc_key, &pin_token_enc)
348 .map_err(|_| Error::Other)?;
349 let mut token = [0u8; 32];
350 token.copy_from_slice(&decrypted[..32]);
351 token
352 }
353 PinProtocol::V2 => {
354 let decrypted = pin_protocol::v2::decrypt(&enc_key, &pin_token_enc)
355 .map_err(|_| Error::Other)?;
356 let mut token = [0u8; 32];
357 token.copy_from_slice(&decrypted[..32]);
358 token
359 }
360 });
361
362 self.pin_token = Some(SecBytes::from_slice(&*pin_token));
364
365 Ok(pin_token.to_vec())
366 }
367
368 pub fn authenticate(&self, data: &[u8], pin_token: &[u8]) -> Result<Vec<u8>> {
375 let pin_token_array: &[u8; 32] = pin_token.try_into().map_err(|_| Error::Other)?;
376 let result = match self.protocol {
377 PinProtocol::V1 => pin_protocol::v1::authenticate(pin_token_array, data).to_vec(),
378 PinProtocol::V2 => pin_protocol::v2::authenticate(pin_token_array, data).to_vec(),
379 };
380
381 Ok(result)
382 }
383
384 fn get_key_agreement_cose(&self) -> Result<Value> {
386 let secret_bytes = self.platform_secret.as_ref().ok_or(Error::Other)?;
387 let secret_arr = secret_bytes.to_array::<32>().ok_or(Error::Other)?;
388 let secret_key =
389 P256SecretKey::from_bytes((&*secret_arr).into()).map_err(|_| Error::Other)?;
390 let public_key = secret_key.public_key();
391 let point = public_key.to_encoded_point(false);
392
393 let key_map = vec![
394 (Value::Integer(1.into()), Value::Integer(2.into())), (Value::Integer(3.into()), Value::Integer((-25).into())), (Value::Integer((-1).into()), Value::Integer(1.into())), (
398 Value::Integer((-2).into()),
399 Value::Bytes(point.x().ok_or(Error::Other)?.to_vec()),
400 ), (
402 Value::Integer((-3).into()),
403 Value::Bytes(point.y().ok_or(Error::Other)?.to_vec()),
404 ), ];
406
407 Ok(Value::Map(key_map))
408 }
409
410 #[allow(clippy::type_complexity)]
412 fn derive_keys_zeroized(
413 &self,
414 shared_secret: &[u8],
415 ) -> Result<(Zeroizing<[u8; 32]>, Zeroizing<[u8; 32]>)> {
416 let secret_arr: &[u8; 32] = shared_secret.try_into().map_err(|_| Error::Other)?;
418
419 match self.protocol {
420 PinProtocol::V1 => {
421 let (enc, hmac) = pin_protocol::v1::derive_keys(secret_arr);
422 Ok((enc, hmac))
423 }
424 PinProtocol::V2 => {
425 let enc = pin_protocol::v2::derive_encryption_key(secret_arr);
426 let hmac = pin_protocol::v2::derive_hmac_key(secret_arr);
427 Ok((enc, hmac))
428 }
429 }
430 }
431
432 fn parse_cose_key(cose_key: &Value) -> Result<P256PublicKey> {
434 let map = match cose_key {
435 Value::Map(m) => m,
436 _ => return Err(Error::Other),
437 };
438
439 let x = map
441 .iter()
442 .find(|(k, _)| matches!(k, Value::Integer(i) if *i == (-2).into()))
443 .and_then(|(_, v)| match v {
444 Value::Bytes(b) => Some(b.clone()),
445 _ => None,
446 })
447 .ok_or(Error::Other)?;
448
449 let y = map
450 .iter()
451 .find(|(k, _)| matches!(k, Value::Integer(i) if *i == (-3).into()))
452 .and_then(|(_, v)| match v {
453 Value::Bytes(b) => Some(b.clone()),
454 _ => None,
455 })
456 .ok_or(Error::Other)?;
457
458 let mut uncompressed = vec![0x04];
460 uncompressed.extend_from_slice(&x);
461 uncompressed.extend_from_slice(&y);
462
463 use p256::elliptic_curve::sec1::FromEncodedPoint;
465 let point = p256::EncodedPoint::from_bytes(&uncompressed).map_err(|_| Error::Other)?;
466
467 let public_key: Option<P256PublicKey> = P256PublicKey::from_encoded_point(&point).into();
469 public_key.ok_or(Error::Other)
470 }
471
472 pub fn set_pin(&mut self, transport: &mut Transport, new_pin: &str) -> Result<()> {
490 let shared_secret = self.shared_secret.as_ref().ok_or(Error::Other)?;
491
492 let pin_len = new_pin.chars().count();
494 if !(4..=63).contains(&pin_len) {
495 return Err(Error::InvalidPinLength);
496 }
497
498 let padded_pin = Zeroizing::new({
500 let mut buf = [0u8; 64];
501 let pin_bytes = new_pin.as_bytes();
502 if pin_bytes.len() > 64 {
503 return Err(Error::InvalidPinLength);
504 }
505 buf[..pin_bytes.len()].copy_from_slice(pin_bytes);
506 buf
507 });
508
509 let (enc_key, hmac_key) = self.derive_keys_zeroized(shared_secret.as_slice())?;
511
512 let new_pin_enc = match self.protocol {
514 PinProtocol::V1 => {
515 pin_protocol::v1::encrypt(&enc_key, &*padded_pin).map_err(|_| Error::Other)?
516 }
517 PinProtocol::V2 => {
518 pin_protocol::v2::encrypt(&enc_key, &*padded_pin).map_err(|_| Error::Other)?
519 }
520 };
521
522 let pin_uv_auth_param = match self.protocol {
524 PinProtocol::V1 => pin_protocol::v1::authenticate(&hmac_key, &new_pin_enc).to_vec(),
525 PinProtocol::V2 => pin_protocol::v2::authenticate(&hmac_key, &new_pin_enc).to_vec(),
526 };
527
528 let platform_key_agreement = self.get_key_agreement_cose()?;
530
531 let protocol_version = match self.protocol {
533 PinProtocol::V1 => 1u8,
534 PinProtocol::V2 => 2u8,
535 };
536
537 let request_bytes = MapBuilder::new()
538 .insert(1, protocol_version) .map_err(|_| Error::Other)?
540 .insert(2, 0x03u8) .map_err(|_| Error::Other)?
542 .insert(3, &platform_key_agreement) .map_err(|_| Error::Other)?
544 .insert_bytes(4, &pin_uv_auth_param) .map_err(|_| Error::Other)?
546 .insert_bytes(5, &new_pin_enc) .map_err(|_| Error::Other)?
548 .build()
549 .map_err(|_| Error::Other)?;
550
551 let _response = transport.send_ctap_command(0x06, &request_bytes, 30000)?;
553
554 Ok(())
556 }
557
558 pub fn change_pin(
578 &mut self,
579 transport: &mut Transport,
580 current_pin: &str,
581 new_pin: &str,
582 ) -> Result<()> {
583 let shared_secret = self.shared_secret.as_ref().ok_or(Error::Other)?;
584
585 let pin_len = new_pin.chars().count();
587 if !(4..=63).contains(&pin_len) {
588 return Err(Error::InvalidPinLength);
589 }
590
591 let current_pin_hash = Zeroizing::new({
593 use sha2::{Digest, Sha256};
594 let hash: [u8; 32] = Sha256::digest(current_pin.as_bytes()).into();
595 hash
596 });
597
598 let padded_new_pin = Zeroizing::new({
600 let mut buf = [0u8; 64];
601 let new_pin_bytes = new_pin.as_bytes();
602 if new_pin_bytes.len() > 64 {
603 return Err(Error::InvalidPinLength);
604 }
605 buf[..new_pin_bytes.len()].copy_from_slice(new_pin_bytes);
606 buf
607 });
608
609 let (enc_key, hmac_key) = self.derive_keys_zeroized(shared_secret.as_slice())?;
611
612 let pin_hash_enc = match self.protocol {
614 PinProtocol::V1 => pin_protocol::v1::encrypt(&enc_key, ¤t_pin_hash[..16])
615 .map_err(|_| Error::Other)?,
616 PinProtocol::V2 => pin_protocol::v2::encrypt(&enc_key, ¤t_pin_hash[..16])
617 .map_err(|_| Error::Other)?,
618 };
619
620 let new_pin_enc =
622 match self.protocol {
623 PinProtocol::V1 => pin_protocol::v1::encrypt(&enc_key, &*padded_new_pin)
624 .map_err(|_| Error::Other)?,
625 PinProtocol::V2 => pin_protocol::v2::encrypt(&enc_key, &*padded_new_pin)
626 .map_err(|_| Error::Other)?,
627 };
628
629 let mut verify_data = new_pin_enc.clone();
631 verify_data.extend_from_slice(&pin_hash_enc);
632
633 let pin_uv_auth_param = match self.protocol {
634 PinProtocol::V1 => pin_protocol::v1::authenticate(&hmac_key, &verify_data).to_vec(),
635 PinProtocol::V2 => pin_protocol::v2::authenticate(&hmac_key, &verify_data).to_vec(),
636 };
637
638 let platform_key_agreement = self.get_key_agreement_cose()?;
640
641 let protocol_version = match self.protocol {
643 PinProtocol::V1 => 1u8,
644 PinProtocol::V2 => 2u8,
645 };
646
647 let request_bytes = MapBuilder::new()
648 .insert(1, protocol_version) .map_err(|_| Error::Other)?
650 .insert(2, 0x04u8) .map_err(|_| Error::Other)?
652 .insert(3, &platform_key_agreement) .map_err(|_| Error::Other)?
654 .insert_bytes(4, &pin_uv_auth_param) .map_err(|_| Error::Other)?
656 .insert_bytes(5, &new_pin_enc) .map_err(|_| Error::Other)?
658 .insert_bytes(6, &pin_hash_enc) .map_err(|_| Error::Other)?
660 .build()
661 .map_err(|_| Error::Other)?;
662
663 let _response = transport.send_ctap_command(0x06, &request_bytes, 30000)?;
665
666 Ok(())
668 }
669
670 pub fn get_pin_retries(&self, transport: &mut Transport) -> Result<u8> {
682 let request_bytes = MapBuilder::new()
684 .insert(2, 0x01u8) .map_err(|_| Error::Other)?
686 .build()
687 .map_err(|_| Error::Other)?;
688
689 let response = transport.send_ctap_command(0x06, &request_bytes, 30000)?;
691
692 if response.is_empty() {
693 return Err(Error::Other);
694 }
695
696 let response_value: Value =
698 soft_fido2_ctap::cbor::decode(&response).map_err(|_| Error::Other)?;
699
700 let retries = match response_value {
702 Value::Map(map) => map
703 .iter()
704 .find(|(k, _)| matches!(k, Value::Integer(i) if *i == 3.into()))
705 .and_then(|(_, v)| match v {
706 Value::Integer(i) => {
707 let val: i128 = *i;
708 u8::try_from(val).ok()
709 }
710 _ => None,
711 })
712 .ok_or(Error::Other)?,
713 _ => return Err(Error::Other),
714 };
715
716 Ok(retries)
717 }
718}
719
720#[cfg(test)]
721mod tests {
722 use super::*;
723
724 #[test]
725 fn test_pin_protocol_conversion() {
726 assert_eq!(PinProtocol::from(PinUvAuthProtocol::V1), PinProtocol::V1);
727 assert_eq!(PinProtocol::from(PinUvAuthProtocol::V2), PinProtocol::V2);
728 }
729}