Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 165 additions & 13 deletions src/fido/verification.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use super::AuthData;

use ring::signature::{UnparsedPublicKey, ECDSA_P256_SHA256_ASN1};

const YUBICO_U2F_ROOT_CA: &str = "-----BEGIN CERTIFICATE-----
/// From https://developers.yubico.com/PKI/yubico-ca-certs.txt
const YUBICO_U2F_ROOT_CA_457200631: &str = "-----BEGIN CERTIFICATE-----
MIIDHjCCAgagAwIBAgIEG0BT9zANBgkqhkiG9w0BAQsFADAuMSwwKgYDVQQDEyNZ
dWJpY28gVTJGIFJvb3QgQ0EgU2VyaWFsIDQ1NzIwMDYzMTAgFw0xNDA4MDEwMDAw
MDBaGA8yMDUwMDkwNDAwMDAwMFowLjEsMCoGA1UEAxMjWXViaWNvIFUyRiBSb290
Expand All @@ -30,6 +31,116 @@ sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc
U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw==
-----END CERTIFICATE-----";

/// From https://developers.yubico.com/PKI/yubico-ca-certs.txt
const YUBICO_ATTESTATION_ROOT_1: &str = "-----BEGIN CERTIFICATE-----
MIIDPjCCAiagAwIBAgIUXzeiEDJEOTt14F5n0o6Zf/bBwiUwDQYJKoZIhvcNAQEN
BQAwJDEiMCAGA1UEAwwZWXViaWNvIEF0dGVzdGF0aW9uIFJvb3QgMTAgFw0yNDEy
MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowJDEiMCAGA1UEAwwZWXViaWNvIEF0
dGVzdGF0aW9uIFJvb3QgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
AMZ6/TxM8rIT+EaoPvG81ontMOo/2mQ2RBwJHS0QZcxVaNXvl12LUhBZ5LmiBScI
Zd1Rnx1od585h+/dhK7hEm7JAALkKKts1fO53KGNLZujz5h3wGncr4hyKF0G74b/
U3K9hE5mGND6zqYchCRAHfrYMYRDF4YL0X4D5nGdxvppAy6nkEmtWmMnwO3i0TAu
csrbE485HvGM4r0VpgVdJpvgQjiTJCTIq+D35hwtT8QDIv+nGvpcyi5wcIfCkzyC
imJukhYy6KoqNMKQEdpNiSOvWyDMTMt1bwCvEzpw91u+msUt4rj0efnO9s0ZOwdw
MRDnH4xgUl5ZLwrrPkfC1/0CAwEAAaNmMGQwHQYDVR0OBBYEFNLu71oijTptXCOX
PfKF1SbxJXuSMB8GA1UdIwQYMBaAFNLu71oijTptXCOXPfKF1SbxJXuSMBIGA1Ud
EwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBDQUAA4IB
AQC3IW/sgB9pZ8apJNjxuGoX+FkILks0wMNrdXL/coUvsrhzsvl6mePMrbGJByJ1
XnquB5sgcRENFxdQFma3mio8Upf1owM1ZreXrJ0mADG2BplqbJnxiyYa+R11reIF
TWeIhMNcZKsDZrFAyPuFjCWSQvJmNWe9mFRYFgNhXJKkXIb5H1XgEDlwiedYRM7V
olBNlld6pRFKlX8ust6OTMOeADl2xNF0m1LThSdeuXvDyC1g9+ILfz3S6OIYgc3i
roRcFD354g7rKfu67qFAw9gC4yi0xBTPrY95rh4/HqaUYCA/L8ldRk6H7Xk35D+W
Vpmq2Sh/xT5HiFuhf4wJb0bK
-----END CERTIFICATE-----";

