1#[cfg(doc)]
2use crate::stubs::*;
3
4use crate::{
5 authenticator_hashed::AuthenticatorBackendHashedClientData,
6 crypto::{compute_sha256, get_group},
7 ctap2::commands::{value_to_vec_u8, GetInfoResponse},
8 error::WebauthnCError,
9 BASE64_ENGINE,
10};
11use base64::Engine;
12use openssl::x509::{
13 extension::{AuthorityKeyIdentifier, BasicConstraints, KeyUsage, SubjectKeyIdentifier},
14 X509NameBuilder, X509Ref, X509ReqBuilder, X509,
15};
16use openssl::{asn1, bn, ec, hash, pkey, rand, sign};
17use serde::{Deserialize, Serialize};
18use serde_cbor_2::value::Value;
19use std::collections::HashMap;
20use std::iter;
21use std::{collections::BTreeMap, fs::File, io::Read};
22use std::{
23 collections::BTreeSet,
24 io::{Seek, Write},
25};
26use uuid::Uuid;
27
28use base64urlsafedata::Base64UrlSafeData;
29
30use webauthn_rs_proto::{
31 AllowCredentials, AuthenticationExtensionsClientOutputs, AuthenticatorAssertionResponseRaw,
32 AuthenticatorAttachment, AuthenticatorAttestationResponseRaw, AuthenticatorTransport,
33 PublicKeyCredential, PublicKeyCredentialCreationOptions, PublicKeyCredentialRequestOptions,
34 RegisterPublicKeyCredential, RegistrationExtensionsClientOutputs, UserVerificationPolicy,
35};
36
37pub const AAGUID: Uuid = uuid::uuid!("0fb9bcbc-a0d4-4042-bbb0-559bc1631e28");
38
39#[derive(Serialize, Deserialize)]
40pub struct SoftToken {
41 #[serde(with = "PKeyPrivateDef")]
42 _ca_key: pkey::PKey<pkey::Private>,
43 #[serde(with = "X509Def")]
44 ca_cert: X509,
45 #[serde(with = "PKeyPrivateDef")]
46 intermediate_key: pkey::PKey<pkey::Private>,
47 #[serde(with = "X509Def")]
48 intermediate_cert: X509,
49 tokens: HashMap<Vec<u8>, Vec<u8>>,
50 counter: u32,
51 falsify_uv: bool,
52}
53
54#[derive(Serialize, Deserialize)]
55#[serde(remote = "pkey::PKey<pkey::Private>")]
56struct PKeyPrivateDef {
57 #[serde(getter = "private_key_to_der")]
58 der: Value,
59}
60
61fn private_key_to_der(k: &pkey::PKeyRef<pkey::Private>) -> Value {
62 Value::Bytes(
63 k.private_key_to_der()
64 .expect("Cannot convert private key to DER"),
65 )
66}
67
68impl From<PKeyPrivateDef> for pkey::PKey<pkey::Private> {
69 fn from(def: PKeyPrivateDef) -> Self {
70 let b = value_to_vec_u8(def.der, "der").expect("Cannot deserialise private key");
71 Self::private_key_from_der(&b).expect("Cannot read private key as DER")
72 }
73}
74
75#[derive(Serialize, Deserialize)]
76#[serde(remote = "X509")]
77struct X509Def {
78 #[serde(getter = "x509_to_der")]
79 der: Value,
80}
81
82fn x509_to_der(k: &X509Ref) -> Value {
83 Value::Bytes(k.to_der().expect("Cannot convert certificate to DER"))
84}
85
86impl From<X509Def> for X509 {
87 fn from(def: X509Def) -> Self {
88 let b = value_to_vec_u8(def.der, "der").expect("Cannot deserialise certificate");
89 Self::from_der(&b).expect("Cannot read certificate as DER")
90 }
91}
92
93fn build_ca() -> Result<(pkey::PKey<pkey::Private>, X509), WebauthnCError> {
94 let ecgroup = get_group()?;
95 let eckey = ec::EcKey::generate(&ecgroup)?;
96 let ca_key = pkey::PKey::from_ec_key(eckey)?;
97 let mut x509_name = X509NameBuilder::new()?;
98
99 x509_name.append_entry_by_text("C", "AU")?;
100 x509_name.append_entry_by_text("ST", "QLD")?;
101 x509_name.append_entry_by_text("O", "Webauthn Authenticator RS")?;
102 x509_name.append_entry_by_text("CN", "Dynamic Softtoken CA")?;
103 let x509_name = x509_name.build();
104
105 let mut cert_builder = X509::builder()?;
106 cert_builder.set_version(2)?;
108
109 let serial_number = bn::BigNum::from_u32(1).and_then(|serial| serial.to_asn1_integer())?;
110
111 cert_builder.set_serial_number(&serial_number)?;
112 cert_builder.set_subject_name(&x509_name)?;
113 cert_builder.set_issuer_name(&x509_name)?;
114
115 let not_before = asn1::Asn1Time::days_from_now(0)?;
116 cert_builder.set_not_before(¬_before)?;
117 let not_after = asn1::Asn1Time::days_from_now(1)?;
118 cert_builder.set_not_after(¬_after)?;
119
120 cert_builder.append_extension(BasicConstraints::new().critical().ca().build()?)?;
121 cert_builder.append_extension(
122 KeyUsage::new()
123 .critical()
124 .key_cert_sign()
125 .crl_sign()
126 .build()?,
127 )?;
128
129 let subject_key_identifier =
130 SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(None, None))?;
131 cert_builder.append_extension(subject_key_identifier)?;
132
133 cert_builder.set_pubkey(&ca_key)?;
134
135 cert_builder.sign(&ca_key, hash::MessageDigest::sha256())?;
136 let ca_cert = cert_builder.build();
137
138 Ok((ca_key, ca_cert))
139}
140
141fn build_intermediate(
142 ca_key: &pkey::PKeyRef<pkey::Private>,
143 ca_cert: &X509Ref,
144) -> Result<(pkey::PKey<pkey::Private>, X509), WebauthnCError> {
145 let ecgroup = get_group()?;
146 let eckey = ec::EcKey::generate(&ecgroup)?;
147 let int_key = pkey::PKey::from_ec_key(eckey)?;
148
149 let mut req_builder = X509ReqBuilder::new()?;
151 req_builder.set_pubkey(&int_key)?;
152
153 let mut x509_name = X509NameBuilder::new()?;
154 x509_name.append_entry_by_text("C", "AU")?;
155 x509_name.append_entry_by_text("ST", "QLD")?;
156 x509_name.append_entry_by_text("O", "Webauthn Authenticator RS")?;
157 x509_name.append_entry_by_text("CN", "Dynamic Softtoken Leaf Certificate")?;
158 x509_name.append_entry_by_text("OU", "Authenticator Attestation")?;
160 let x509_name = x509_name.build();
161
162 req_builder.set_subject_name(&x509_name)?;
163 req_builder.sign(&int_key, hash::MessageDigest::sha256())?;
164 let req = req_builder.build();
165 let mut cert_builder = X509::builder()?;
168 cert_builder.set_version(2)?;
170 let serial_number = bn::BigNum::from_u32(2).and_then(|serial| serial.to_asn1_integer())?;
171
172 cert_builder.set_pubkey(&int_key)?;
173
174 cert_builder.set_serial_number(&serial_number)?;
175 cert_builder.set_subject_name(req.subject_name())?;
176 cert_builder.set_issuer_name(ca_cert.subject_name())?;
177
178 let not_before = asn1::Asn1Time::days_from_now(0)?;
179 cert_builder.set_not_before(¬_before)?;
180 let not_after = asn1::Asn1Time::days_from_now(1)?;
181 cert_builder.set_not_after(¬_after)?;
182
183 cert_builder.append_extension(BasicConstraints::new().build()?)?;
184
185 cert_builder.append_extension(
186 KeyUsage::new()
187 .critical()
188 .non_repudiation()
189 .digital_signature()
190 .key_encipherment()
191 .build()?,
192 )?;
193
194 let subject_key_identifier =
195 SubjectKeyIdentifier::new().build(&cert_builder.x509v3_context(Some(ca_cert), None))?;
196 cert_builder.append_extension(subject_key_identifier)?;
197
198 let auth_key_identifier = AuthorityKeyIdentifier::new()
199 .keyid(false)
200 .issuer(false)
201 .build(&cert_builder.x509v3_context(Some(ca_cert), None))?;
202 cert_builder.append_extension(auth_key_identifier)?;
203
204 cert_builder.sign(ca_key, hash::MessageDigest::sha256())?;
213 let int_cert = cert_builder.build();
214
215 Ok((int_key, int_cert))
216}
217
218impl SoftToken {
219 pub fn new(falsify_uv: bool) -> Result<(Self, X509), WebauthnCError> {
220 let (ca_key, ca_cert) = build_ca()?;
221
222 let ca = ca_cert.clone();
223 let (intermediate_key, intermediate_cert) = build_intermediate(&ca_key, &ca_cert)?;
235
236 Ok((
248 SoftToken {
249 _ca_key: ca_key,
251 ca_cert,
252 intermediate_key,
253 intermediate_cert,
254 tokens: HashMap::new(),
255 counter: 0,
256 falsify_uv,
257 },
258 ca,
259 ))
260 }
261
262 pub fn get_info(&self) -> GetInfoResponse {
263 GetInfoResponse {
264 versions: BTreeSet::from(["FIDO_2_0".to_string()]),
265 aaguid: Some(AAGUID),
266 transports: Some(vec!["internal".to_string()]),
267 ..Default::default()
268 }
269 }
270
271 pub fn to_cbor(&self) -> Result<Vec<u8>, WebauthnCError> {
272 serde_cbor_2::ser::to_vec(self).map_err(|e| {
273 error!("SoftToken.to_cbor: {:?}", e);
274 WebauthnCError::Cbor
275 })
276 }
277
278 pub fn from_cbor(v: &[u8]) -> Result<Self, WebauthnCError> {
279 serde_cbor_2::from_slice(v).map_err(|e| {
280 error!("SoftToken::from_cbor: {:?}", e);
281 WebauthnCError::Cbor
282 })
283 }
284}
285
286#[derive(Debug)]
287pub struct U2FSignData {
288 key_handle: Vec<u8>,
289 counter: u32,
290 signature: Vec<u8>,
291 flags: u8,
292}
293
294impl AuthenticatorBackendHashedClientData for SoftToken {
295 fn perform_register(
296 &mut self,
297 client_data_json_hash: Vec<u8>,
298 options: PublicKeyCredentialCreationOptions,
299 _timeout_ms: u32,
300 ) -> Result<RegisterPublicKeyCredential, WebauthnCError> {
301 let cred_types_and_pub_key_algs: Vec<_> = options
309 .pub_key_cred_params
310 .iter()
311 .filter_map(|param| {
312 if param.type_ != "public-key" {
313 None
314 } else {
315 Some((param.type_.clone(), param.alg))
316 }
317 })
318 .collect();
319
320 trace!("Found -> {:x?}", cred_types_and_pub_key_algs);
321
322 if cred_types_and_pub_key_algs.is_empty() {
324 return Err(WebauthnCError::NotSupported);
325 }
326
327 let (platform_attached, resident_key, user_verification) =
412 match &options.authenticator_selection {
413 Some(auth_sel) => {
414 let pa = auth_sel
415 .authenticator_attachment
416 .as_ref()
417 .map(|v| v == &AuthenticatorAttachment::Platform)
418 .unwrap_or(false);
419 let uv = auth_sel.user_verification == UserVerificationPolicy::Required;
420 (pa, auth_sel.require_resident_key, uv)
421 }
422 None => (false, false, false),
423 };
424
425 let rp_id_hash = compute_sha256(options.rp.id.as_bytes()).to_vec();
426
427 if user_verification && !self.falsify_uv {
430 error!("User Verification not supported by softtoken");
431 return Err(WebauthnCError::NotSupported);
432 }
433
434 if platform_attached {
435 error!("Platform Attachement not supported by softtoken");
436 return Err(WebauthnCError::NotSupported);
437 }
438
439 if resident_key {
440 error!("Resident Keys not supported by softtoken");
441 return Err(WebauthnCError::NotSupported);
443 }
444
445 let mut key_handle: Vec<u8> = Vec::with_capacity(32);
447 key_handle.resize_with(32, Default::default);
448 rand::rand_bytes(key_handle.as_mut_slice())?;
449
450 let ecgroup = get_group()?;
452
453 let eckey = ec::EcKey::generate(&ecgroup)?;
454
455 let ecpub_points = eckey.public_key();
457
458 let mut bnctx = bn::BigNumContext::new()?;
459
460 let mut xbn = bn::BigNum::new()?;
461
462 let mut ybn = bn::BigNum::new()?;
463
464 ecpub_points.affine_coordinates_gfp(&ecgroup, &mut xbn, &mut ybn, &mut bnctx)?;
465
466 let mut public_key_x = Vec::with_capacity(32);
467 let mut public_key_y = Vec::with_capacity(32);
468
469 public_key_x.resize(32, 0);
470 public_key_y.resize(32, 0);
471
472 let xbnv = xbn.to_vec();
473 let ybnv = ybn.to_vec();
474
475 let (_pad, x_fill) = public_key_x.split_at_mut(32 - xbnv.len());
476 x_fill.copy_from_slice(&xbnv);
477
478 let (_pad, y_fill) = public_key_y.split_at_mut(32 - ybnv.len());
479 y_fill.copy_from_slice(&ybnv);
480
481 let ecpriv_der = eckey.private_key_to_der()?;
483
484 let mut map = BTreeMap::new();
491 map.insert(Value::Integer(1), Value::Integer(2));
493 map.insert(Value::Integer(3), Value::Integer(-7));
495
496 map.insert(Value::Integer(-1), Value::Integer(1));
498 map.insert(Value::Integer(-2), Value::Bytes(public_key_x));
500 map.insert(Value::Integer(-3), Value::Bytes(public_key_y));
502
503 let pk_cbor = Value::Map(map);
504 let pk_cbor_bytes = serde_cbor_2::to_vec(&pk_cbor).map_err(|e| {
505 error!("PK CBOR -> {:x?}", e);
506 WebauthnCError::Cbor
507 })?;
508
509 let key_handle_len: u16 = u16::try_from(key_handle.len()).map_err(|e| {
510 error!("CBOR kh len is not u16 -> {:x?}", e);
511 WebauthnCError::Cbor
512 })?;
513
514 let khlen_be_bytes = key_handle_len.to_be_bytes();
516 let acd_iter = AAGUID
517 .as_bytes()
518 .iter()
519 .chain(khlen_be_bytes.iter())
520 .copied()
521 .chain(key_handle.iter().copied())
522 .chain(pk_cbor_bytes.iter().copied());
523
524 let flags = if user_verification {
528 0b01000101
529 } else {
530 0b01000001
531 };
532
533 let authdata: Vec<u8> = rp_id_hash
534 .iter()
535 .copied()
536 .chain(iter::once(flags))
537 .chain(
538 iter::repeat(0).take(4),
540 )
541 .chain(acd_iter)
542 .collect();
543
544 let verification_data: Vec<u8> = authdata
547 .iter()
548 .chain(client_data_json_hash.iter())
549 .copied()
550 .collect();
551
552 let mut signer = sign::Signer::new(hash::MessageDigest::sha256(), &self.intermediate_key)?;
555
556 let signature = signer
558 .update(verification_data.as_slice())
559 .and_then(|_| signer.sign_to_vec())?;
560
561 let mut attest_map = BTreeMap::new();
562
563 attest_map.insert(
564 Value::Text("fmt".to_string()),
565 Value::Text("packed".to_string()),
566 );
567 let mut att_stmt_map = BTreeMap::new();
568 att_stmt_map.insert(Value::Text("alg".to_string()), Value::Integer(-7));
569
570 let x509_bytes = Value::Bytes(self.intermediate_cert.to_der()?);
571
572 att_stmt_map.insert(
573 Value::Text("x5c".to_string()),
574 Value::Array(vec![x509_bytes]),
575 );
576 att_stmt_map.insert(Value::Text("sig".to_string()), Value::Bytes(signature));
577
578 attest_map.insert(Value::Text("attStmt".to_string()), Value::Map(att_stmt_map));
579 attest_map.insert(Value::Text("authData".to_string()), Value::Bytes(authdata));
580
581 let ao = Value::Map(attest_map);
582
583 let ao_bytes = serde_cbor_2::to_vec(&ao).map_err(|e| {
584 error!("AO CBOR -> {:x?}", e);
585 WebauthnCError::Cbor
586 })?;
587
588 self.tokens.insert(key_handle.clone(), ecpriv_der);
592
593 let rego = RegisterPublicKeyCredential {
594 id: BASE64_ENGINE.encode(&key_handle),
595 raw_id: key_handle.into(),
596 response: AuthenticatorAttestationResponseRaw {
597 attestation_object: ao_bytes.into(),
598 client_data_json: Base64UrlSafeData::new(),
599 transports: Some(vec![AuthenticatorTransport::Internal]),
600 },
601 type_: "public-key".to_string(),
602 extensions: RegistrationExtensionsClientOutputs::default(),
603 };
604
605 trace!("rego -> {:x?}", rego);
606 Ok(rego)
607 }
608
609 fn perform_auth(
610 &mut self,
611 client_data_json_hash: Vec<u8>,
612 options: PublicKeyCredentialRequestOptions,
613 timeout_ms: u32,
614 ) -> Result<PublicKeyCredential, WebauthnCError> {
615 let user_verification = options.user_verification == UserVerificationPolicy::Required;
623
624 let rp_id_hash = compute_sha256(options.rp_id.as_bytes()).to_vec();
625
626 let u2sd = self.perform_u2f_sign(
627 rp_id_hash.clone(),
628 client_data_json_hash,
629 timeout_ms.into(),
630 options.allow_credentials.as_slice(),
631 user_verification && self.falsify_uv,
632 )?;
633
634 trace!("u2sd -> {:x?}", u2sd);
635 let authdata: Vec<u8> = rp_id_hash
640 .iter()
641 .copied()
642 .chain(iter::once(u2sd.flags))
643 .chain(
644 u2sd.counter.to_be_bytes().iter().copied(),
646 )
647 .collect();
648
649 Ok(PublicKeyCredential {
650 id: BASE64_ENGINE.encode(&u2sd.key_handle),
651 raw_id: u2sd.key_handle.into(),
652 response: AuthenticatorAssertionResponseRaw {
653 authenticator_data: authdata.into(),
654 client_data_json: Base64UrlSafeData::new(),
655 signature: u2sd.signature.into(),
656 user_handle: None,
657 },
658 type_: "public-key".to_string(),
659 extensions: AuthenticationExtensionsClientOutputs::default(),
660 })
661 }
662}
663
664pub trait U2FToken {
665 fn perform_u2f_sign(
666 &mut self,
667 app_bytes: Vec<u8>,
669 chal_bytes: Vec<u8>,
671 timeout_ms: u64,
673 allowed_credentials: &[AllowCredentials],
675 user_verification: bool,
676 ) -> Result<U2FSignData, WebauthnCError>;
677}
678
679impl U2FToken for SoftToken {
680 fn perform_u2f_sign(
681 &mut self,
682 app_bytes: Vec<u8>,
684 chal_bytes: Vec<u8>,
686 _timeout_ms: u64,
688 allowed_credentials: &[AllowCredentials],
690 user_verification: bool,
691 ) -> Result<U2FSignData, WebauthnCError> {
692 if user_verification && !self.falsify_uv {
693 error!("User Verification not supported by softtoken");
694 return Err(WebauthnCError::NotSupported);
695 }
696
697 let cred = allowed_credentials
698 .iter()
699 .filter_map(|ac| {
700 self.tokens
701 .get(ac.id.as_ref())
702 .map(|v| (ac.id.clone().into(), v.clone()))
703 })
704 .take(1)
705 .next();
706
707 let (key_handle, pkder) = if let Some((key_handle, pkder)) = cred {
708 (key_handle, pkder)
709 } else {
710 error!("Credential ID not found");
711 return Err(WebauthnCError::Internal);
712 };
713
714 debug!("Using -> {:?}", key_handle);
715
716 let eckey = ec::EcKey::private_key_from_der(pkder.as_slice())?;
717
718 let pkey = pkey::PKey::from_ec_key(eckey)?;
719
720 let mut signer = sign::Signer::new(hash::MessageDigest::sha256(), &pkey)?;
721
722 self.counter += 1;
724 let counter = self.counter;
725
726 let flags = if user_verification {
727 0b00000101
728 } else {
729 0b00000001
730 };
731
732 let verification_data: Vec<u8> = app_bytes
733 .iter()
734 .chain(iter::once(&flags))
735 .chain(counter.to_be_bytes().iter())
736 .chain(chal_bytes.iter())
737 .copied()
738 .collect();
739
740 trace!("Signing: {:?}", verification_data.as_slice());
741 let signature = signer
742 .update(verification_data.as_slice())
743 .and_then(|_| signer.sign_to_vec())?;
744
745 Ok(U2FSignData {
746 key_handle,
747 counter,
748 signature,
749 flags,
750 })
751 }
752}
753
754pub struct SoftTokenFile {
767 token: SoftToken,
768 file: File,
769}
770
771impl SoftTokenFile {
772 pub fn new(token: SoftToken, file: File) -> Self {
774 Self { token, file }
775 }
776
777 pub fn open(mut file: File) -> Result<Self, WebauthnCError> {
779 let mut buf = Vec::new();
780 file.read_to_end(&mut buf)?;
781
782 let token: SoftToken = serde_cbor_2::from_slice(&buf).map_err(|e| {
783 error!("Error reading SoftToken: {:?}", e);
784 WebauthnCError::Cbor
785 })?;
786
787 Ok(Self { token, file })
788 }
789
790 fn save(&mut self) -> Result<(), WebauthnCError> {
792 trace!("Saving SoftToken to {:?}", self.file);
793 let d = self.token.to_cbor()?;
794 self.file.set_len(0)?;
795 self.file.rewind()?;
796 self.file.write_all(&d)?;
797 self.file.flush()?;
798 Ok(())
799 }
800}
801
802impl TryFrom<SoftTokenFile> for File {
805 type Error = WebauthnCError;
806 fn try_from(value: SoftTokenFile) -> Result<Self, Self::Error> {
807 Ok(value.file.try_clone()?)
808 }
809}
810
811impl AsRef<SoftToken> for SoftTokenFile {
812 fn as_ref(&self) -> &SoftToken {
813 &self.token
814 }
815}
816
817impl Drop for SoftTokenFile {
819 fn drop(&mut self) {
820 self.save().unwrap_or_else(|e| {
821 error!("Error saving SoftToken: {:?}", e);
822 });
823 }
824}
825
826impl AuthenticatorBackendHashedClientData for SoftTokenFile {
827 fn perform_register(
828 &mut self,
829 client_data_hash: Vec<u8>,
830 options: PublicKeyCredentialCreationOptions,
831 timeout_ms: u32,
832 ) -> Result<RegisterPublicKeyCredential, WebauthnCError> {
833 self.token
834 .perform_register(client_data_hash, options, timeout_ms)
835 }
836
837 fn perform_auth(
838 &mut self,
839 client_data_hash: Vec<u8>,
840 options: PublicKeyCredentialRequestOptions,
841 timeout_ms: u32,
842 ) -> Result<PublicKeyCredential, WebauthnCError> {
843 self.token
844 .perform_auth(client_data_hash, options, timeout_ms)
845 }
846}
847
848#[cfg(test)]
849#[allow(clippy::panic)]
850mod tests {
851 use super::*;
852 use openssl::{hash::MessageDigest, rand::rand_bytes, sign::Verifier, x509::X509};
853 use std::time::Duration;
854 use tempfile::tempfile;
855 use webauthn_rs_core::{
856 proto::{AttestationCaList, AttestationCaListBuilder, COSEKey},
857 WebauthnCore as Webauthn,
858 };
859 use webauthn_rs_proto::{
860 AllowCredentials, AttestationConveyancePreference, COSEAlgorithm, PubKeyCredParams,
861 RelyingParty, User, UserVerificationPolicy,
862 };
863
864 use crate::{
865 ctap2::{
866 commands::{
867 value_to_vec_u8, GetAssertionRequest, GetAssertionResponse, MakeCredentialRequest,
868 MakeCredentialResponse,
869 },
870 CBORResponse,
871 },
872 perform_auth_with_request, perform_register_with_request,
873 prelude::{Url, WebauthnAuthenticator},
874 softtoken::SoftToken,
875 };
876
877 const AUTHENTICATOR_TIMEOUT: Duration = Duration::from_secs(60);
878
879 #[test]
880 fn webauthn_authenticator_wan_softtoken_direct_attest() {
881 let _ = tracing_subscriber::fmt::try_init();
882 let wan = Webauthn::new_unsafe_experts_only(
883 "https://localhost:8080/auth",
884 "localhost",
885 vec![url::Url::parse("https://localhost:8080").unwrap()],
886 AUTHENTICATOR_TIMEOUT,
887 None,
888 None,
889 );
890
891 let (soft_token, ca_root) = SoftToken::new(true).unwrap();
892
893 let mut wa = WebauthnAuthenticator::new(soft_token);
894
895 let unique_id = [
896 158, 170, 228, 89, 68, 28, 73, 194, 134, 19, 227, 153, 107, 220, 150, 238,
897 ];
898 let name = "william";
899
900 let builder = wan
901 .new_challenge_register_builder(&unique_id, name, name)
902 .unwrap()
903 .attestation(AttestationConveyancePreference::Direct)
904 .user_verification_policy(UserVerificationPolicy::Preferred);
905
906 let (chal, reg_state) = wan.generate_challenge_register(builder).unwrap();
907
908 info!("🍿 challenge -> {:x?}", chal);
909
910 let r = wa
911 .do_registration(Url::parse("https://localhost:8080").unwrap(), chal)
912 .map_err(|e| {
913 error!("Error -> {:x?}", e);
914 e
915 })
916 .expect("Failed to register");
917
918 let mut att_ca_builder = AttestationCaListBuilder::new();
919 att_ca_builder
920 .insert_device_x509(ca_root, AAGUID, "softtoken".to_string(), Default::default())
921 .expect("Failed to build att ca list");
922 let att_ca_list: AttestationCaList = att_ca_builder.build();
923
924 let cred = wan
925 .register_credential(&r, ®_state, Some(&att_ca_list))
926 .unwrap();
927
928 info!("Credential -> {:?}", cred);
929
930 let (chal, auth_state) = wan
931 .new_challenge_authenticate_builder(vec![cred], None)
932 .and_then(|b| wan.generate_challenge_authenticate(b))
933 .unwrap();
934
935 let r = wa
936 .do_authentication(Url::parse("https://localhost:8080").unwrap(), chal)
937 .map_err(|e| {
938 error!("Error -> {:x?}", e);
939 e
940 })
941 .expect("Failed to auth");
942
943 let auth_res = wan
944 .authenticate_credential(&r, &auth_state)
945 .expect("webauth authentication denied");
946 info!("auth_res -> {:x?}", auth_res);
947 }
948
949 #[test]
950 fn softtoken_persistence() {
951 let _ = tracing_subscriber::fmt::try_init();
952 let wan = Webauthn::new_unsafe_experts_only(
953 "https://localhost:8080/auth",
954 "localhost",
955 vec![url::Url::parse("https://localhost:8080").unwrap()],
956 AUTHENTICATOR_TIMEOUT,
957 None,
958 None,
959 );
960
961 let (soft_token, ca_root) = SoftToken::new(true).unwrap();
962 let file = tempfile().unwrap();
963 let soft_token = SoftTokenFile::new(soft_token, file);
964 assert_eq!(soft_token.token.tokens.len(), 0);
965
966 let mut wa = WebauthnAuthenticator::new(soft_token);
967
968 let unique_id = [
969 158, 170, 228, 89, 68, 28, 73, 194, 134, 19, 227, 153, 107, 220, 150, 238,
970 ];
971 let name = "william";
972
973 let builder = wan
974 .new_challenge_register_builder(&unique_id, name, name)
975 .unwrap()
976 .attestation(AttestationConveyancePreference::Direct)
977 .user_verification_policy(UserVerificationPolicy::Preferred);
978
979 let (chal, reg_state) = wan.generate_challenge_register(builder).unwrap();
980
981 info!("🍿 challenge -> {:x?}", chal);
982
983 let r = wa
984 .do_registration(Url::parse("https://localhost:8080").unwrap(), chal)
985 .map_err(|e| {
986 error!("Error -> {:x?}", e);
987 e
988 })
989 .expect("Failed to register");
990
991 let mut att_ca_builder = AttestationCaListBuilder::new();
992 att_ca_builder
993 .insert_device_x509(ca_root, AAGUID, "softtoken".to_string(), Default::default())
994 .expect("Failed to build att ca list");
995 let att_ca_list: AttestationCaList = att_ca_builder.build();
996
997 let cred = wan
998 .register_credential(&r, ®_state, Some(&att_ca_list))
999 .unwrap();
1000
1001 info!("Credential -> {:?}", cred);
1002
1003 assert_eq!(wa.backend.token.tokens.len(), 1);
1004
1005 let mut file: File = wa.backend.try_into().unwrap();
1007 assert!(file.stream_position().unwrap() > 0);
1008
1009 file.rewind().unwrap();
1011
1012 let soft_token = SoftTokenFile::open(file).unwrap();
1013 assert_eq!(soft_token.token.tokens.len(), 1);
1014
1015 let mut wa = WebauthnAuthenticator::new(soft_token);
1016
1017 let (chal, auth_state) = wan
1018 .new_challenge_authenticate_builder(vec![cred], None)
1019 .and_then(|b| wan.generate_challenge_authenticate(b))
1020 .unwrap();
1021
1022 let r = wa
1023 .do_authentication(Url::parse("https://localhost:8080").unwrap(), chal)
1024 .map_err(|e| {
1025 error!("Error -> {:x?}", e);
1026 e
1027 })
1028 .expect("Failed to auth");
1029
1030 let auth_res = wan
1031 .authenticate_credential(&r, &auth_state)
1032 .expect("webauth authentication denied");
1033 info!("auth_res -> {:x?}", auth_res);
1034 }
1035
1036 #[test]
1037 fn perform_register_auth_with_command() {
1038 let _ = tracing_subscriber::fmt::try_init();
1039 let (mut soft_token, _) = SoftToken::new(true).unwrap();
1040 let mut client_data_hash = vec![0; 32];
1041 let mut user_id = vec![0; 16];
1042 rand_bytes(&mut client_data_hash).unwrap();
1043 rand_bytes(&mut user_id).unwrap();
1044
1045 let request = MakeCredentialRequest {
1046 client_data_hash: client_data_hash.clone(),
1047 rp: RelyingParty {
1048 name: "example.com".to_string(),
1049 id: "example.com".to_string(),
1050 },
1051 user: User {
1052 id: Base64UrlSafeData::from(user_id),
1053 name: "sampleuser".to_string(),
1054 display_name: "Sample User".to_string(),
1055 },
1056 pub_key_cred_params: vec![
1057 PubKeyCredParams {
1058 type_: "public-key".to_string(),
1059 alg: -7,
1060 },
1061 PubKeyCredParams {
1062 type_: "public-key".to_string(),
1063 alg: -257,
1064 },
1065 ],
1066 exclude_list: vec![],
1067 options: None,
1068 pin_uv_auth_param: None,
1069 pin_uv_auth_proto: None,
1070 enterprise_attest: None,
1071 };
1072
1073 let response = perform_register_with_request(&mut soft_token, request, 10000).unwrap();
1074
1075 let m: Value = serde_cbor_2::from_slice(response.as_slice()).unwrap();
1077 let m = if let Value::Map(m) = m {
1078 m
1079 } else {
1080 panic!("unexpected type")
1081 };
1082 assert!(m.keys().all(|k| matches!(k, Value::Integer(_))));
1083
1084 let response =
1086 <MakeCredentialResponse as CBORResponse>::try_from(response.as_slice()).unwrap();
1087 trace!(?response);
1088
1089 let mut att_stmt = if let Value::Map(m) = response.att_stmt.unwrap() {
1092 m
1093 } else {
1094 panic!("unexpected type");
1095 };
1096 trace!(?att_stmt);
1097 let signature = value_to_vec_u8(
1098 att_stmt.remove(&Value::Text("sig".to_string())).unwrap(),
1099 "att_stmt.sig",
1100 )
1101 .unwrap();
1102
1103 let x5c = if let Value::Array(v) = att_stmt.remove(&Value::Text("x5c".to_string())).unwrap()
1105 {
1106 v
1107 } else {
1108 panic!("Unexpected type");
1109 };
1110 let x5c = value_to_vec_u8(x5c[0].to_owned(), "x5c[0]").unwrap();
1111 let verification_cert = X509::from_der(&x5c).unwrap();
1112 let pubkey = verification_cert.public_key().unwrap();
1113
1114 let mut verification_data =
1116 value_to_vec_u8(response.auth_data.unwrap(), "verification_data").unwrap();
1117 let auth_data_len = verification_data.len();
1118 verification_data.reserve(client_data_hash.len());
1119 verification_data.extend_from_slice(&client_data_hash);
1120
1121 let mut verifier = Verifier::new(MessageDigest::sha256(), &pubkey).unwrap();
1122 assert!(verifier
1123 .verify_oneshot(&signature, &verification_data)
1124 .unwrap());
1125
1126 let cred_id_off = 32 + 1 + 4 + 16;
1128 let cred_id_len = u16::from_be_bytes(
1129 (&verification_data[cred_id_off..cred_id_off + 2])
1130 .try_into()
1131 .unwrap(),
1132 ) as usize;
1133 let cred_id = Base64UrlSafeData::from(
1134 (verification_data[cred_id_off + 2..cred_id_off + 2 + cred_id_len]).to_vec(),
1135 );
1136
1137 let cose_key: Value = serde_cbor_2::from_slice(
1139 &verification_data[cred_id_off + 2 + cred_id_len..auth_data_len],
1140 )
1141 .unwrap();
1142 let cose_key = COSEKey::try_from(&cose_key).unwrap();
1143
1144 rand_bytes(&mut client_data_hash).unwrap();
1145 let request = GetAssertionRequest {
1146 client_data_hash: client_data_hash.clone(),
1147 rp_id: "example.com".to_string(),
1148 allow_list: vec![AllowCredentials {
1149 type_: "public-key".to_string(),
1150 id: cred_id.to_owned(),
1151 transports: None,
1152 }],
1153 options: None,
1154 pin_uv_auth_param: None,
1155 pin_uv_auth_proto: None,
1156 };
1157 trace!(?request);
1158
1159 let response = perform_auth_with_request(&mut soft_token, request, 10000).unwrap();
1160 let response =
1161 <GetAssertionResponse as CBORResponse>::try_from(response.as_slice()).unwrap();
1162 trace!(?response);
1163
1164 assert_eq!(response.credential.unwrap().id, cred_id);
1166
1167 let signature = response.signature.unwrap();
1169 let mut verification_data = response.auth_data.unwrap();
1170 verification_data.reserve(client_data_hash.len());
1171 verification_data.extend_from_slice(&client_data_hash);
1172
1173 assert!(cose_key
1174 .verify_signature(&signature, &verification_data)
1175 .unwrap());
1176 }
1177}