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 crate::proto::AttestedPublicKey;
24use crypto_glue::{ecdsa_p256::EcdsaP256PublicKey, traits::DecodeDer, x509};
25use nom::{
26 bytes::complete::{tag, take},
27 number::complete::be_u32,
28};
29use sshkeys::{Curve, EcdsaPublicKey, KeyType, KeyTypeKind, PublicKey, PublicKeyKind};
30use std::time::SystemTime;
31use uuid::Uuid;
32pub use webauthn_rs_core::error::WebauthnError;
33use webauthn_rs_core::{
34 attestation::{
35 assert_packed_attest_req, validate_extension, verify_attestation_ca_chain, FidoGenCeAaguid,
36 },
37 crypto::{compute_sha256, verify_signature},
38 internals::AuthenticatorData,
39 proto::{
40 AttestationCaList, AttestationFormat, AttestationMetadata, COSEKey, COSEKeyType,
41 CredentialProtectionPolicy, ExtnState, ParsedAttestation, ParsedAttestationData,
42 RegisteredExtensions, Registration,
43 },
44};
45
46pub fn verify_fido_sk_ssh_attestation(
60 attestation: &[u8],
61 challenge: &[u8],
62 attestation_cas: &AttestationCaList,
63 current_time: SystemTime,
64) -> Result<AttestedPublicKey, WebauthnError> {
65 if attestation_cas.is_empty() {
66 return Err(WebauthnError::MissingAttestationCaList);
67 }
68
69 let ssh_sk_attest = SshSkAttestation::try_from(attestation)?;
70
71 let acd = ssh_sk_attest
72 .auth_data
73 .acd
74 .as_ref()
75 .ok_or(WebauthnError::MissingAttestationCredentialData)?;
76
77 let attestation_format = AttestationFormat::Packed;
78
79 let client_data_hash = compute_sha256(challenge);
81
82 trace!(?ssh_sk_attest);
83
84 let verification_data: Vec<u8> = ssh_sk_attest
85 .auth_data_bytes
86 .iter()
87 .chain(client_data_hash.iter())
88 .copied()
89 .collect();
90
91 let is_valid_signature = verify_signature(
92 &ssh_sk_attest.att_cert,
93 &ssh_sk_attest.sig,
94 &verification_data,
95 )?;
96
97 if !is_valid_signature {
98 return Err(WebauthnError::AttestationStatementSigInvalid);
99 }
100
101 assert_packed_attest_req(&ssh_sk_attest.att_cert)?;
106
107 validate_extension::<FidoGenCeAaguid>(&ssh_sk_attest.att_cert, &acd.aaguid)?;
112
113 let att_x509 = vec![ssh_sk_attest.att_cert.clone()];
115
116 let attestation = ParsedAttestation {
117 data: ParsedAttestationData::Basic(att_x509),
118 metadata: AttestationMetadata::Packed {
119 aaguid: Uuid::from_bytes(acd.aaguid),
120 },
121 };
122
123 let ca_crt = verify_attestation_ca_chain(&attestation.data, attestation_cas, current_time)?;
124
125 let ca_crt = ca_crt.ok_or(WebauthnError::AttestationNotVerifiable)?;
130
131 match &attestation.metadata {
132 AttestationMetadata::Packed { aaguid } | AttestationMetadata::Tpm { aaguid, .. } => {
133 if !ca_crt.aaguids().contains_key(aaguid) {
135 error!(?aaguid, "aaguid not trusted by this CA");
136 return Err(WebauthnError::AttestationUntrustedAaguid);
137 }
138 }
139 _ => {
140 error!("this attestation format does not contain an aaguid and can not proceed");
141 return Err(WebauthnError::AttestationFormatMissingAaguid);
142 }
143 };
144
145 let cred_protect = match ssh_sk_attest.auth_data.extensions.cred_protect.as_ref() {
146 Some(credprotect) => {
147 if credprotect.0 == CredentialProtectionPolicy::UserVerificationRequired
148 && !ssh_sk_attest.auth_data.user_verified
149 {
150 return Err(WebauthnError::SshPublicKeyInconsistentUserVerification);
151 }
152 ExtnState::Set(credprotect.0)
153 }
154 None => ExtnState::NotRequested,
155 };
156
157 let extensions = RegisteredExtensions {
158 cred_protect,
159 ..Default::default()
160 };
161
162 if ssh_sk_attest.auth_data.backup_eligible || ssh_sk_attest.auth_data.backup_state {
165 error!("Fido ssh sk keys may not be backed up or backup eligible");
166 return Err(WebauthnError::SshPublicKeyBackupState);
167 }
168
169 let ck = COSEKey::try_from(&acd.credential_pk).map_err(|e| {
173 if matches!(e, WebauthnError::COSEKeyEDUnsupported) {
174 WebauthnError::SshPublicKeyEDUnsupported
175 } else {
176 e
177 }
178 })?;
179 trace!(?ck);
180
181 let pubkey = to_ssh_pubkey(&ck)?;
182
183 Ok(AttestedPublicKey {
184 pubkey,
185 extensions,
186 attestation,
187 attestation_format,
188 })
189}
190
191macro_rules! cbor_try_bytes {
192 (
193 $v:expr
194 ) => {{
195 match $v {
196 serde_cbor_2::Value::Bytes(m) => Ok(m),
197 _ => Err(WebauthnError::COSEKeyInvalidCBORValue),
198 }
199 }};
200}
201
202#[derive(Debug)]
203struct SshSkAttestation {
204 att_cert: x509::Certificate,
205 sig: Vec<u8>,
206 auth_data_bytes: Vec<u8>,
207 auth_data: AuthenticatorData<Registration>,
208}
209
210struct SshSkAttestationRaw<'a> {
211 att_cert_raw: &'a [u8],
213 sig_raw: &'a [u8],
215 auth_data_raw: &'a [u8],
217}
218
219impl TryFrom<&[u8]> for SshSkAttestation {
220 type Error = WebauthnError;
221
222 fn try_from(data: &[u8]) -> Result<SshSkAttestation, WebauthnError> {
223 let sk_raw = parse_ssh_sk_attestation(data)
230 .map_err(|e| {
231 error!(?e, "try_from parse_ssh_sk_attestation");
232 WebauthnError::ParseNOMFailure
233 })
234 .map(|(_, ad)| ad)?;
236
237 let sig = sk_raw.sig_raw.to_vec();
240
241 let att_cert = x509::Certificate::from_der(sk_raw.att_cert_raw)
242 .map_err(|_err| WebauthnError::X509DerInvalid)?;
243
244 let auth_data_bytes = serde_cbor_2::from_slice(sk_raw.auth_data_raw)
245 .map_err(|e| {
246 error!(?e, "invalid auth data cbor");
247 WebauthnError::ParseNOMFailure
248 })
249 .and_then(|value| cbor_try_bytes!(value))?;
250
251 let auth_data: AuthenticatorData<Registration> =
252 AuthenticatorData::try_from(auth_data_bytes.as_slice()).map_err(|e| {
253 error!(?e, "invalid auth data structure");
254 WebauthnError::ParseNOMFailure
255 })?;
256
257 Ok(SshSkAttestation {
258 att_cert,
259 sig,
260 auth_data_bytes,
262 auth_data,
263 })
264 }
265}
266
267fn parse_ssh_sk_attestation(i: &[u8]) -> nom::IResult<&[u8], SshSkAttestationRaw<'_>> {
268 let (i, _tag_len) = tag([0, 0, 0, 17])(i)?;
271 let (i, _tag) = tag("ssh-sk-attest-v01")(i)?;
272
273 let (i, att_cert_len) = be_u32(i)?;
274 let (i, att_cert_raw) = take(att_cert_len as usize)(i)?;
275
276 let (i, sig_len) = be_u32(i)?;
277 let (i, sig_raw) = take(sig_len as usize)(i)?;
278
279 let (i, auth_data_len) = be_u32(i)?;
280 let (i, auth_data_raw) = take(auth_data_len as usize)(i)?;
281
282 let (i, _resvd_flags) = be_u32(i)?;
283 let (i, _resvd) = be_u32(i)?;
284
285 Ok((
286 i,
287 SshSkAttestationRaw {
288 att_cert_raw,
289 sig_raw,
290 auth_data_raw,
291 },
292 ))
293}
294
295fn to_ssh_pubkey(cose: &COSEKey) -> Result<PublicKey, WebauthnError> {
296 match &cose.key {
297 COSEKeyType::EC_EC2(ec2k) => {
298 let pubkey = EcdsaP256PublicKey::try_from(ec2k)?;
299 let key = pubkey.to_sec1_bytes().into();
300
301 let kind = PublicKeyKind::Ecdsa(EcdsaPublicKey {
302 curve: Curve::from_identifier("nistp256").map_err(|_| {
303 error!("Invalid curve identifier");
304 WebauthnError::SshPublicKeyInvalidCurve
305 })?,
306 key,
307 sk_application: Some("ssh:".to_string()),
308 });
309
310 Ok(PublicKey {
311 key_type: KeyType {
312 name: "sk-ecdsa-sha2-nistp256@openssh.com",
313 short_name: "ECDSA-SK",
314 is_cert: false,
315 is_sk: true,
316 kind: KeyTypeKind::EcdsaSk,
317 plain: "sk-ecdsa-sha2-nistp256@openssh.com",
318 },
319 kind,
320 comment: None,
321 })
322 }
323 _ => {
324 error!("ed25519 or ed448 public keys are not supported");
325 Err(WebauthnError::SshPublicKeyEDUnsupported)
326 }
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::{verify_fido_sk_ssh_attestation, WebauthnError};
333 use base64::{engine::general_purpose::STANDARD, Engine};
334 use std::time::SystemTime;
335 use webauthn_rs_core::proto::{
336 AttestationCaList, AttestationCaListBuilder, CredentialProtectionPolicy, ExtnState,
337 };
338 use webauthn_rs_device_catalog::data::yubico::YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM;
339
340 #[test]
341 fn test_ssh_ecdsa_sk_attest() {
342 let _ = tracing_subscriber::fmt::try_init();
343
344 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")
349 .expect("Failed to decode attestation");
350
351 let challenge = STANDARD
352 .decode("VzCkpMNVYVgXHBuDP74v9A==")
353 .expect("Failed to decode attestation");
354
355 let pubkey = "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBENubZikrb8hu+HeVRdZ0pp/VAk2qv4JDbuJhvD0yNdWDL2e3cBbERiDeNPkWx58Q4rVnxkbV1fa8E2waRtT91wAAAAEc3NoOg== william@hostname";
356 let mut key = sshkeys::PublicKey::from_string(pubkey).unwrap();
357 key.comment = None;
359
360 let mut att_ca_builder = AttestationCaListBuilder::new();
361 att_ca_builder
362 .insert_device_pem(
363 YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM,
364 uuid::uuid!("cb69481e-8ff7-4039-93ec-0a2729a154a8"),
365 "yk 5 nano".to_string(),
366 Default::default(),
367 )
368 .expect("Failed to build att ca list");
369 let att_ca_list: AttestationCaList = att_ca_builder.build();
370
371 let current_time = SystemTime::now();
372
373 let att = verify_fido_sk_ssh_attestation(
375 attest.as_slice(),
376 challenge.as_slice(),
377 &att_ca_list,
378 current_time,
379 )
380 .expect("Failed to parse attestation");
381
382 trace!("key {:?}", key);
383 trace!("att {:?}", att.pubkey);
384 trace!("att full {:?}", att);
385
386 assert_eq!(att.pubkey, key);
388
389 assert!(matches!(
391 att.extensions.cred_protect,
392 ExtnState::NotRequested
393 ));
394 }
395
396 #[test]
397 fn test_ssh_ecdsa_sk_credprotect_attest() {
398 let _ = tracing_subscriber::fmt::try_init();
399
400 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")
405 .expect("Failed to decode attestation");
406 let challenge = STANDARD
407 .decode("VzCkpMNVYVgXHBuDP74v9A==")
408 .expect("Failed to decode attestation");
409
410 let pubkey = "sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBMD4M0oQcZZURp0PhmabT3X+rvYak+JdnMwDTlJ/zBzZrec0hNPMTEy2/BSWTiX/LCtOIuxSUAzRFG07JAwxxTwAAAAEc3NoOg== william@hostname";
411 let mut key = sshkeys::PublicKey::from_string(pubkey).unwrap();
412 key.comment = None;
414
415 let mut att_ca_builder = AttestationCaListBuilder::new();
416 att_ca_builder
417 .insert_device_pem(
418 YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM,
419 uuid::uuid!("73bb0cd4-e502-49b8-9c6f-b59445bf720b"),
420 "yk 5 fips".to_string(),
421 Default::default(),
422 )
423 .expect("Failed to build att ca list");
424 let att_ca_list: AttestationCaList = att_ca_builder.build();
425
426 let current_time = SystemTime::now();
427
428 let att = verify_fido_sk_ssh_attestation(
430 attest.as_slice(),
431 challenge.as_slice(),
432 &att_ca_list,
433 current_time,
434 )
435 .expect("Failed to parse attestation");
436
437 trace!("key {:?}", key);
438 trace!("att {:?}", att.pubkey);
439 trace!("att full {:?}", att);
440
441 assert_eq!(att.pubkey, key);
443
444 assert!(matches!(
446 att.extensions.cred_protect,
447 ExtnState::Set(CredentialProtectionPolicy::UserVerificationRequired)
448 ));
449 }
450
451 #[test]
452 fn test_ssh_ed25519_sk_attest() {
453 let _ = tracing_subscriber::fmt::try_init();
454
455 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=")
460 .expect("Failed to decode attestation");
461
462 let challenge = STANDARD
463 .decode("aAqBnywP0Vbv3SUgqmnMRQ==")
464 .expect("Failed to decode attestation");
465
466 let mut att_ca_builder = AttestationCaListBuilder::new();
467 att_ca_builder
468 .insert_device_pem(
469 YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM,
470 uuid::uuid!("73bb0cd4-e502-49b8-9c6f-b59445bf720b"),
471 "yk 5 fips".to_string(),
472 Default::default(),
473 )
474 .expect("Failed to build att ca list");
475 let att_ca_list: AttestationCaList = att_ca_builder.build();
476
477 let current_time = SystemTime::now();
478
479 let att = verify_fido_sk_ssh_attestation(
481 attest.as_slice(),
482 challenge.as_slice(),
483 &att_ca_list,
484 current_time,
485 );
486
487 trace!("att full {:?}", att);
488
489 assert!(matches!(att, Err(WebauthnError::SshPublicKeyEDUnsupported)));
490
491 }
506
507 #[test]
508 fn test_ssh_ecdsa_sk_reject_attest_aaguid() {
509 let _ = tracing_subscriber::fmt::try_init();
510
511 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")
516 .expect("Failed to decode attestation");
517
518 let challenge = STANDARD
519 .decode("VzCkpMNVYVgXHBuDP74v9A==")
520 .expect("Failed to decode attestation");
521
522 let mut att_ca_builder = AttestationCaListBuilder::new();
525 att_ca_builder
526 .insert_device_pem(
527 YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM,
528 uuid::uuid!("73bb0cd4-e502-49b8-9c6f-b59445bf720b"),
529 "yk 5 fips".to_string(),
530 Default::default(),
531 )
532 .expect("Failed to build att ca list");
533 let att_ca_list: AttestationCaList = att_ca_builder.build();
534
535 let current_time = SystemTime::now();
536
537 let att = verify_fido_sk_ssh_attestation(
539 attest.as_slice(),
540 challenge.as_slice(),
541 &att_ca_list,
542 current_time,
543 );
544
545 trace!("att full {:?}", att);
546
547 assert!(matches!(
548 att,
549 Err(WebauthnError::AttestationUntrustedAaguid)
550 ));
551 }
552
553 #[test]
554 fn test_ssh_ecdsa_sk_reject_attest_ca() {
555 let _ = tracing_subscriber::fmt::try_init();
556
557 let attest = STANDARD.decode("AAAAEXNzaC1zay1hdHRlc3QtdjAxAAADGzCCAxcwggK+oAMCAQICCQDFabHRsxYpGTAKBggqhkjOPQQDAjCBnDELMAkGA1UEBhMCQ0gxDzANBgNVBAgMBkdlbmV2YTEQMA4GA1UEBwwHVmVyc29peDEPMA0GA1UECgwGVE9LRU4yMSIwIAYDVQQLDBlBdXRoZW50aWNhdG9yIEF0dGVzdGF0aW9uMRMwEQYDVQQDDAp0b2tlbjIuY29tMSAwHgYJKoZIhvcNAQkBFhFvZmZpY2VAdG9rZW4yLmNvbTAeFw0xOTEyMDQwNzAyMjJaFw0zOTExMjkwNzAyMjJaMF4xCzAJBgNVBAYTAkNIMQ8wDQYDVQQKDAZUT0tFTjIxIjAgBgNVBAsMGUF1dGhlbnRpY2F0b3IgQXR0ZXN0YXRpb24xGjAYBgNVBAMMEW9mZmljZUB0b2tlbjIuY29tMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC/l7QJNMxtrBu91XScVYjFlqTFza0N/9RRYWPItzgmppWvjUPwyCres27Lo3Waf7OVMdmc5ML5HB+eECnVWqg6OCASQwggEgMAkGA1UdEwQCMAAwHQYDVR0OBBYEFFCysnSFvKuvrSVyB3ToAQshmMpjMIG7BgNVHSMEgbMwgbChgaKkgZ8wgZwxCzAJBgNVBAYTAkNIMQ8wDQYDVQQIDAZHZW5ldmExEDAOBgNVBAcMB1ZlcnNvaXgxDzANBgNVBAoMBlRPS0VOMjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjETMBEGA1UEAwwKdG9rZW4yLmNvbTEgMB4GCSqGSIb3DQEJARYRb2ZmaWNlQHRva2VuMi5jb22CCQCv1vlqKeW5ejATBgsrBgEEAYLlHAIBAQQEAwIFIDAhBgsrBgEEAYLlHAEBBAQSBBCrMvDGIjmvu8Rw0u9OJU23MAoGCCqGSM49BAMCA0cAMEQCIGgzWHRCvlMLEPA+qAk+33KwVVyvTKnBxC7jESc0vSV1AiBXPi/VVvaIiDh0vtnBmMSP1WCUGHhY7RReYNm9cbe8swAAAEYwRAIgfxtWfDli/pqS0/DqyaXvLn5C4BNRXoHx1ofpU4WZqfICIEzUSXKUI4/DezfU9MtW3t5ua5fhgL7EoMdaXBRGmNnLAAAA5ljk4wYQ6KFiEVlg/h7CI+ZSnJ9LboAgDcteXDIcivHisb9FAAADIasy8MYiOa+7xHDS704lTbcAYCnb4hHUYvEK9Dp4gjgJer+Wtcj0GglGtd5ubraTzUc19amoIyg/+/lNKrntsFSalESwu7fNNRPjWldzr2zyueB9MyJZDXkOrkP1iK/B836pudmGcJq6vfV1Da2Bieks16UBAgMmIAEhWCAe32mzSUWbouK4KOykaK3dGczNTUoTqBjengeoL6DhyCJYIIogmo+NOwfBZgF5xEORNffCk+4dA+preNaQE9mSv506AAAAAAAAAAA=")
562 .expect("Failed to decode attestation");
563
564 let challenge = STANDARD
565 .decode("aAqBnywP0Vbv3SUgqmnMRQ==")
566 .expect("Failed to decode attestation");
567
568 let mut att_ca_builder = AttestationCaListBuilder::new();
572 att_ca_builder
573 .insert_device_pem(
574 YUBICO_U2F_ROOT_CA_SERIAL_457200631_PEM,
575 uuid::uuid!("73bb0cd4-e502-49b8-9c6f-b59445bf720b"),
576 "yk 5 fips".to_string(),
577 Default::default(),
578 )
579 .expect("Failed to build att ca list");
580 let att_ca_list: AttestationCaList = att_ca_builder.build();
581
582 let current_time = SystemTime::now();
583
584 let att = verify_fido_sk_ssh_attestation(
586 attest.as_slice(),
587 challenge.as_slice(),
588 &att_ca_list,
589 current_time,
590 );
591
592 trace!("att full {:?}", att);
593
594 assert!(matches!(
595 att,
596 Err(WebauthnError::AttestationChainNotTrusted(_))
597 ));
598 }
599}