/// From https://developers.yubico.com/PKI/yubico-intermediate.pem
const YUBICO_ATTESTATION_INTERMEDIATE_A_1: &str = "-----BEGIN CERTIFICATE-----
MIIDSDCCAjCgAwIBAgIUUcmMXzRIFOgGTK0Tb3gEuZYZkBIwDQYJKoZIhvcNAQEL
BQAwJDEiMCAGA1UEAwwZWXViaWNvIEF0dGVzdGF0aW9uIFJvb3QgMTAgFw0yNDEy
MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowLjEsMCoGA1UEAwwjWXViaWNvIEF0
dGVzdGF0aW9uIEludGVybWVkaWF0ZSBBIDEwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDm555bWY9WW+tOY0rIWHldh+aNanoCZCFh7Gk3YZrQmPUw0hkS
G6qYHQtP+fZyS33VErvg+BQqnmumgNhfxFrkwEZELeidBcC8C4Ag4nqqiPWpzsvI
17NcxYlInLNLFcZY/+gOiN6ZOTihO5/vBZMbj9riaAcqliYmNGJPgTcMGaEAyMzE
MNy2nm6Ep+pjP5aF6gi21t/UQFsuJ1j2Rj/ynM/SdRt+ecal5OYotxHkFbL9vvv2
A2Ov5ITZClw4bOS9npypQimOZ5QAYytmYaQpWl/pMYz6zSj8RqkVDNEJGqNfTKA2
ivLYwX6lSttMPapg0J84l9X0voVN/FpS4VCVAgMBAAGjZjBkMB0GA1UdDgQWBBQg
KFAhG6RaW+hTy52dxeT8bC96HzAfBgNVHSMEGDAWgBTS7u9aIo06bVwjlz3yhdUm
8SV7kjASBgNVHRMBAf8ECDAGAQH/AgECMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
9w0BAQsFAAOCAQEAYMzgLrJLIr0OovQnAZrRIGuabiHSUKSmbLRWpRkWeAtsChDE
HpXcJ/bgDNKYWoHqQ8xRUjB4CyepYevc3YlrG8o7zHxpfVcaoL5SeuJkzHxKn4bT
aSp9+Mvwamnp64kZMiNbFLknfP9kYKoRHkMWheRJ1UsP1z4ScmkCeILfsMs6vqov
qjWClFsJpBcsluYHWF7bBJ1n4Rwg+ATEopY4IgGv6Zvwc+A9r+AT2hqpoSkYoAl+
ANYwgslOf9sJe0V+TA9YY/UlaBmPPTd0//r9wvcePWZkPjKoAC/zUNhfDbh4LV8G
Hs3lyX2XomL/LNc8JYzyIaDEhGQveoPhh/tr1g==
-----END CERTIFICATE-----";

/// From https://developers.yubico.com/PKI/yubico-intermediate.pem
const YUBICO_ATTESTATION_INTERMEDIATE_B_1: &str = "-----BEGIN CERTIFICATE-----
MIIDSDCCAjCgAwIBAgIUDqERw+4RnGSggxgUewJFEPDRZ3YwDQYJKoZIhvcNAQEL
BQAwJDEiMCAGA1UEAwwZWXViaWNvIEF0dGVzdGF0aW9uIFJvb3QgMTAgFw0yNDEy
MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowLjEsMCoGA1UEAwwjWXViaWNvIEF0
dGVzdGF0aW9uIEludGVybWVkaWF0ZSBCIDEwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDI7XnH+ZvDwMCQU8M8ZeV5qscublvVYaaRt3Ybaxn9godLx5sw
H0lXrdgjh5h7FpVgCgYYX7E4bl1vbzULemrMWT8N3WMGUe8QAJbBeioV7W/E+hTZ
P/0SKJVa3ewKBo6ULeMnfQZDrVORAk8wTLq2v5Llj5vMj7JtOotKa9J7nHS8kLmz
XXSaj0SwEPh5OAZUTNV4zs1bvoTAQQWrL4/J9QuKt6WCFE5nUNiRQcEbVF8mlqK2
bx2z6okVltyDVLCxYbpUTELvY1usR3DTGPUoIClOm4crpwnDRLVHvjYePGBB//pE
yzxA/gcScxjwaH1ZUw9bnSbHyurKqbTa1KvjAgMBAAGjZjBkMB0GA1UdDgQWBBTq
t0KQngx7ZHrbVHwDunxOn9ihYTAfBgNVHSMEGDAWgBTS7u9aIo06bVwjlz3yhdUm
8SV7kjASBgNVHRMBAf8ECDAGAQH/AgECMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
9w0BAQsFAAOCAQEAqQaCWMxTGqVVX7Sk7kkJmUueTSYKuU6+KBBSgwIRnlw9K7He
1IpxZ0hdwpPNikKjmcyFgFPzhImwHJgxxuT90Pw3vYOdcJJNktDg35PXOfzSn15c
FAx1RO0mPTmIb8dXiEWOpzoXvdwXDM41ZaCDYMT7w4IQtMyvE7xUBZq2bjtAnq/N
DUA7be4H8H3ipC+/+NKlUrcUh+j48K67WI0u1m6FeQueBA7n06j825rqDqsaLs9T
b7KAHAw8PmrWaNPG2kjKerxPEfecivlFawp2RWZvxrVtn3TV2SBxyCJCkXsND05d
CErVHSJIs+BdtTVNY9AwtyPmnyb0v4mSTzvWdw==
-----END CERTIFICATE-----";

