1#![cfg_attr(docsrs, feature(doc_cfg))]
5#![deny(warnings)]
6#![warn(unused_extern_crates)]
7#![warn(missing_docs)]
8#![deny(clippy::todo)]
9#![deny(clippy::unimplemented)]
10#![deny(clippy::unwrap_used)]
11#![deny(clippy::panic)]
13#![deny(clippy::unreachable)]
14#![deny(clippy::await_holding_lock)]
15#![deny(clippy::needless_pass_by_value)]
16#![deny(clippy::trivially_copy_pass_by_ref)]
17
18#[macro_use]
19extern crate tracing;
20
21pub mod proto;
22
23use uuid::Uuid;
24pub use webauthn_rs_core::error::WebauthnError;
25use webauthn_rs_core::{
26 attestation::{
27 assert_packed_attest_req, validate_extension, verify_attestation_ca_chain, FidoGenCeAaguid,
28 },
29 crypto::{compute_sha256, verify_signature},
30 internals::AuthenticatorData,
31 proto::{
32 AttestationCaList, AttestationFormat, AttestationMetadata, COSEAlgorithm, COSEKey,
33 COSEKeyType, CredentialProtectionPolicy, ExtnState, ParsedAttestation,
34 ParsedAttestationData, RegisteredExtensions, Registration,
35 },
36};
37
38use nom::{
39 bytes::complete::{tag, take},
40 number::complete::be_u32,
41};
42
43use openssl::{bn, ec, nid, x509};
44
45use sshkeys::{Curve, EcdsaPublicKey, KeyType, KeyTypeKind, PublicKey, PublicKeyKind};
46
47use crate::proto::AttestedPublicKey;
48
49pub fn verify_fido_sk_ssh_attestation(
63 attestation: &[u8],
64 challenge: &[u8],
65 attestation_cas: &AttestationCaList,
66 danger_disable_certificate_time_checks: bool,
67) -> Result<AttestedPublicKey, WebauthnError> {
68 if attestation_cas.is_empty() {
69 return Err(WebauthnError::MissingAttestationCaList);
70 }
71
72 let alg = COSEAlgorithm::ES256;
73
74 let ssh_sk_attest = SshSkAttestation::try_from(attestation)?;
75
76 let acd = ssh_sk_attest
77 .auth_data
78 .acd
79 .as_ref()
80 .ok_or(WebauthnError::MissingAttestationCredentialData)?;
81
82 let attestation_format = AttestationFormat::Packed;
83
84 let client_data_hash = compute_sha256(challenge);
86
87 trace!(?ssh_sk_attest);
88
89 let verification_data: Vec<u8> = ssh_sk_attest
90 .auth_data_bytes
91 .iter()
92 .chain(client_data_hash.iter())
93 .copied()
94 .collect();
95
96 let is_valid_signature = verify_signature(
97 alg,
98 &ssh_sk_attest.att_cert,
99 &ssh_sk_attest.sig,
100 &verification_data,
101 )?;
102
103 if !is_valid_signature {
104 return Err(WebauthnError::AttestationStatementSigInvalid);
105 }
106
107 assert_packed_attest_req(&ssh_sk_attest.att_cert)?;
112
113 validate_extension::<FidoGenCeAaguid>(&ssh_sk_attest.att_cert, &acd.aaguid)?;
118
119 let att_x509 = vec![ssh_sk_attest.att_cert.clone()];
121
122 let attestation = ParsedAttestation {
123 data: ParsedAttestationData::Basic(att_x509),
124 metadata: AttestationMetadata::Packed {
125 aaguid: Uuid::from_bytes(acd.aaguid),
126 },
127 };
128
129 let ca_crt = verify_attestation_ca_chain(
130 &attestation.data,
131 attestation_cas,
132 danger_disable_certificate_time_checks,
133 )?;
134
135 let ca_crt = ca_crt.ok_or(WebauthnError::AttestationNotVerifiable)?;
140
141 match &attestation.metadata {
142 AttestationMetadata::Packed { aaguid } | AttestationMetadata::Tpm { aaguid, .. } => {
143 if !ca_crt.aaguids().contains_key(aaguid) {
145 error!(?aaguid, "aaguid not trusted by this CA");
146 return Err(WebauthnError::AttestationUntrustedAaguid);
147 }
148 }
149 _ => {
150 error!("this attestation format does not contain an aaguid and can not proceed");
151 return Err(WebauthnError::AttestationFormatMissingAaguid);
152 }
153 };
154
155 let cred_protect = match ssh_sk_attest.auth_data.extensions.cred_protect.as_ref() {
156 Some(credprotect) => {
157 if credprotect.0 == CredentialProtectionPolicy::UserVerificationRequired
158 && !ssh_sk_attest.auth_data.user_verified
159 {
160 return Err(WebauthnError::SshPublicKeyInconsistentUserVerification);
161 }
162 ExtnState::Set(credprotect.0)
163 }
164 None => ExtnState::NotRequested,
165 };
166
167 let extensions = RegisteredExtensions {
168 cred_protect,
169 ..Default::default()
170 };
171
172 if ssh_sk_attest.auth_data.backup_eligible || ssh_sk_attest.auth_data.backup_state {
175 error!("Fido ssh sk keys may not be backed up or backup eligible");
176 return Err(WebauthnError::SshPublicKeyBackupState);
177 }
178
179 let ck = COSEKey::try_from(&acd.credential_pk).map_err(|e| {
183 if matches!(e, WebauthnError::COSEKeyEDUnsupported) {
184 WebauthnError::SshPublicKeyEDUnsupported
185 } else {
186 e
187 }
188 })?;
189 trace!(?ck);
190
191 let pubkey = to_ssh_pubkey(&ck)?;
192
193 Ok(AttestedPublicKey {
194 pubkey,
195 extensions,
196 attestation,
197 attestation_format,
198 })
199}
200
201macro_rules! cbor_try_bytes {
202 (
203 $v:expr
204 ) => {{
205 match $v {
206 serde_cbor_2::Value::Bytes(m) => Ok(m),
207 _ => Err(WebauthnError::COSEKeyInvalidCBORValue),
208 }
209 }};
210}
211
212#[derive(Debug)]
213struct SshSkAttestation {
214 att_cert: x509::X509,
215 sig: Vec<u8>,
216 auth_data_bytes: Vec<u8>,
217 auth_data: AuthenticatorData<Registration>,
218}
219
220struct SshSkAttestationRaw<'a> {
221 att_cert_raw: &'a [u8],
223 sig_raw: &'a [u8],
225 auth_data_raw: &'a [u8],
227}
228
229impl TryFrom<&[u8]> for SshSkAttestation {
230 type Error = WebauthnError;
231
232 fn try_from(data: &[u8]) -> Result<SshSkAttestation, WebauthnError> {
233 let sk_raw = parse_ssh_sk_attestation(data)
240 .map_err(|e| {
241 error!(?e, "try_from parse_ssh_sk_attestation");
242 WebauthnError::ParseNOMFailure
243 })
244 .map(|(_, ad)| ad)?;
246
247 let sig = sk_raw.sig_raw.to_vec();
250
251 let att_cert =
252 x509::X509::from_der(sk_raw.att_cert_raw).map_err(WebauthnError::OpenSSLError)?;
253
254 let auth_data_bytes = serde_cbor_2::from_slice(sk_raw.auth_data_raw)
255 .map_err(|e| {
256 error!(?e, "invalid auth data cbor");
257 WebauthnError::ParseNOMFailure
258 })
259 .and_then(|value| cbor_try_bytes!(value))?;
260
261 let auth_data: AuthenticatorData<Registration> =
262 AuthenticatorData::try_from(auth_data_bytes.as_slice()).map_err(|e| {
263 error!(?e, "invalid auth data structure");
264 WebauthnError::ParseNOMFailure
265 })?;
266
267 Ok(SshSkAttestation {
268 att_cert,
269 sig,
270 auth_data_bytes,
272 auth_data,
273 })
274 }
275}
276
277fn parse_ssh_sk_attestation(i: &[u8]) -> nom::IResult<&[u8], SshSkAttestationRaw<'_>> {
278 let (i, _tag_len) = tag([0, 0, 0, 17])(i)?;
281 let (i, _tag) = tag("ssh-sk-attest-v01")(i)?;
282
283 let (i, att_cert_len) = be_u32(i)?;
284 let (i, att_cert_raw) = take(att_cert_len as usize)(i)?;
285
286 let (i, sig_len) = be_u32(i)?;
287 let (i, sig_raw) = take(sig_len as usize)(i)?;
288
289 let (i, auth_data_len) = be_u32(i)?;
290 let (i, auth_data_raw) = take(auth_data_len as usize)(i)?;
291
292 let (i, _resvd_flags) = be_u32(i)?;
293 let (i, _resvd) = be_u32(i)?;
294
295 Ok((
296 i,
297 SshSkAttestationRaw {
298 att_cert_raw,
299 sig_raw,
300 auth_data_raw,
301 },
302 ))
303}
304
305fn to_ssh_pubkey(cose: &COSEKey) -> Result<PublicKey, WebauthnError> {
306 match &cose.key {
307 COSEKeyType::EC_EC2(_ec2k) => {
308 let pubkey = cose.get_openssl_pkey()?;
309 let key = pubkey
310 .ec_key()
311 .and_then(|ec| {
312 let mut ctx = bn::BigNumContext::new()?;
313 let c_nid = nid::Nid::X9_62_PRIME256V1; let group = ec::EcGroup::from_curve_name(c_nid)?;
315
316 ec.public_key().to_bytes(
317 &group,
318 ec::PointConversionForm::UNCOMPRESSED,
319 &mut ctx,
320 )
321 })
322 .map_err(WebauthnError::OpenSSLError)?;
323
324 let kind = PublicKeyKind::Ecdsa(EcdsaPublicKey {
325 curve: Curve::from_identifier("nistp256").map_err(|_| {
326 error!("Invalid curve identifier");
327 WebauthnError::SshPublicKeyInvalidCurve
328 })?,
329 key,
330 sk_application: Some("ssh:".to_string()),
331 });
332
333 Ok(PublicKey {
334 key_type: KeyType {
335 name: "sk-ecdsa-sha2-nistp256@openssh.com",
336 short_name: "ECDSA-SK",
337 is_cert: false,
338 is_sk: true,
339 kind: KeyTypeKind::EcdsaSk,
340 plain: "sk-ecdsa-sha2-nistp256@openssh.com",
341 },
342 kind,
343 comment: None,
344 })
345 }
346 _ => {
347 error!("ed25519 or ed448 public keys are not supported");
348 Err(WebauthnError::SshPublicKeyEDUnsupported)
349 }
350 }
351}
352
353#[cfg(test)]
354mod tests {
355 use super::{verify_fido_sk_ssh_attestation, WebauthnError};
356 use base64::{engine::general_purpose::STANDARD, Engine};
357 use webauthn_rs_core::proto::{
358 AttestationCaList, AttestationCaListBuilder, CredentialProtectionPolicy, ExtnState,
359 };
360 use webauthn_rs_device_catalog::data::yubico::YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM;
361
362 #[test]
363 fn test_ssh_ecdsa_sk_attest() {
364 let _ = tracing_subscriber::fmt::try_init();
365
366 let attest = STANDARD.decode("AAAAEXNzaC1zay1hdHRlc3QtdjAxAAACwTCCAr0wggGloAMCAQICBBisRsAwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG4xCzAJBgNVBAYTAlNFMRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xJzAlBgNVBAMMHll1YmljbyBVMkYgRUUgU2VyaWFsIDQxMzk0MzQ4ODBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHnqOyx8SXAQYiMM0j/rYOUpMXHUg/EAvoWdaw+DlwMBtUbN1G7PyuPj8w+B6e1ivSaNTB69N7O8vpKowq7rTjqjbDBqMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS43MBMGCysGAQQBguUcAgEBBAQDAgUgMCEGCysGAQQBguUcAQEEBBIEEMtpSB6P90A5k+wKJymhVKgwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAl50Dl9hg+C7hXTEceW66+yL6p+CE2bq0xhu7V/PmtMGKSDe4XDxO2+SDQ/TWpdmxztqK4f7UkSkhcwWOXuHL3WvawHVXxqDo02gluhWef7WtjNr4BIaM+Q6PH4rqF8AWtVwqetSXyJT7cddT15uaSEtsN21yO5mNLh1DBr8QM7Wu+Myly7JWi2kkIm0io1irfYfkrF8uCRqnFXnzpWkJSX1y9U4GusHDtEE7ul6vlMO2TzT566Qay2rig3dtNkZTeEj+6IS93fWxuleYVM/9zrrDRAWVJ+Vt1Zj49WZxWr5DAd0ZETDmufDGQDkSU+IpgD867ydL7b/eP8u9QurWeQAAAEYwRAIgeYp6mYVsuaj0NpHps1qkGkJYroyurnuCKdSYWUCCsVgCIAhFdmhNWGG0cY5l3sZUhjmrwCHpuQ1A0QXbhuEtjM7sAAAAxljE4wYQ6KFiEVlg/h7CI+ZSnJ9LboAgDcteXDIcivHisb9FAAALNMtpSB6P90A5k+wKJymhVKgAQPQVE6m4sayalwAfqHVZBGEP32y5ju2Vo7U3k1zPFKQGLDhpA0dRHWvYbsvTPmqVzSGuxSyRW/ugWzPqsveALlSlAQIDJiABIVggQ25tmKStvyG74d5VF1nSmn9UCTaq/gkNu4mG8PTI11YiWCAMvZ7dwFsRGIN40+RbHnxDitWfGRtXV9rwTbBpG1P3XAAAAAAAAAAA")
371 .expect("Failed to decode attestation");
372
373 let challenge = STANDARD
374 .decode("VzCkpMNVYVgXHBuDP74v9A==")
375 .expect("Failed to decode attestation");
376
377 let pubkey = "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBENubZikrb8hu+HeVRdZ0pp/VAk2qv4JDbuJhvD0yNdWDL2e3cBbERiDeNPkWx58Q4rVnxkbV1fa8E2waRtT91wAAAAEc3NoOg== william@hostname";
378 let mut key = sshkeys::PublicKey::from_string(pubkey).unwrap();
379 key.comment = None;
381
382 let mut att_ca_builder = AttestationCaListBuilder::new();
383 att_ca_builder
384 .insert_device_pem(
385 YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM,
386 uuid::uuid!("cb69481e-8ff7-4039-93ec-0a2729a154a8"),
387 "yk 5 nano".to_string(),
388 Default::default(),
389 )
390 .expect("Failed to build att ca list");
391 let att_ca_list: AttestationCaList = att_ca_builder.build();
392
393 let att = verify_fido_sk_ssh_attestation(
395 attest.as_slice(),
396 challenge.as_slice(),
397 &att_ca_list,
398 false,
399 )
400 .expect("Failed to parse attestation");
401
402 trace!("key {:?}", key);
403 trace!("att {:?}", att.pubkey);
404 trace!("att full {:?}", att);
405
406 assert_eq!(att.pubkey, key);
408
409 assert!(matches!(
411 att.extensions.cred_protect,
412 ExtnState::NotRequested
413 ));
414 }
415
416 #[test]
417 fn test_ssh_ecdsa_sk_credprotect_attest() {
418 let _ = tracing_subscriber::fmt::try_init();
419
420 let attest = STANDARD.decode("AAAAEXNzaC1zay1hdHRlc3QtdjAxAAAC8DCCAuwwggHUoAMCAQICCQCIobnFT2wgvjANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbzELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEoMCYGA1UEAwwfWXViaWNvIFUyRiBFRSBTZXJpYWwgMTE2OTc5MzQxNjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABP3N+hZ2qRVyajtVRGx/tdK/YAcNNGY++kDoDODSHk4cAqXSZ7jZepIkLdQXk7JP2dD0gVMpP5WzOJpEv8J6tRejgZQwgZEwEwYKKwYBBAGCxAoNAQQFBAMFBAMwEAYJKwYBBAGCxAoMBAMCAQcwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjcwEwYLKwYBBAGC5RwCAQEEBAMCBSAwIQYLKwYBBAGC5RwBAQQEEgQQc7sM1OUCSbicb7WURb9yCzAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQA8JczOIP9yDzuYizYwoJwGCwmYbUWNuokPu/NOMRLKTHvRfZRhf5LRHX7WjD0kJwifo725l/O7b+G4Y+3w9a1tK00wBGCMxw3F/oGcxsn+Tg6zWQZW3HXN8Qxfb5vtnX7lK5omugUPyq7XBqiBqFi2oqHFxjPjZSYFqQLE1DxDfJVtxXysvG1q/tkTkRagkAaqLb59SitNKsSXJ14Y9aG6liaFpSL8q+BeIe6XBHZ8NGxGhZdnhOu6qzYcTpSXlYHjeUoVF2/crpnQocjl59cgarJgS2aJV/jlSWnyZVhKbq14up6YUg0UsO60+UYm5rKuxS5OvAsvgKbl+71jhxCSAAAASDBGAiEA0tN1SoFM6y25G1MAqiogh9YrxC3xXAjq5PqSLfpEiBMCIQCzlf2HkoQxzw26d/H54qG7usJxGqjI7ar5QTPTmyPiPAAAANRY0uMGEOihYhFZYP4ewiPmUpyfS26AIA3LXlwyHIrx4rG/xQAAAANzuwzU5QJJuJxvtZRFv3ILAEDSkcmqMSeSNIZeeun9OR70HsiBGZv4Z487AIxLcDGlygV+x8o0pcXuQLBt5qkyLgbjHz9AG8VnoG89Xsqc7FDNpQECAyYgASFYIMD4M0oQcZZURp0PhmabT3X+rvYak+JdnMwDTlJ/zBzZIlggrec0hNPMTEy2/BSWTiX/LCtOIuxSUAzRFG07JAwxxTyha2NyZWRQcm90ZWN0AwAAAAAAAAAA")
425 .expect("Failed to decode attestation");
426 let challenge = STANDARD
427 .decode("VzCkpMNVYVgXHBuDP74v9A==")
428 .expect("Failed to decode attestation");
429
430 let pubkey = "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBMD4M0oQcZZURp0PhmabT3X+rvYak+JdnMwDTlJ/zBzZrec0hNPMTEy2/BSWTiX/LCtOIuxSUAzRFG07JAwxxTwAAAAEc3NoOg== william@hostname";
431 let mut key = sshkeys::PublicKey::from_string(pubkey).unwrap();
432 key.comment = None;
434
435 let mut att_ca_builder = AttestationCaListBuilder::new();
436 att_ca_builder
437 .insert_device_pem(
438 YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM,
439 uuid::uuid!("73bb0cd4-e502-49b8-9c6f-b59445bf720b"),
440 "yk 5 fips".to_string(),
441 Default::default(),
442 )
443 .expect("Failed to build att ca list");
444 let att_ca_list: AttestationCaList = att_ca_builder.build();
445
446 let att = verify_fido_sk_ssh_attestation(
448 attest.as_slice(),
449 challenge.as_slice(),
450 &att_ca_list,
451 false,
452 )
453 .expect("Failed to parse attestation");
454
455 trace!("key {:?}", key);
456 trace!("att {:?}", att.pubkey);
457 trace!("att full {:?}", att);
458
459 assert_eq!(att.pubkey, key);
461
462 assert!(matches!(
464 att.extensions.cred_protect,
465 ExtnState::Set(CredentialProtectionPolicy::UserVerificationRequired)
466 ));
467 }
468
469 #[test]
470 fn test_ssh_ed25519_sk_attest() {
471 let _ = tracing_subscriber::fmt::try_init();
472
473 let attest = STANDARD.decode("AAAAEXNzaC1zay1hdHRlc3QtdjAxAAAC8DCCAuwwggHUoAMCAQICCQCIobnFT2wgvjANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZdWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAwMDBaGA8yMDUwMDkwNDAwMDAwMFowbzELMAkGA1UEBhMCU0UxEjAQBgNVBAoMCVl1YmljbyBBQjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEoMCYGA1UEAwwfWXViaWNvIFUyRiBFRSBTZXJpYWwgMTE2OTc5MzQxNjBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABP3N+hZ2qRVyajtVRGx/tdK/YAcNNGY++kDoDODSHk4cAqXSZ7jZepIkLdQXk7JP2dD0gVMpP5WzOJpEv8J6tRejgZQwgZEwEwYKKwYBBAGCxAoNAQQFBAMFBAMwEAYJKwYBBAGCxAoMBAMCAQcwIgYJKwYBBAGCxAoCBBUxLjMuNi4xLjQuMS40MTQ4Mi4xLjcwEwYLKwYBBAGC5RwCAQEEBAMCBSAwIQYLKwYBBAGC5RwBAQQEEgQQc7sM1OUCSbicb7WURb9yCzAMBgNVHRMBAf8EAjAAMA0GCSqGSIb3DQEBCwUAA4IBAQA8JczOIP9yDzuYizYwoJwGCwmYbUWNuokPu/NOMRLKTHvRfZRhf5LRHX7WjD0kJwifo725l/O7b+G4Y+3w9a1tK00wBGCMxw3F/oGcxsn+Tg6zWQZW3HXN8Qxfb5vtnX7lK5omugUPyq7XBqiBqFi2oqHFxjPjZSYFqQLE1DxDfJVtxXysvG1q/tkTkRagkAaqLb59SitNKsSXJ14Y9aG6liaFpSL8q+BeIe6XBHZ8NGxGhZdnhOu6qzYcTpSXlYHjeUoVF2/crpnQocjl59cgarJgS2aJV/jlSWnyZVhKbq14up6YUg0UsO60+UYm5rKuxS5OvAsvgKbl+71jhxCSAAAARzBFAiEA9wvGXR0jdmlx41KiDgVnHng/u+aABcL0T7Mcla5RY1cCIG3w7FmnUCC9cN4OTsF0YIUKREVl7YZ/ULpgG9r3gbGcAAAA41jh4wYQ6KFiEVlg/h7CI+ZSnJ9LboAgDcteXDIcivHisb9FAAAAAnO7DNTlAkm4nG+1lEW/cgsAgOlyrDirl7wov1VQfV/0peGGSiOf4dfQ/MwcKRxhWA7OIEczExGaaoiNJZBKyVUnte5FWF4xz+g2yY1LA9DYizkHRyuH3V6nOqaBl56+pImD7oJA2sMGgFaK7OawkNInLrZn+kK1KwDwAuqGyraYxUwOimcyj3iO0cmnx8Kl3VsbpAEBAycgBiFYIJrDpo9OvZ479Kr/+2n9IY88++eEu1g+RqRgrNsGWyCLAAAAAAAAAAA=")
478 .expect("Failed to decode attestation");
479
480 let challenge = STANDARD
481 .decode("aAqBnywP0Vbv3SUgqmnMRQ==")
482 .expect("Failed to decode attestation");
483
484 let mut att_ca_builder = AttestationCaListBuilder::new();
485 att_ca_builder
486 .insert_device_pem(
487 YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM,
488 uuid::uuid!("73bb0cd4-e502-49b8-9c6f-b59445bf720b"),
489 "yk 5 fips".to_string(),
490 Default::default(),
491 )
492 .expect("Failed to build att ca list");
493 let att_ca_list: AttestationCaList = att_ca_builder.build();
494
495 let att = verify_fido_sk_ssh_attestation(
497 attest.as_slice(),
498 challenge.as_slice(),
499 &att_ca_list,
500 false,
501 );
502
503 trace!("att full {:?}", att);
504
505 assert!(matches!(att, Err(WebauthnError::SshPublicKeyEDUnsupported)));
506
507 }
522
523 #[test]
524 fn test_ssh_ecdsa_sk_reject_attest_aaguid() {
525 let _ = tracing_subscriber::fmt::try_init();
526
527 let attest = STANDARD.decode("AAAAEXNzaC1zay1hdHRlc3QtdjAxAAACwTCCAr0wggGloAMCAQICBBisRsAwDQYJKoZIhvcNAQELBQAwLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290IENBIFNlcmlhbCA0NTcyMDA2MzEwIBcNMTQwODAxMDAwMDAwWhgPMjA1MDA5MDQwMDAwMDBaMG4xCzAJBgNVBAYTAlNFMRIwEAYDVQQKDAlZdWJpY28gQUIxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xJzAlBgNVBAMMHll1YmljbyBVMkYgRUUgU2VyaWFsIDQxMzk0MzQ4ODBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABHnqOyx8SXAQYiMM0j/rYOUpMXHUg/EAvoWdaw+DlwMBtUbN1G7PyuPj8w+B6e1ivSaNTB69N7O8vpKowq7rTjqjbDBqMCIGCSsGAQQBgsQKAgQVMS4zLjYuMS40LjEuNDE0ODIuMS43MBMGCysGAQQBguUcAgEBBAQDAgUgMCEGCysGAQQBguUcAQEEBBIEEMtpSB6P90A5k+wKJymhVKgwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAQEAl50Dl9hg+C7hXTEceW66+yL6p+CE2bq0xhu7V/PmtMGKSDe4XDxO2+SDQ/TWpdmxztqK4f7UkSkhcwWOXuHL3WvawHVXxqDo02gluhWef7WtjNr4BIaM+Q6PH4rqF8AWtVwqetSXyJT7cddT15uaSEtsN21yO5mNLh1DBr8QM7Wu+Myly7JWi2kkIm0io1irfYfkrF8uCRqnFXnzpWkJSX1y9U4GusHDtEE7ul6vlMO2TzT566Qay2rig3dtNkZTeEj+6IS93fWxuleYVM/9zrrDRAWVJ+Vt1Zj49WZxWr5DAd0ZETDmufDGQDkSU+IpgD867ydL7b/eP8u9QurWeQAAAEYwRAIgeYp6mYVsuaj0NpHps1qkGkJYroyurnuCKdSYWUCCsVgCIAhFdmhNWGG0cY5l3sZUhjmrwCHpuQ1A0QXbhuEtjM7sAAAAxljE4wYQ6KFiEVlg/h7CI+ZSnJ9LboAgDcteXDIcivHisb9FAAALNMtpSB6P90A5k+wKJymhVKgAQPQVE6m4sayalwAfqHVZBGEP32y5ju2Vo7U3k1zPFKQGLDhpA0dRHWvYbsvTPmqVzSGuxSyRW/ugWzPqsveALlSlAQIDJiABIVggQ25tmKStvyG74d5VF1nSmn9UCTaq/gkNu4mG8PTI11YiWCAMvZ7dwFsRGIN40+RbHnxDitWfGRtXV9rwTbBpG1P3XAAAAAAAAAAA")
532 .expect("Failed to decode attestation");
533
534 let challenge = STANDARD
535 .decode("VzCkpMNVYVgXHBuDP74v9A==")
536 .expect("Failed to decode attestation");
537
538 let mut att_ca_builder = AttestationCaListBuilder::new();
541 att_ca_builder
542 .insert_device_pem(
543 YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM,
544 uuid::uuid!("73bb0cd4-e502-49b8-9c6f-b59445bf720b"),
545 "yk 5 fips".to_string(),
546 Default::default(),
547 )
548 .expect("Failed to build att ca list");
549 let att_ca_list: AttestationCaList = att_ca_builder.build();
550
551 let att = verify_fido_sk_ssh_attestation(
553 attest.as_slice(),
554 challenge.as_slice(),
555 &att_ca_list,
556 false,
557 );
558
559 trace!("att full {:?}", att);
560
561 assert!(matches!(
562 att,
563 Err(WebauthnError::AttestationUntrustedAaguid)
564 ));
565 }
566
567 #[test]
568 fn test_ssh_ecdsa_sk_reject_attest_ca() {
569 let _ = tracing_subscriber::fmt::try_init();
570
571 let attest = STANDARD.decode("AAAAEXNzaC1zay1hdHRlc3QtdjAxAAADGzCCAxcwggK+oAMCAQICCQDFabHRsxYpGTAKBggqhkjOPQQDAjCBnDELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBkdlbmV2YTEQMA4GA1UEBwwHVmVyc29peDEPMA0GA1UECgwGVE9LRU4yMSIwIAYDVQQLDBlBdXRoZW50aWNhdG9yIEF0dGVzdGF0aW9uMRMwEQYDVQQDDAp0b2tlbjIuY29tMSAwHgYJKoZIhvcNAQkBFhFvZmZpY2VAdG9rZW4yLmNvbTAeFw0xOTEyMDQwNzAyMjJaFw0zOTExMjkwNzAyMjJaMF4xCzAJBgNVBAYTAkNIMQ8wDQYDVQQKDAZUT0tFTjIxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xGjAYBgNVBAMMEW9mZmljZUB0b2tlbjIuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC/l7QJNMxtrBu91XScVYjFlqTFza0N/9RRYWPItzgmppWvjUPwyCres27Lo3Waf7OVMdmc5ML5HB+eECnVWqg6OCASQwggEgMAkGA1UdEwQCMAAwHQYDVR0OBBYEFFCysnSFvKuvrSVyB3ToAQshmMpjMIG7BgNVHSMEgbMwgbChgaKkgZ8wgZwxCzAJBgNVBAYTAkNIMQ8wDQYDVQQIDAZHZW5ldmExEDAOBgNVBAcMB1ZlcnNvaXgxDzANBgNVBAoMBlRPS0VOMjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjETMBEGA1UEAwwKdG9rZW4yLmNvbTEgMB4GCSqGSIb3DQEJARYRb2ZmaWNlQHRva2VuMi5jb22CCQCv1vlqKeW5ejATBgsrBgEEAYLlHAIBAQQEAwIFIDAhBgsrBgEEAYLlHAEBBAQSBBCrMvDGIjmvu8Rw0u9OJU23MAoGCCqGSM49BAMCA0cAMEQCIGgzWHRCvlMLEPA+qAk+33KwVVyvTKnBxC7jESc0vSV1AiBXPi/VVvaIiDh0vtnBmMSP1WCUGHhY7RReYNm9cbe8swAAAEYwRAIgfxtWfDli/pqS0/DqyaXvLn5C4BNRXoHx1ofpU4WZqfICIEzUSXKUI4/DezfU9MtW3t5ua5fhgL7EoMdaXBRGmNnLAAAA5ljk4wYQ6KFiEVlg/h7CI+ZSnJ9LboAgDcteXDIcivHisb9FAAADIasy8MYiOa+7xHDS704lTbcAYCnb4hHUYvEK9Dp4gjgJer+Wtcj0GglGtd5ubraTzUc19amoIyg/+/lNKrntsFSalESwu7fNNRPjWldzr2zyueB9MyJZDXkOrkP1iK/B836pudmGcJq6vfV1Da2Bieks16UBAgMmIAEhWCAe32mzSUWbouK4KOykaK3dGczNTUoTqBjengeoL6DhyCJYIIogmo+NOwfBZgF5xEORNffCk+4dA+preNaQE9mSv506AAAAAAAAAAA=")
576 .expect("Failed to decode attestation");
577
578 let challenge = STANDARD
579 .decode("aAqBnywP0Vbv3SUgqmnMRQ==")
580 .expect("Failed to decode attestation");
581
582 let mut att_ca_builder = AttestationCaListBuilder::new();
586 att_ca_builder
587 .insert_device_pem(
588 YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM,
589 uuid::uuid!("73bb0cd4-e502-49b8-9c6f-b59445bf720b"),
590 "yk 5 fips".to_string(),
591 Default::default(),
592 )
593 .expect("Failed to build att ca list");
594 let att_ca_list: AttestationCaList = att_ca_builder.build();
595
596 let att = verify_fido_sk_ssh_attestation(
598 attest.as_slice(),
599 challenge.as_slice(),
600 &att_ca_list,
601 false,
602 );
603
604 trace!("att full {:?}", att);
605
606 assert!(matches!(
607 att,
608 Err(WebauthnError::AttestationChainNotTrusted(_))
609 ));
610 }
611}