/// From https://developers.yubico.com/PKI/yubico-intermediate.pem
const YUBICO_FIDO_ATTESTATION_A_1: &str = "-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIUTnbbGIR2NHvzqIKFAeQwG1XBis0wDQYJKoZIhvcNAQEL
BQAwLjEsMCoGA1UEAwwjWXViaWNvIEF0dGVzdGF0aW9uIEludGVybWVkaWF0ZSBB
IDEwIBcNMjQxMjAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMCYxJDAiBgNVBAMM
G1l1YmljbyBGSURPIEF0dGVzdGF0aW9uIEEgMTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBAOsXj3k04Ban4TYdtZKqD/OPJxyDyaPmCBUFUiaZIgTteZnj
3X25DhgpZZXsC4D0ydIcrlA6wNUInORL/L9zBbTEIMAVMGo6g7UKAmb2MF6AHbnh
YJd9eikupVNWShHNYNc4GBdO1YN6AfUqvJhHbe3V4SNMPmBREKJPVz7ThwgmggTe
8Ws2K0/wsqv2wSE7pbCBsUZhIX51bZM3pqDwJPTmRFEvt0/6tG5eO8F3j14OXqfE
hmjn1VvxKDYQOLZAxCwwgC0P4CdfWv3y8PSR8I354hO1Y+GzNjvIqX38NKLywuIY
HFerOxNlxEMBvFhYBuRuYAkkgUaPqN6UBhsILrsCAwEAAaNmMGQwHQYDVR0OBBYE
FCCoRHhiyNnbnXRWIL6ZBXoBX9YTMB8GA1UdIwQYMBaAFCAoUCEbpFpb6FPLnZ3F
5PxsL3ofMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqG
SIb3DQEBCwUAA4IBAQCQFafJI1/5Wg9CEEimE1RP54RgQwTNTOOQsLACTe+rItlF
QzC9ZDhrV828yX7jzy+AAsp3izK7T1th2dl7m+tu0sw2Pa/olc02nt6PyIw348ga
HzhI1+0KE45qxvFDeL2lMxbPfCYvyEEaYzjiQELU5951pXGWyKMa/4fLtO+ZKOXh
MuVeq4rXDPI54W6JHOiAaiKdiw+5e3c2kt/jFIQtM6vMXg9LNFzdjETNt20VX9Qe
vRpFZfucMG9wCaQDoFlPzpTMJKhPev/imJmZYhKfr0lLcemtqjIxLAoqZdOYfHBg
6+vAcdPI/iauGpUAv7X+UKNmDwjZ2BaH4sLwhB2m
-----END CERTIFICATE-----";

/// From https://developers.yubico.com/PKI/yubico-intermediate.pem
const YUBICO_FIDO_ATTESTATION_B_1: &str = "-----BEGIN CERTIFICATE-----
MIIDSjCCAjKgAwIBAgIUR38mq26Sf2szVV2BdG6WEN7kuWUwDQYJKoZIhvcNAQEL
BQAwLjEsMCoGA1UEAwwjWXViaWNvIEF0dGVzdGF0aW9uIEludGVybWVkaWF0ZSBC
IDEwIBcNMjQxMjAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMCYxJDAiBgNVBAMM
G1l1YmljbyBGSURPIEF0dGVzdGF0aW9uIEIgMTCCASIwDQYJKoZIhvcNAQEBBQAD
ggEPADCCAQoCggEBANY0Wb9oPoRoKoQyWPaJpz11vrWTg6zTtmNj2VoKRnyvKGRq
pzb83w5l6YA96UYkYBDQP0ilO2DPe6wWqVR5zDfRzdcH8bh+L7dGGvae6hRTZhkF
kCpXDs4HccknrDf8FClJ7He39Jf42/G1Qm2zz9WWmrPXtgiK/x05GjsQfGuDG1zf
5QTUUie8lwymK3TfdOvNeeJAAPe2pn7ItfRb+rVrNWiDzlRn2vNnZ2wPo4wH/WJ6
dhXZG+rMWT+a6Bocg1UfIw6kdunG4bTpZzsvacFYyR0mpf+DeOnpSWAmywJWHvTl
f2YXxFyeXcTACdQlcMNGJ2VhZQ48xtP5/RBP/8kCAwEAAaNmMGQwHQYDVR0OBBYE
FChy42okiqcTS1iqa/HRWjkBn4H/MB8GA1UdIwQYMBaAFOq3QpCeDHtkettUfAO6
fE6f2KFhMBIGA1UdEwEB/wQIMAYBAf8CAQAwDgYDVR0PAQH/BAQDAgGGMA0GCSqG
SIb3DQEBCwUAA4IBAQAn+RHIPbtMEDNdT1g8H/RitAkUdLgAt1tWGWnlj9knbv4/
4GlX7C9p45efPO9/aZL6OV1XRKBi6KmtBW5K7nuYEnMx/5BqBSbLT7rhduC49TBe
Mb9PHdXsTlSVNYefr1dGidr4j0xVBQLb1rknDAbdWDzKfvnayKO8Frwe7Hx843MG
/rJ+c0XruUvbfVTCHLiIWhM7oNDhL8xob6xUo9KLKcSL+ItYsO3/9Wb8Q9GjsqL4
FXsDcG1SaYh7KpfuMmOixqzJZO2nIicPYRg1I2SuiUfYO70tmdHcbl+kSQmSYt7r
q4viILg2Gx3j9rITuWTjbaUaSSQxgOmMSHuyzMAC
-----END CERTIFICATE-----";

/// Defines the transports supported by the FIDO standard
#[derive(Clone, Debug, PartialEq)]
pub enum Transport {
Expand Down Expand Up @@ -132,8 +243,52 @@ fn extract_certificate_extension_data(
Ok(valid_attestation)
}

/// Verify that the intermediates are chained to the root CA.
fn verify_intermediates(
parsed_intermediate: &X509Certificate<'_>,
ca_pems_chain: Vec<&str>,
) -> Result<(), Error> {
// There has to be at the root CA
if ca_pems_chain.is_empty() {
return Err(Error::InvalidSignature);
}

let mut ca_parsed_pems = vec![];

// Parse all the pems
for pem in ca_pems_chain {
let (_, parsed_pem) = parse_x509_pem(pem.as_bytes())
.map_err(|_| Error::ParsingError)?;
ca_parsed_pems.push(parsed_pem);
}

// Parse the root CA
// This is a safe unwrap as we made sure the list is not empty
let mut parent_ca = Pem::parse_x509(&ca_parsed_pems.first().unwrap())
.map_err(|_| Error::ParsingError)?;

// Iteratively verify the chain
for intermediate_ca in ca_parsed_pems.iter().skip(1) {
let intermediate_ca = Pem::parse_x509(&intermediate_ca).map_err(|_| Error::ParsingError)?;

// Check the parent CA has signed this intermediate, return error if not
intermediate_ca
.verify_signature(Some(&parent_ca.tbs_certificate.public_key()))
.map_err(|_| Error::InvalidSignature)?;

parent_ca = intermediate_ca;
}

// Check the parent intermediate CA has signed the final intermediate, return error if not
parsed_intermediate
.verify_signature(Some(&parent_ca.tbs_certificate.public_key()))
.map_err(|_| Error::InvalidSignature)?;

Ok(())
}

/// Verify a provided U2F attestation, signature, and certificate are valid
/// against the root. If no root is given, the Yubico U2F Root is used.
/// against the root. If no root is given, the Yubico U2F Root and FIDO root are used.
pub fn verify_auth_data(
auth_data: &[u8],
auth_data_signature: &[u8],
Expand All @@ -145,20 +300,17 @@ pub fn verify_auth_data(
match alg {
// Verify using ECDSA256
-7 => {
let root_ca_pem = root_pem.unwrap_or(YUBICO_U2F_ROOT_CA);

// Parse the U2F root CA
let (_, root_ca) =
parse_x509_pem(root_ca_pem.as_bytes()).map_err(|_| Error::ParsingError)?;
let root_ca = Pem::parse_x509(&root_ca).map_err(|_| Error::ParsingError)?;

let (_, parsed_intermediate) =
X509Certificate::from_der(intermediate).map_err(|_| Error::ParsingError)?;

// Check the root CA has signed the intermediate, return error if not
parsed_intermediate
.verify_signature(Some(&root_ca.tbs_certificate.public_key()))
.map_err(|_| Error::InvalidSignature)?;
if let Some(pem) = root_pem {
verify_intermediates(&parsed_intermediate, vec![pem])?;
} else if verify_intermediates(&parsed_intermediate, vec![YUBICO_ATTESTATION_ROOT_1, YUBICO_ATTESTATION_INTERMEDIATE_A_1, YUBICO_FIDO_ATTESTATION_A_1]).is_err()
&& verify_intermediates(&parsed_intermediate, vec![YUBICO_ATTESTATION_ROOT_1, YUBICO_ATTESTATION_INTERMEDIATE_B_1, YUBICO_FIDO_ATTESTATION_B_1]).is_err() {
// YUBICO_FIDO_ROOT_CA_450203556 was added later in 2025
// We support both by default to ensure backward compatibility
verify_intermediates(&parsed_intermediate, vec![YUBICO_U2F_ROOT_CA_457200631])?;
}

// Extract public key from verified intermediate certificate
let key_bytes = parsed_intermediate
Expand Down
99 changes: 99 additions & 0 deletions tests/fido-lite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,68 @@ use sshcerts::fido::*;

use ring::digest;

const YUBIKEY_5C_NFC_5_7_4_AUTH_DATA_ED25519: [u8; 225] = [
24, 215, 9, 61, 183, 202, 121, 173, 239, 111, 95, 74, 89, 136, 195, 47, 121, 236, 155, 71, 59,
195, 142, 93, 31, 100, 49, 19, 43, 73, 121, 223, 65, 0, 0, 0, 2, 215, 120, 30, 93, 227, 83, 70,
170, 175, 226, 60, 164, 159, 19, 51, 42, 0, 128, 181, 107, 62, 199, 160, 106, 19, 187, 158,
247, 13, 245, 95, 181, 81, 174, 117, 166, 255, 206, 83, 215, 54, 88, 235, 32, 84, 17, 216, 53,
163, 197, 202, 80, 151, 32, 102, 235, 151, 229, 233, 63, 180, 255, 99, 3, 47, 255, 129, 70,
102, 236, 113, 178, 86, 188, 10, 241, 36, 0, 94, 243, 4, 86, 140, 102, 215, 71, 56, 117, 185,
49, 112, 232, 209, 217, 134, 253, 37, 132, 139, 196, 39, 48, 173, 93, 159, 8, 72, 103, 244, 98,
143, 101, 110, 94, 81, 209, 67, 200, 139, 198, 13, 237, 110, 56, 38, 166, 196, 113, 169, 152,
205, 210, 90, 83, 208, 120, 167, 220, 22, 82, 69, 238, 129, 233, 100, 16, 164, 1, 1, 3, 39, 32,
6, 33, 88, 32, 193, 17, 95, 182, 218, 154, 215, 23, 121, 92, 9, 147, 81, 175, 117, 92, 164,
240, 117, 205, 156, 217, 189, 219, 213, 79, 90, 76, 184, 70, 71, 93,
];

const YUBIKEY_5C_NFC_5_7_4_AUTH_SIG_ED25519: [u8; 70] = [
48, 68, 2, 32, 68, 224, 82, 70, 239, 132, 78, 26, 46, 145, 7, 206, 120, 107, 237, 123, 178, 68,
57, 14, 199, 171, 239, 31, 4, 68, 187, 43, 84, 112, 227, 227, 2, 32, 7, 60, 129, 110, 141, 39,
253, 238, 233, 102, 212, 213, 86, 188, 206, 26, 201, 13, 127, 69, 103, 79, 200, 112, 198, 207,
80, 77, 55, 73, 110, 46
];

const YUBIKEY_5C_NFC_5_7_4_CHALLENGE_ED25519: [u8; 32] = [
32, 42, 235, 166, 59, 236, 122, 244, 184, 25, 62, 65, 163, 147, 88, 50, 160, 74, 219, 14, 203,
46, 26, 228, 50, 238, 7, 254, 63, 233, 154, 161,
];

const YUBIKEY_5C_NFC_5_7_4_INTERMEDIATE: [u8; 731] = [
48, 130, 2, 215, 48, 130, 1, 191, 160, 3, 2, 1, 2, 2, 9, 0, 181, 31, 70, 127, 92, 146, 129, 57,
48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 48, 38, 49, 36, 48, 34, 6, 3, 85, 4,
3, 12, 27, 89, 117, 98, 105, 99, 111, 32, 70, 73, 68, 79, 32, 65, 116, 116, 101, 115, 116, 97,
116, 105, 111, 110, 32, 66, 32, 49, 48, 32, 23, 13, 50, 52, 49, 50, 48, 49, 48, 48, 48, 48, 48,
48, 90, 24, 15, 57, 57, 57, 57, 49, 50, 51, 49, 50, 51, 53, 57, 53, 57, 90, 48, 117, 49, 11,
48, 9, 6, 3, 85, 4, 6, 19, 2, 83, 69, 49, 18, 48, 16, 6, 3, 85, 4, 10, 12, 9, 89, 117, 98, 105,
99, 111, 32, 65, 66, 49, 34, 48, 32, 6, 3, 85, 4, 11, 12, 25, 65, 117, 116, 104, 101, 110, 116,
105, 99, 97, 116, 111, 114, 32, 65, 116, 116, 101, 115, 116, 97, 116, 105, 111, 110, 49, 46,
48, 44, 6, 3, 85, 4, 3, 12, 37, 89, 117, 98, 105, 99, 111, 32, 70, 73, 68, 79, 32, 69, 69, 32,
83, 101, 114, 105, 97, 108, 32, 49, 52, 48, 54, 57, 54, 54, 55, 51, 57, 56, 52, 52, 51, 55, 48,
89, 48, 19, 6, 7, 42, 134, 72, 206, 61, 2, 1, 6, 8, 42, 134, 72, 206, 61, 3, 1, 7, 3, 66, 0,
4, 91, 49, 68, 249, 200, 24, 0, 95, 15, 104, 216, 30, 216, 204, 75, 50, 21, 139, 193, 38, 21,
237, 235, 86, 19, 36, 46, 30, 181, 227, 135, 211, 247, 22, 185, 45, 243, 59, 182, 172, 188,
233, 93, 231, 105, 63, 3, 28, 86, 63, 243, 7, 76, 222, 73, 33, 38, 127, 190, 193, 110, 86, 144,
233, 163, 129, 129, 48, 127, 48, 19, 6, 10, 43, 6, 1, 4, 1, 130, 196, 10, 13, 1, 4, 5, 4, 3,
5, 7, 4, 48, 34, 6, 9, 43, 6, 1, 4, 1, 130, 196, 10, 2, 4, 21, 49, 46, 51, 46, 54, 46, 49, 46,
52, 46, 49, 46, 52, 49, 52, 56, 50, 46, 49, 46, 55, 48, 19, 6, 11, 43, 6, 1, 4, 1, 130, 229,
28, 2, 1, 1, 4, 4, 3, 2, 4, 48, 48, 33, 6, 11, 43, 6, 1, 4, 1, 130, 229, 28, 1, 1, 4, 4, 18, 4,
16, 215, 120, 30, 93, 227, 83, 70, 170, 175, 226, 60, 164, 159, 19, 51, 42, 48, 12, 6, 3, 85,
29, 19, 1, 1, 255, 4, 2, 48, 0, 48, 13, 6, 9, 42, 134, 72, 134, 247, 13, 1, 1, 11, 5, 0, 3,
130, 1, 1, 0, 13, 245, 58, 88, 199, 17, 70, 58, 18, 10, 7, 187, 196, 155, 3, 4, 45, 60, 249,
165, 0, 45, 239, 11, 113, 34, 188, 225, 108, 59, 148, 177, 83, 88, 150, 48, 121, 196, 75, 16,
236, 30, 27, 157, 180, 233, 12, 81, 170, 142, 158, 124, 57, 170, 128, 34, 10, 47, 240, 224,
137, 243, 136, 130, 204, 165, 49, 201, 126, 234, 8, 79, 248, 135, 193, 194, 73, 178, 174, 117,
73, 125, 115, 150, 190, 245, 44, 220, 1, 200, 161, 25, 239, 113, 82, 16, 85, 66, 175, 135, 32,
146, 7, 208, 215, 36, 37, 14, 88, 33, 212, 16, 190, 165, 244, 0, 106, 208, 233, 221, 254, 15,
55, 102, 67, 4, 190, 94, 183, 96, 179, 29, 19, 246, 201, 223, 82, 107, 184, 246, 3, 192, 130,
210, 174, 121, 234, 91, 244, 191, 236, 141, 92, 172, 193, 254, 182, 221, 154, 108, 60, 59, 8,
255, 100, 197, 23, 235, 201, 71, 88, 213, 76, 83, 232, 102, 121, 154, 179, 180, 235, 206, 121,
148, 215, 188, 111, 223, 59, 197, 14, 41, 197, 22, 198, 180, 96, 162, 182, 244, 157, 161, 107,
15, 25, 76, 145, 171, 139, 181, 64, 6, 48, 172, 109, 5, 230, 148, 193, 22, 180, 246, 149, 169,
180, 209, 139, 95, 48, 194, 134, 37, 113, 163, 66, 221, 228, 108, 43, 19, 148, 246, 210, 38,
223, 224, 246, 208, 68, 255, 11, 24, 82, 74, 195, 81,
];

const YUBIKEY_BIO_AUTH_DATA_ED25519: [u8; 225] = [
159, 134, 208, 129, 136, 76, 125, 101, 154, 47, 234, 160, 197, 90, 208, 21, 163, 191, 79, 27,
43, 11, 130, 44, 209, 93, 108, 21, 176, 240, 10, 8, 69, 0, 0, 0, 4, 216, 82, 45, 159, 87, 91,
Expand Down Expand Up @@ -127,6 +189,7 @@ const YUBIKEY_5C_NFC_INTERMEDIATE: [u8; 705] = [
];

#[test]
/// This uses the old YUBICO_U2F_ROOT_CA_457200631
fn verify_and_parse_auth_data_yubikey_bio() {
let hash_challenge = digest::digest(&digest::SHA256, &YUBIKEY_BIO_CHALLENGE_ED25519);
let verified_data = verification::verify_auth_data(
Expand Down Expand Up @@ -159,6 +222,7 @@ fn verify_and_parse_auth_data_yubikey_bio() {
}

#[test]
/// This uses the old YUBICO_U2F_ROOT_CA_457200631
fn verify_and_parse_auth_data_yubikey_5c_nfc() {
let hash_challenge = digest::digest(&digest::SHA256, &YUBIKEY_5C_NFC_CHALLENGE_ED25519);
let verified_data = verification::verify_auth_data(
Expand Down Expand Up @@ -191,3 +255,38 @@ fn verify_and_parse_auth_data_yubikey_5c_nfc() {
hex::decode("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08").unwrap()
);
}

#[test]
/// YUBICO_ATTESTATION_ROOT_1 was rolled out for Yubikey 5c 5.7.4 so this will test the new
/// FIDO root CA
fn verify_and_parse_auth_data_yubikey_5c_nfc_5_7_4() {
let verified_data = verification::verify_auth_data(
&YUBIKEY_5C_NFC_5_7_4_AUTH_DATA_ED25519,
&YUBIKEY_5C_NFC_5_7_4_AUTH_SIG_ED25519,
&YUBIKEY_5C_NFC_5_7_4_CHALLENGE_ED25519,
-7,
&YUBIKEY_5C_NFC_5_7_4_INTERMEDIATE,
None,
)
.unwrap();

// Verify data pulled from the intermediate certificate
assert_eq!(
verified_data.transports,
Some(vec![Transport::USB, Transport::NFC])
);
assert_eq!(
verified_data.aaguid,
Some(hex::decode("d7781e5de35346aaafe23ca49f13332a").unwrap())
);

// Verify data pulled from the auth data
assert_eq!(
verified_data.auth_data.aaguid,
verified_data.aaguid.unwrap()
);
assert_eq!(
verified_data.auth_data.rpid_hash,
hex::decode("18d7093db7ca79adef6f5f4a5988c32f79ec9b473bc38e5d1f6431132b4979df").unwrap()
);
}