From 2627bf3d3fe9c13eee0af5c8926068d377f6c51d Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Thu, 10 Apr 2025 01:56:43 -0700 Subject: [PATCH 1/6] Add Yubico FIDO Root CA Serial 450203556 --- src/fido/verification.rs | 62 +++++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/src/fido/verification.rs b/src/fido/verification.rs index 5b272aa..5192fd1 100644 --- a/src/fido/verification.rs +++ b/src/fido/verification.rs @@ -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 @@ -30,6 +31,28 @@ sG/5xUb/Btwb2X2g4InpiB/yt/3CpQXpiWX/K4mBvUKiGn05ZsqeY1gx4g0xLBqc U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw== -----END CERTIFICATE-----"; +/// From https://developers.yubico.com/PKI/yubico-ca-certs.txt +const YUBICO_FIDO_ROOT_CA_450203556: &str = "-----BEGIN CERTIFICATE----- +MIIDMzCCAhugAwIBAgIUSOEjTf//yqRfPW7Qq8qtIyCrAg8wDQYJKoZIhvcNAQEL +BQAwLzEtMCsGA1UEAwwkWXViaWNvIEZJRE8gUm9vdCBDQSBTZXJpYWwgNDUwMjAz +NTU2MCAXDTI0MDUwMTAwMDAwMFoYDzIwNjAwNDMwMDAwMDAwWjAvMS0wKwYDVQQD +DCRZdWJpY28gRklETyBSb290IENBIFNlcmlhbCA0NTAyMDM1NTYwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdvl27w2gu1fPXeEFbIdqx0BalvVDVWrQP +J7HqviuEtZHlxSLxSFtcXpTolvLvof8f4tMerQTkVGzcmYzm1EBT4IJuMmoEqfkE +EhWpsADMFrjZkqlZY9EqxQzLoVEEonE5oGxSdVCxCcLIackpyR/CCXvj1Bt/hTgE +9hTlF4pRqxMkx3plF7y8dDZlRHWs7vbnhmBCGeI0ZPEQ6nl2mCg2r74adF2u6K9r +rLfhBC3QLE8EPrgqUsI+hkuq2tK4M2SMOp8uUVVkqUeu3h0kr3WVI0W02pkgrOgi +FKLFNkSrbYhdjMBDj5izmqfc9xJRKoDX612qd8ZGVHpT5AYFX+1hAgMBAAGjRTBD +MB0GA1UdDgQWBBTZyU5DiQ/a2UEgE7qBK0zhIsRNRjASBgNVHRMBAf8ECDAGAQH/ +AgEAMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAXvnB4SLuUJfY +MSVGAhssL/SmWli3FSccgxydvKlACcidIIWKQqa3q/QSUEQzC9DgEfMgr7iC1BkT +ZbILboV6UZ5knNsvjEZWuMeogJ8tgZs1hVvKwZizwJ+mEcmsjhIrBYuoL1T6yrOJ +vKFg1jv+Cy4ZwA9Bpk/V3UOir1VyK8dCtyHu6vfosotAdYx8FAuR243gRTMV6Jx8 +Jdig2JDIAQMlzVeDpSUHX/K2HXRHxHwfgjbgUjjBu/72r8OfehyhzHXI3K8CFFdf +lO+8nEOJK3y8F1ivgS5uN/8SmcYw/STQYwhrxPuwz3nP8baMum4BB2nnYmpB60sX +3bl5k8QUSw== +-----END CERTIFICATE-----"; + /// Defines the transports supported by the FIDO standard #[derive(Clone, Debug, PartialEq)] pub enum Transport { @@ -132,8 +155,25 @@ fn extract_certificate_extension_data( Ok(valid_attestation) } +/// Verify that the intermediate was signed by the root CA. +fn verify_intermediate( + parsed_intermediate: &X509Certificate<'_>, + root_ca_pem: &str, +) -> Result<(), Error> { + // Parse the 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)?; + // 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)?; + + 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], @@ -145,20 +185,16 @@ 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_intermediate(&parsed_intermediate, pem)?; + } else if verify_intermediate(&parsed_intermediate, YUBICO_FIDO_ROOT_CA_450203556).is_err() { + // YUBICO_FIDO_ROOT_CA_450203556 was added later in 2025 + // We support both by default to ensure backward compatibility + verify_intermediate(&parsed_intermediate, YUBICO_U2F_ROOT_CA_457200631)?; + } // Extract public key from verified intermediate certificate let key_bytes = parsed_intermediate From 5d8dc18e0ddb6d507f0baf8ed74309fb042e143a Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Sat, 12 Apr 2025 17:36:08 -0700 Subject: [PATCH 2/6] Use Attestation Root instead of FIDO Root CA --- src/fido/verification.rs | 174 ++++++++++++++++++++++++++++++++------- tests/fido-lite.rs | 99 ++++++++++++++++++++++ 2 files changed, 244 insertions(+), 29 deletions(-) diff --git a/src/fido/verification.rs b/src/fido/verification.rs index 5192fd1..5ca18e6 100644 --- a/src/fido/verification.rs +++ b/src/fido/verification.rs @@ -32,25 +32,113 @@ U9psmyPzK+Vsgw2jeRQ5JlKDyqE0hebfC1tvFu0CCrJFcw== -----END CERTIFICATE-----"; /// From https://developers.yubico.com/PKI/yubico-ca-certs.txt -const YUBICO_FIDO_ROOT_CA_450203556: &str = "-----BEGIN CERTIFICATE----- -MIIDMzCCAhugAwIBAgIUSOEjTf//yqRfPW7Qq8qtIyCrAg8wDQYJKoZIhvcNAQEL -BQAwLzEtMCsGA1UEAwwkWXViaWNvIEZJRE8gUm9vdCBDQSBTZXJpYWwgNDUwMjAz -NTU2MCAXDTI0MDUwMTAwMDAwMFoYDzIwNjAwNDMwMDAwMDAwWjAvMS0wKwYDVQQD -DCRZdWJpY28gRklETyBSb290IENBIFNlcmlhbCA0NTAyMDM1NTYwggEiMA0GCSqG -SIb3DQEBAQUAA4IBDwAwggEKAoIBAQCdvl27w2gu1fPXeEFbIdqx0BalvVDVWrQP -J7HqviuEtZHlxSLxSFtcXpTolvLvof8f4tMerQTkVGzcmYzm1EBT4IJuMmoEqfkE -EhWpsADMFrjZkqlZY9EqxQzLoVEEonE5oGxSdVCxCcLIackpyR/CCXvj1Bt/hTgE -9hTlF4pRqxMkx3plF7y8dDZlRHWs7vbnhmBCGeI0ZPEQ6nl2mCg2r74adF2u6K9r -rLfhBC3QLE8EPrgqUsI+hkuq2tK4M2SMOp8uUVVkqUeu3h0kr3WVI0W02pkgrOgi -FKLFNkSrbYhdjMBDj5izmqfc9xJRKoDX612qd8ZGVHpT5AYFX+1hAgMBAAGjRTBD -MB0GA1UdDgQWBBTZyU5DiQ/a2UEgE7qBK0zhIsRNRjASBgNVHRMBAf8ECDAGAQH/ -AgEAMA4GA1UdDwEB/wQEAwIBBjANBgkqhkiG9w0BAQsFAAOCAQEAXvnB4SLuUJfY -MSVGAhssL/SmWli3FSccgxydvKlACcidIIWKQqa3q/QSUEQzC9DgEfMgr7iC1BkT -ZbILboV6UZ5knNsvjEZWuMeogJ8tgZs1hVvKwZizwJ+mEcmsjhIrBYuoL1T6yrOJ -vKFg1jv+Cy4ZwA9Bpk/V3UOir1VyK8dCtyHu6vfosotAdYx8FAuR243gRTMV6Jx8 -Jdig2JDIAQMlzVeDpSUHX/K2HXRHxHwfgjbgUjjBu/72r8OfehyhzHXI3K8CFFdf -lO+8nEOJK3y8F1ivgS5uN/8SmcYw/STQYwhrxPuwz3nP8baMum4BB2nnYmpB60sX -3bl5k8QUSw== +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 @@ -155,18 +243,45 @@ fn extract_certificate_extension_data( Ok(valid_attestation) } -/// Verify that the intermediate was signed by the root CA. -fn verify_intermediate( +/// Verify that the intermediates are chained to the root CA. +fn verify_intermediates( parsed_intermediate: &X509Certificate<'_>, - root_ca_pem: &str, + 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 - let (_, root_ca) = parse_x509_pem(root_ca_pem.as_bytes()) + // 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)?; - let root_ca = Pem::parse_x509(&root_ca).map_err(|_| Error::ParsingError)?; - // Check the root CA has signed the intermediate, return error if not + + // 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(&root_ca.tbs_certificate.public_key())) + .verify_signature(Some(&parent_ca.tbs_certificate.public_key())) .map_err(|_| Error::InvalidSignature)?; Ok(()) @@ -189,11 +304,12 @@ pub fn verify_auth_data( X509Certificate::from_der(intermediate).map_err(|_| Error::ParsingError)?; if let Some(pem) = root_pem { - verify_intermediate(&parsed_intermediate, pem)?; - } else if verify_intermediate(&parsed_intermediate, YUBICO_FIDO_ROOT_CA_450203556).is_err() { + 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_intermediate(&parsed_intermediate, YUBICO_U2F_ROOT_CA_457200631)?; + verify_intermediates(&parsed_intermediate, vec![YUBICO_U2F_ROOT_CA_457200631])?; } // Extract public key from verified intermediate certificate diff --git a/tests/fido-lite.rs b/tests/fido-lite.rs index c1ab872..e854d44 100644 --- a/tests/fido-lite.rs +++ b/tests/fido-lite.rs @@ -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, @@ -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( @@ -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( @@ -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() + ); +} From 68ca4739e4e85cd77b6dfbc7af323bba2571d671 Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Sat, 12 Apr 2025 18:11:56 -0700 Subject: [PATCH 3/6] Refactor --- src/fido/verification.rs | 49 +++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/fido/verification.rs b/src/fido/verification.rs index 5ca18e6..9ceb78a 100644 --- a/src/fido/verification.rs +++ b/src/fido/verification.rs @@ -246,25 +246,25 @@ fn extract_certificate_extension_data( /// Verify that the intermediates are chained to the root CA. fn verify_intermediates( parsed_intermediate: &X509Certificate<'_>, - ca_pems_chain: Vec<&str>, + ca_pems: Vec<&str>, ) -> Result<(), Error> { - // There has to be at the root CA - if ca_pems_chain.is_empty() { + // There has to be at least the root CA + if ca_pems.is_empty() { return Err(Error::InvalidSignature); } let mut ca_parsed_pems = vec![]; // Parse all the pems - for pem in ca_pems_chain { + for pem in ca_pems { 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()) + let root_ca = ca_parsed_pems.first().ok_or(Error::ParsingError)?; + let mut parent_ca = Pem::parse_x509(&root_ca) .map_err(|_| Error::ParsingError)?; // Iteratively verify the chain @@ -287,6 +287,34 @@ fn verify_intermediates( Ok(()) } +/// Verify that the intermediate chains to some Yubico root CA for FIDO attestation +/// We try all known Yubico Root CAs for backward compatibility +fn verify_yubico_intermediate(parsed_intermediate: &X509Certificate<'_>) -> Result<(), Error> { + if verify_intermediates( + &parsed_intermediate, + vec![ + YUBICO_ATTESTATION_ROOT_1, + YUBICO_ATTESTATION_INTERMEDIATE_A_1, + YUBICO_FIDO_ATTESTATION_A_1 + ] + ).is_ok() { + return Ok(()); + } + + if verify_intermediates( + &parsed_intermediate, + vec![ + YUBICO_ATTESTATION_ROOT_1, + YUBICO_ATTESTATION_INTERMEDIATE_B_1, + YUBICO_FIDO_ATTESTATION_B_1, + ] + ).is_ok() { + return Ok(()); + } + + verify_intermediates(&parsed_intermediate, vec![YUBICO_U2F_ROOT_CA_457200631]) +} + /// Verify a provided U2F attestation, signature, and certificate are valid /// against the root. If no root is given, the Yubico U2F Root and FIDO root are used. pub fn verify_auth_data( @@ -303,13 +331,12 @@ pub fn verify_auth_data( let (_, parsed_intermediate) = X509Certificate::from_der(intermediate).map_err(|_| Error::ParsingError)?; + // If a custom root CA is provided, we use that for verification. + // If not, we will try all the known Yubico Root CAs for backward compatibility 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])?; + } else { + verify_yubico_intermediate(&parsed_intermediate)?; } // Extract public key from verified intermediate certificate From 7a5de1d04f627a682717acf7bf9fb9feb01e641a Mon Sep 17 00:00:00 2001 From: Mitchell Grenier Date: Sun, 13 Apr 2025 18:44:15 -0700 Subject: [PATCH 4/6] Make Tests Consistent When I was originally writing the Mozilla handler for this library, I didn't implement hashing of the challenge because there is no way to take in a challenge from a remote host. This is why @timweri had to remove the hashing from the test in his earlier commits. We now generate random data first, then hash it such that we can get the preimage to test it properly. This also will allow us to extend the API into the future to allow remote challenges for key generation verification. --- src/fido/generate/mozilla.rs | 20 ++++++--- src/fido/verification.rs | 20 +++++---- tests/fido-lite.rs | 81 ++++++++++++++++++------------------ 3 files changed, 66 insertions(+), 55 deletions(-) diff --git a/src/fido/generate/mozilla.rs b/src/fido/generate/mozilla.rs index f17642d..e7a21d9 100644 --- a/src/fido/generate/mozilla.rs +++ b/src/fido/generate/mozilla.rs @@ -8,8 +8,8 @@ use crate::{ PrivateKey, }; -use ring::rand::SecureRandom; -use ring::rand::SystemRandom; +use ring::digest; +use ring::rand::{SecureRandom, SystemRandom}; use crate::fido::Error as FidoError; @@ -47,12 +47,20 @@ pub fn generate_new_ssh_key( }; manager.add_u2f_usb_hid_platform_transports(); - let mut chall_bytes = [0u8; 32]; - SystemRandom::new().fill(&mut chall_bytes).unwrap(); + // This forms the challenge + let mut client_data = [0u8; 32]; + // Fill it with random data because we don't support taking in + // challenge data at this point. + SystemRandom::new().fill(&mut client_data).unwrap(); + + // Hash the data because that is what will actually be signed + let client_data_digest = digest::digest(&digest::SHA256, &client_data); + let mut client_data_hash = [0u8; 32]; + client_data_hash.copy_from_slice(client_data_digest.as_ref()); let origin = application.to_string(); let ctap_args = RegisterArgs { - client_data_hash: chall_bytes, + client_data_hash, relying_party: RelyingParty { id: origin.clone(), name: None, @@ -170,7 +178,7 @@ pub fn generate_new_ssh_key( auth_data: raw_auth_data, auth_data_sig, intermediate, - challenge: chall_bytes.to_vec(), + challenge: client_data_hash.into(), alg, }; diff --git a/src/fido/verification.rs b/src/fido/verification.rs index 9ceb78a..06c465a 100644 --- a/src/fido/verification.rs +++ b/src/fido/verification.rs @@ -257,15 +257,13 @@ fn verify_intermediates( // Parse all the pems for pem in ca_pems { - let (_, parsed_pem) = parse_x509_pem(pem.as_bytes()) - .map_err(|_| Error::ParsingError)?; + let (_, parsed_pem) = parse_x509_pem(pem.as_bytes()).map_err(|_| Error::ParsingError)?; ca_parsed_pems.push(parsed_pem); } // Parse the root CA let root_ca = ca_parsed_pems.first().ok_or(Error::ParsingError)?; - let mut parent_ca = Pem::parse_x509(&root_ca) - .map_err(|_| Error::ParsingError)?; + let mut parent_ca = Pem::parse_x509(&root_ca).map_err(|_| Error::ParsingError)?; // Iteratively verify the chain for intermediate_ca in ca_parsed_pems.iter().skip(1) { @@ -295,9 +293,11 @@ fn verify_yubico_intermediate(parsed_intermediate: &X509Certificate<'_>) -> Resu vec![ YUBICO_ATTESTATION_ROOT_1, YUBICO_ATTESTATION_INTERMEDIATE_A_1, - YUBICO_FIDO_ATTESTATION_A_1 - ] - ).is_ok() { + YUBICO_FIDO_ATTESTATION_A_1, + ], + ) + .is_ok() + { return Ok(()); } @@ -307,8 +307,10 @@ fn verify_yubico_intermediate(parsed_intermediate: &X509Certificate<'_>) -> Resu YUBICO_ATTESTATION_ROOT_1, YUBICO_ATTESTATION_INTERMEDIATE_B_1, YUBICO_FIDO_ATTESTATION_B_1, - ] - ).is_ok() { + ], + ) + .is_ok() + { return Ok(()); } diff --git a/tests/fido-lite.rs b/tests/fido-lite.rs index e854d44..17b5581 100644 --- a/tests/fido-lite.rs +++ b/tests/fido-lite.rs @@ -6,27 +6,27 @@ 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, + 170, 175, 226, 60, 164, 159, 19, 51, 42, 0, 128, 98, 98, 229, 4, 183, 103, 139, 128, 52, 133, + 73, 67, 19, 182, 97, 7, 69, 202, 137, 239, 194, 92, 4, 4, 141, 211, 236, 86, 50, 174, 36, 25, + 121, 3, 188, 192, 34, 181, 231, 164, 247, 91, 236, 16, 48, 104, 25, 60, 88, 199, 131, 196, 98, + 193, 93, 133, 128, 173, 125, 249, 164, 163, 48, 61, 66, 63, 2, 30, 36, 176, 179, 86, 214, 43, + 196, 0, 17, 77, 172, 8, 144, 50, 203, 164, 224, 25, 33, 247, 107, 194, 50, 80, 86, 230, 12, + 200, 220, 208, 44, 31, 128, 145, 203, 205, 110, 249, 52, 73, 133, 207, 37, 198, 9, 146, 183, + 31, 150, 63, 228, 237, 64, 83, 253, 178, 185, 180, 182, 191, 164, 1, 1, 3, 39, 32, 6, 33, 88, + 32, 49, 157, 86, 25, 233, 182, 125, 243, 238, 178, 127, 166, 95, 30, 135, 219, 202, 200, 10, + 252, 115, 193, 102, 75, 210, 246, 2, 7, 27, 57, 51, 49, ]; -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_AUTH_SIG_ED25519: [u8; 72] = [ + 48, 70, 2, 33, 0, 253, 18, 60, 143, 204, 99, 61, 141, 84, 31, 48, 3, 97, 146, 206, 116, 205, + 23, 226, 251, 141, 172, 254, 200, 201, 200, 94, 174, 196, 232, 191, 156, 2, 33, 0, 236, 235, + 250, 190, 18, 41, 99, 204, 96, 21, 146, 184, 56, 77, 175, 88, 187, 93, 203, 175, 83, 3, 150, + 40, 245, 82, 192, 227, 128, 241, 184, 148, ]; 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, + 218, 43, 84, 240, 214, 90, 54, 239, 73, 96, 123, 70, 114, 116, 188, 66, 101, 174, 210, 190, + 106, 26, 124, 2, 48, 14, 158, 215, 185, 48, 59, 252, ]; const YUBIKEY_5C_NFC_5_7_4_INTERMEDIATE: [u8; 731] = [ @@ -40,29 +40,29 @@ const YUBIKEY_5C_NFC_5_7_4_INTERMEDIATE: [u8; 731] = [ 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, + 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] = [ @@ -260,10 +260,11 @@ fn verify_and_parse_auth_data_yubikey_5c_nfc() { /// 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 hash_challenge = digest::digest(&digest::SHA256, &YUBIKEY_5C_NFC_5_7_4_CHALLENGE_ED25519); 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, + &hash_challenge.as_ref(), -7, &YUBIKEY_5C_NFC_5_7_4_INTERMEDIATE, None, From 9e3f61a5638ebbbd662bc532a48b0066822729dd Mon Sep 17 00:00:00 2001 From: Mitchell Grenier Date: Sun, 13 Apr 2025 18:52:51 -0700 Subject: [PATCH 5/6] Bump version --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 6cc0653..02892bb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "sshcerts" -version = "0.13.2" +version = "0.14.0" authors = ["Mitchell Grenier "] edition = "2021" license-file = "LICENSE" From 393f3efc3442c37c06d0f86a23cd450343ac3479 Mon Sep 17 00:00:00 2001 From: Thanh Nguyen Date: Tue, 15 Apr 2025 21:20:36 -0400 Subject: [PATCH 6/6] Add new PIV root CA and intermediates --- src/fido/verification.rs | 4 +- src/yubikey/verification.rs | 211 +++++++++++++++++++++++++++++++++--- tests/yubikey-lite.rs | 109 +++++++++++++++++++ 3 files changed, 308 insertions(+), 16 deletions(-) diff --git a/src/fido/verification.rs b/src/fido/verification.rs index 06c465a..ddac684 100644 --- a/src/fido/verification.rs +++ b/src/fido/verification.rs @@ -287,7 +287,7 @@ fn verify_intermediates( /// Verify that the intermediate chains to some Yubico root CA for FIDO attestation /// We try all known Yubico Root CAs for backward compatibility -fn verify_yubico_intermediate(parsed_intermediate: &X509Certificate<'_>) -> Result<(), Error> { +fn verify_yubico_intermediates(parsed_intermediate: &X509Certificate<'_>) -> Result<(), Error> { if verify_intermediates( &parsed_intermediate, vec![ @@ -338,7 +338,7 @@ pub fn verify_auth_data( if let Some(pem) = root_pem { verify_intermediates(&parsed_intermediate, vec![pem])?; } else { - verify_yubico_intermediate(&parsed_intermediate)?; + verify_yubico_intermediates(&parsed_intermediate)?; } // Extract public key from verified intermediate certificate diff --git a/src/yubikey/verification.rs b/src/yubikey/verification.rs index 8722d91..73d55a5 100644 --- a/src/yubikey/verification.rs +++ b/src/yubikey/verification.rs @@ -6,7 +6,8 @@ use x509_parser::prelude::*; use std::convert::TryInto; -const YUBICO_PIV_ROOT_CA: &str = "-----BEGIN CERTIFICATE----- +/// From https://developers.yubico.com/PKI/yubico-ca-certs.txt +const YUBICO_PIV_ROOT_CA_263751: &str = "-----BEGIN CERTIFICATE----- MIIDFzCCAf+gAwIBAgIDBAZHMA0GCSqGSIb3DQEBCwUAMCsxKTAnBgNVBAMMIFl1 YmljbyBQSVYgUm9vdCBDQSBTZXJpYWwgMjYzNzUxMCAXDTE2MDMxNDAwMDAwMFoY DzIwNTIwNDE3MDAwMDAwWjArMSkwJwYDVQQDDCBZdWJpY28gUElWIFJvb3QgQ0Eg @@ -26,6 +27,116 @@ Fqyi4+JE014cSgR57Jcu3dZiehB6UtAPgad9L5cNvua/IWRmm+ANy3O2LH++Pyl8 SREzU8onbBsjMg9QDiSf5oJLKvd/Ren+zGY7 -----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_PIV_ATTESTATION_A_1: &str = "-----BEGIN CERTIFICATE----- +MIIDSTCCAjGgAwIBAgIUSiefkiKiicP9B63XwO7fKqevCkQwDQYJKoZIhvcNAQEL +BQAwLjEsMCoGA1UEAwwjWXViaWNvIEF0dGVzdGF0aW9uIEludGVybWVkaWF0ZSBB +IDEwIBcNMjQxMjAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMCUxIzAhBgNVBAMM +Gll1YmljbyBQSVYgQXR0ZXN0YXRpb24gQSAxMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAyGCyrZjNrdPfChdDe4JWd+4TMLr8nbugcKJz12egglWi7oy5 +L9GT99/if9i1OrONdpEt0YrCa+qMb+dJJ0WUa8M5zXYnUDpn72vhFjH+Anb9P9+v ++ZrRqaj/jnR/MYP7NpVpeLHiH2dRCe/PX/NH1XE41GvdUEncDtqUUGaXUea0DfDY +McRDpPT2Qn5e8rn9FjzDA37SbOVuws5VlFTDzDdqR0FnqeWeIW0DFu17rzCqXcaB +VRDnQLTc5EEPDTpiRrQE/Ag+7Wg9ieLrueos75YMQ1EIkfjL49OBVogU1A7kwRGv +OnG8l7sYaY8LZ2b5FROe2hKqmsIy600qjn6b/QIDAQABo2YwZDAdBgNVHQ4EFgQU +hAuLXXtpQVBkcsbqyFlj6LVAadgwHwYDVR0jBBgwFoAUIChQIRukWlvoU8udncXk +/Gwveh8wEgYDVR0TAQH/BAgwBgEB/wIBATAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAFxL/2oFjxkLh2KVnFKdhy7Nf7MmEfYXDDFSx1rFDn445jHO +UP5kxQPbZc9r53jdvL5W0SQBqBjqA95PYh0r1CPMFsFJdiFXli8Hf3NQ0bTkeFSN +G3LsQCOKMb+o2WjYU3vHkRVjKgKGLxysxxKxGfMUcXdJ0qM6ZVeRHehC2zy7XuI6 +TQn7/V0ZHXjk7So7dUV55xQde094/3cCTnh9Q3j2aqMjkGx6tDboCsz/+W+tne7W +nMHG92ZiAAmOkP2bABjan461Qty/qBXPHomkfjqNbjUTluPXiMLYKCXHIyKwdkX6 +cphouSMU3QOTsb35Y2PeWNk54xu+Eds/3nhRMso= +-----END CERTIFICATE-----"; + +/// From https://developers.yubico.com/PKI/yubico-intermediate.pem +const YUBICO_PIV_ATTESTATION_B_1: &str = "-----BEGIN CERTIFICATE----- +MIIDSTCCAjGgAwIBAgIUWVf2oJG+t1qP8t8TicWgJ2KYan4wDQYJKoZIhvcNAQEL +BQAwLjEsMCoGA1UEAwwjWXViaWNvIEF0dGVzdGF0aW9uIEludGVybWVkaWF0ZSBC +IDEwIBcNMjQxMjAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMCUxIzAhBgNVBAMM +Gll1YmljbyBQSVYgQXR0ZXN0YXRpb24gQiAxMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAv7WBL9/5AKxSpCMoL63183WqRtFrOHY7tdyuGtoidoYWQrxV +aV9S+ZwH0aynh0IzD5A/PvCtuxdtL5w2cAI3tgsborOlEert4IZ904CZQfq3ooar +1an/wssbtMpPOQkC3MQiqrUyHlFS2BTbuwbBXY66lSVX/tGRuUgnBdfBJtcQKS6M +O4bU5ndPQqhGPyzcyY1LvlfzK7KJ1r/bixCRFqjhJRnPs0Czpg6rkRrFgC6cd5bK +1UgTsJy+3wrIqkv4CeV3EhSVnhnQjZgIrdIcI5WZ8T1Oq3OhMlWmY0K0dy/oZdP/ +bpbG2qbyHLa6gprLT/qChQWLmffxn6D2DAB1zQIDAQABo2YwZDAdBgNVHQ4EFgQU +M0Nt3QHo7eGzaKMZn2SmXT74vpcwHwYDVR0jBBgwFoAU6rdCkJ4Me2R621R8A7p8 +Tp/YoWEwEgYDVR0TAQH/BAgwBgEB/wIBATAOBgNVHQ8BAf8EBAMCAYYwDQYJKoZI +hvcNAQELBQADggEBAI0HwoS84fKMUyIof1LdUXvyeAMmEwW7+nVETvxNNlTMuwv7 +zPJ4XZAm9Fv95tz9CqZBj6l1PAPQn6Zht9LQA92OF7W7buuXuxuusBTgLM0C1iX2 +CGXqY/k/uSNvi3ZYfrpd44TIrfrr8bCG9ux7B5ZCRqb8adDUm92Yz3lK1aX2M6Cw +jC9IZVTXQWhLyP8Ys3p7rb20CO2jJzV94deJ/+AsEb+bnCQImPat1GDKwrBosar+ +BxtU7k6kgkxZ0G384O59GFXqnwkbw2b5HhORvOsX7nhOUhePFufzi1vT1g8Tzbwr ++TUfTwo2biKHHcI762KGtp8o6Bcv5y8WgExFuWY= +-----END CERTIFICATE-----"; + /// Represents the collection of data that has been validated from /// the client leaf certificate, all the way up to the root CA. #[derive(Debug)] @@ -104,6 +215,80 @@ fn extract_certificate_extension_data( }) } +/// Verify that the intermediates are chained to the root CA. +fn verify_intermediates( + parsed_intermediate: &X509Certificate<'_>, + ca_pems: Vec<&str>, +) -> Result<(), Error> { + // There has to be at least the root CA + if ca_pems.is_empty() { + return Err(Error::InvalidSignature); + } + + let mut ca_parsed_pems = vec![]; + + // Parse all the pems + for pem in ca_pems { + let (_, parsed_pem) = parse_x509_pem(pem.as_bytes()).map_err(|_| Error::ParsingError)?; + ca_parsed_pems.push(parsed_pem); + } + + // Parse the root CA + let root_ca = ca_parsed_pems.first().ok_or(Error::ParsingError)?; + let mut parent_ca = Pem::parse_x509(&root_ca).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 Yubikey attestation certification and intermediate +/// certificate are valid against the Yubico Attestation Root CA. +fn verify_yubico_intermediates(parsed_intermediate: &X509Certificate<'_>) -> Result<(), Error> { + if verify_intermediates( + &parsed_intermediate, + vec![ + YUBICO_ATTESTATION_ROOT_1, + YUBICO_ATTESTATION_INTERMEDIATE_A_1, + YUBICO_PIV_ATTESTATION_A_1, + ], + ) + .is_ok() + { + return Ok(()); + } + + if verify_intermediates( + &parsed_intermediate, + vec![ + YUBICO_ATTESTATION_ROOT_1, + YUBICO_ATTESTATION_INTERMEDIATE_B_1, + YUBICO_PIV_ATTESTATION_B_1, + ], + ) + .is_ok() + { + return Ok(()); + } + + verify_intermediates(&parsed_intermediate, vec![YUBICO_PIV_ROOT_CA_263751]) +} + /// Verify a provided yubikey attestation certification and intermediate /// certificate are valid against the Yubico attestation Root CA. pub fn verify_certificate_chain( @@ -111,23 +296,21 @@ pub fn verify_certificate_chain( intermediate: &[u8], root_pem: Option<&str>, ) -> Result { - let root_ca_pem = root_pem.unwrap_or(YUBICO_PIV_ROOT_CA); + let (_, parsed_intermediate) = X509Certificate::from_der(intermediate) + .map_err(|_| Error::ParsingError)?; - // Parse the root ca - let (_, root_ca) = parse_x509_pem(root_ca_pem.as_bytes()).unwrap(); - let root_ca = Pem::parse_x509(&root_ca).unwrap(); + // If a custom root CA is provided, we use that for verification. + // If not, we will try all the known Yubico Root CAs for backward compatibility + if let Some(pem) = root_pem { + verify_intermediates(&parsed_intermediate, vec![pem])?; + } else { + verify_yubico_intermediates(&parsed_intermediate)?; + } - // Parse the certificates - let (_, parsed_intermediate) = - parse_x509_certificate(intermediate).map_err(|_| Error::ParsingError)?; + // Parse the client cert let (_, parsed_client) = parse_x509_certificate(client).map_err(|_| Error::ParsingError)?; - // Validate that the provided intermediate certificate is signed by the Yubico Attestation Root CA - parsed_intermediate - .verify_signature(Some(&root_ca.tbs_certificate.subject_pki)) - .map_err(|_| Error::InvalidSignature)?; - - // Validate that the provided certificate is signed by the intermediate CA + // Validate that the provided client certificate is signed by the intermediate CA parsed_client .verify_signature(Some(&parsed_intermediate.tbs_certificate.subject_pki)) .map_err(|_| Error::InvalidSignature)?; diff --git a/tests/yubikey-lite.rs b/tests/yubikey-lite.rs index da40ce1..3756b4a 100644 --- a/tests/yubikey-lite.rs +++ b/tests/yubikey-lite.rs @@ -93,6 +93,100 @@ const YUBIKEY_5CI_INTERMEDIATE: [u8; 746] = [ 0xaf, 0x34, 0x22, 0xad, 0x63, 0x3e, 0xf7, 0x66, 0xe6, 0x0c, ]; +const YUBIKEY_5C_5_7_4_ATTESTATION: [u8; 631] = [ + 0x30, 0x82, 0x02, 0x73, 0x30, 0x82, 0x01, 0x5b, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x10, 0x01, + 0xae, 0x6a, 0x32, 0xa8, 0x8e, 0x74, 0x5d, 0xba, 0x5d, 0xf3, 0x0c, 0x0e, 0xeb, 0x16, 0xfd, 0x30, + 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x22, + 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x17, 0x59, 0x75, 0x62, 0x69, 0x4b, + 0x65, 0x79, 0x20, 0x50, 0x49, 0x56, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x30, 0x20, 0x17, 0x0d, 0x32, 0x34, 0x31, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, + 0x30, 0x30, 0x5a, 0x18, 0x0f, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, + 0x39, 0x35, 0x39, 0x5a, 0x30, 0x25, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, + 0x1a, 0x59, 0x75, 0x62, 0x69, 0x4b, 0x65, 0x79, 0x20, 0x50, 0x49, 0x56, 0x20, 0x41, 0x74, 0x74, + 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x38, 0x32, 0x30, 0x76, 0x30, 0x10, 0x06, + 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x02, 0x01, 0x06, 0x05, 0x2b, 0x81, 0x04, 0x00, 0x22, 0x03, + 0x62, 0x00, 0x04, 0xd7, 0xb4, 0x2c, 0x12, 0x0e, 0xb2, 0x97, 0xb3, 0xf3, 0x2d, 0xe9, 0x7c, 0xb3, + 0x76, 0xe9, 0x77, 0xfd, 0x2e, 0x0c, 0x47, 0x97, 0xba, 0x28, 0xe7, 0xe8, 0x0e, 0x36, 0xae, 0x15, + 0xb1, 0x18, 0x0a, 0x09, 0xfc, 0xcd, 0x64, 0xb5, 0x1f, 0xa1, 0xb8, 0x00, 0x13, 0x69, 0x4c, 0x47, + 0x5c, 0x0f, 0xae, 0x55, 0xb1, 0x28, 0x65, 0x7c, 0xb4, 0xf7, 0x15, 0x63, 0x6a, 0xc1, 0x5e, 0x33, + 0x5b, 0x96, 0xd8, 0x0b, 0x5b, 0x53, 0x57, 0x1d, 0xb5, 0xf3, 0x26, 0xeb, 0xc9, 0x79, 0x07, 0x64, + 0xc4, 0xb2, 0x63, 0x6d, 0xb1, 0x7c, 0x21, 0x5c, 0x02, 0x86, 0x61, 0xf8, 0x3d, 0x60, 0xa4, 0x39, + 0x47, 0x3b, 0xe2, 0xa3, 0x4e, 0x30, 0x4c, 0x30, 0x11, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, + 0x82, 0xc4, 0x0a, 0x03, 0x03, 0x04, 0x03, 0x05, 0x07, 0x04, 0x30, 0x14, 0x06, 0x0a, 0x2b, 0x06, + 0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, 0x03, 0x07, 0x04, 0x06, 0x02, 0x04, 0x01, 0xfa, 0x05, 0x3a, + 0x30, 0x10, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, 0x03, 0x08, 0x04, 0x02, + 0x01, 0x02, 0x30, 0x0f, 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, 0x03, 0x09, + 0x04, 0x01, 0x03, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x01, 0x0b, + 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x5e, 0xb9, 0x1c, 0x19, 0x7b, 0x1a, 0x50, 0x7f, 0xed, + 0xc5, 0xaf, 0x2e, 0xa5, 0x72, 0xbf, 0x17, 0x9d, 0x0c, 0x5f, 0x1b, 0xa3, 0x7f, 0x11, 0x59, 0xea, + 0xe5, 0xb2, 0x03, 0xcd, 0x84, 0xa2, 0x69, 0x5f, 0x51, 0x43, 0xb7, 0x78, 0x42, 0xc7, 0xf9, 0x51, + 0x95, 0x4c, 0x86, 0x83, 0x8c, 0x73, 0xca, 0x79, 0x5c, 0x06, 0xfa, 0xa9, 0x38, 0xd0, 0xc3, 0xe6, + 0x4e, 0x94, 0x4f, 0x26, 0x1a, 0x8f, 0x5a, 0x17, 0x58, 0x0c, 0xf7, 0x54, 0x71, 0x00, 0x8c, 0x20, + 0x1b, 0x62, 0xd9, 0xb0, 0x4e, 0xd0, 0xe5, 0x07, 0x08, 0x68, 0x4c, 0x11, 0x83, 0xc5, 0x0e, 0x69, + 0x99, 0xbc, 0x78, 0xaf, 0xd5, 0xbe, 0xe8, 0x2c, 0x3b, 0xe5, 0xdd, 0xf6, 0x1d, 0x97, 0x1c, 0x81, + 0x93, 0x29, 0x20, 0x5d, 0xe7, 0x8a, 0xfd, 0x42, 0x7d, 0xe8, 0x72, 0x59, 0xcc, 0x33, 0xb9, 0x80, + 0x68, 0xc1, 0xa1, 0xa7, 0x6a, 0xc5, 0xa7, 0x54, 0xa3, 0xbe, 0xe8, 0x0f, 0xbd, 0xb3, 0x6e, 0x05, + 0x72, 0xe9, 0x2f, 0x40, 0xd5, 0x61, 0xd7, 0x5d, 0x85, 0xf1, 0xab, 0xa9, 0xd6, 0xf5, 0x27, 0xe2, + 0xd2, 0x22, 0x19, 0xd0, 0x1a, 0x21, 0xff, 0xbf, 0x53, 0xc5, 0xb5, 0x5d, 0x2d, 0xcb, 0xbf, 0x2d, + 0xf7, 0xd7, 0xe6, 0x77, 0xad, 0x90, 0xb9, 0xd7, 0x96, 0x95, 0x68, 0xc2, 0x3c, 0xf8, 0x73, 0xc1, + 0xc6, 0xd0, 0x7f, 0xb9, 0xf3, 0xb1, 0x43, 0x48, 0xa9, 0x62, 0x24, 0x5e, 0x91, 0x2b, 0x98, 0x6a, + 0xa3, 0xc9, 0x29, 0x5d, 0x0a, 0xc1, 0x03, 0x0c, 0xeb, 0xca, 0x61, 0x15, 0x0c, 0x00, 0xc8, 0x49, + 0x78, 0xbf, 0xb4, 0xc5, 0x19, 0xe2, 0x27, 0xf4, 0xe6, 0x14, 0xb6, 0xf4, 0x64, 0x32, 0xcf, 0x31, + 0xe0, 0x21, 0x22, 0x8c, 0xba, 0x96, 0xc5, 0xf8, 0x78, 0x84, 0x93, 0xd7, 0x01, 0x2b, 0xc4, 0xa0, + 0xb2, 0xe6, 0xb1, 0x40, 0x3e, 0xad, 0xfd, +]; + +const YUBIKEY_5C_5_7_4_INTERMEDIATE: [u8; 761] = [ + 0x30, 0x82, 0x02, 0xf5, 0x30, 0x82, 0x01, 0xdd, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x09, 0x00, + 0x95, 0xdc, 0x17, 0x99, 0x3b, 0x2d, 0xb7, 0xfc, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x0b, 0x05, 0x00, 0x30, 0x25, 0x31, 0x23, 0x30, 0x21, 0x06, 0x03, 0x55, + 0x04, 0x03, 0x0c, 0x1a, 0x59, 0x75, 0x62, 0x69, 0x63, 0x6f, 0x20, 0x50, 0x49, 0x56, 0x20, 0x41, + 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x42, 0x20, 0x31, 0x30, 0x20, + 0x17, 0x0d, 0x32, 0x34, 0x31, 0x32, 0x30, 0x31, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x5a, 0x18, + 0x0f, 0x39, 0x39, 0x39, 0x39, 0x31, 0x32, 0x33, 0x31, 0x32, 0x33, 0x35, 0x39, 0x35, 0x39, 0x5a, + 0x30, 0x22, 0x31, 0x20, 0x30, 0x1e, 0x06, 0x03, 0x55, 0x04, 0x03, 0x0c, 0x17, 0x59, 0x75, 0x62, + 0x69, 0x4b, 0x65, 0x79, 0x20, 0x50, 0x49, 0x56, 0x20, 0x41, 0x74, 0x74, 0x65, 0x73, 0x74, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00, 0x30, 0x82, 0x01, 0x0a, + 0x02, 0x82, 0x01, 0x01, 0x00, 0xb7, 0xc6, 0xb0, 0x99, 0x54, 0x26, 0x54, 0x85, 0x5b, 0xd5, 0x35, + 0x2e, 0x23, 0xc9, 0x7b, 0x62, 0x74, 0x1c, 0x4c, 0xb4, 0xc1, 0xa7, 0x31, 0x6b, 0x9d, 0xdc, 0xd1, + 0x41, 0x1e, 0xc6, 0xbf, 0xd9, 0xb6, 0x09, 0xab, 0x21, 0xff, 0xc2, 0x56, 0x1f, 0x24, 0xa8, 0x4e, + 0x2a, 0xa4, 0xcb, 0x39, 0x8a, 0x07, 0xb2, 0xab, 0x1a, 0x99, 0xa8, 0xb8, 0xdd, 0x36, 0xa6, 0x59, + 0x99, 0xd6, 0x05, 0x7c, 0x72, 0x4b, 0xe8, 0x02, 0x92, 0xa6, 0x90, 0x9c, 0x59, 0x89, 0xda, 0x1c, + 0xde, 0xab, 0x4a, 0x3e, 0x8e, 0x80, 0x1a, 0x62, 0x76, 0x0f, 0x6e, 0x69, 0x09, 0x4d, 0xc7, 0xed, + 0x37, 0x7d, 0x79, 0xfb, 0x18, 0x91, 0xd0, 0x85, 0x66, 0x20, 0xbd, 0x35, 0xfd, 0xa9, 0xcc, 0xe6, + 0x7e, 0x4a, 0xa1, 0xda, 0xb8, 0x78, 0x6b, 0x5e, 0x01, 0x83, 0x68, 0xa9, 0x80, 0x17, 0x8f, 0xc3, + 0x0b, 0xcf, 0x52, 0x27, 0x47, 0x49, 0xda, 0xb2, 0xb5, 0xf3, 0x20, 0xab, 0xe1, 0xf5, 0x63, 0x36, + 0xe8, 0xb9, 0xa9, 0x97, 0xc8, 0x77, 0x5c, 0xa5, 0x6f, 0xce, 0x3c, 0xf3, 0x9e, 0xa1, 0x3c, 0x2f, + 0xf9, 0x7e, 0x1f, 0xaf, 0x76, 0xe5, 0x76, 0x9c, 0x00, 0x06, 0xa1, 0xc3, 0x4f, 0xf9, 0x8c, 0xff, + 0x39, 0xe1, 0xd7, 0xa7, 0x61, 0x42, 0xee, 0xd0, 0x94, 0xf1, 0x60, 0x9c, 0x6d, 0xeb, 0x34, 0xae, + 0x9d, 0x74, 0x22, 0x1d, 0x56, 0xe1, 0x6c, 0xf8, 0xba, 0x98, 0x95, 0x4e, 0xd4, 0xb5, 0xef, 0x9f, + 0x41, 0xb5, 0x69, 0xf0, 0x58, 0xab, 0x6a, 0xfc, 0x5c, 0xcd, 0x46, 0xf5, 0x19, 0x14, 0x4a, 0x30, + 0xd9, 0x16, 0x1a, 0xd0, 0x3a, 0x9b, 0x93, 0xfc, 0xef, 0x3c, 0x80, 0xc3, 0xcb, 0xd4, 0x6e, 0x73, + 0x25, 0xb1, 0xf9, 0x63, 0xea, 0x96, 0x87, 0x03, 0x03, 0xa9, 0xcd, 0x77, 0xc0, 0x4f, 0x8f, 0xc6, + 0x93, 0xae, 0x54, 0x86, 0xa5, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x29, 0x30, 0x27, 0x30, 0x11, + 0x06, 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x82, 0xc4, 0x0a, 0x03, 0x03, 0x04, 0x03, 0x05, 0x07, + 0x04, 0x30, 0x12, 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, 0x04, 0x08, 0x30, 0x06, 0x01, + 0x01, 0xff, 0x02, 0x01, 0x00, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, + 0x01, 0x0b, 0x05, 0x00, 0x03, 0x82, 0x01, 0x01, 0x00, 0x4b, 0x32, 0x50, 0xcd, 0xc6, 0xc5, 0x40, + 0x96, 0x48, 0x79, 0x62, 0x4d, 0xf7, 0x05, 0xf7, 0xea, 0xf9, 0x04, 0x42, 0x0b, 0x7d, 0x1f, 0xf9, + 0xd9, 0xc7, 0x69, 0x45, 0xf6, 0x89, 0x59, 0x27, 0x56, 0xfb, 0xf6, 0xc7, 0x51, 0xf7, 0xdd, 0x19, + 0x64, 0x43, 0xe8, 0xf7, 0x24, 0x63, 0x70, 0x10, 0x06, 0x98, 0x1a, 0xa7, 0x7c, 0xc1, 0x83, 0x31, + 0x39, 0xca, 0xad, 0x8a, 0xbf, 0x70, 0x35, 0x6e, 0x70, 0x05, 0xa3, 0xa0, 0xc7, 0x8c, 0xf0, 0xa7, + 0xc1, 0xaa, 0x2c, 0xf6, 0x11, 0x66, 0xfd, 0x85, 0x62, 0x77, 0x18, 0xd6, 0xbe, 0x2d, 0x1b, 0xac, + 0x7c, 0x0a, 0x1c, 0x71, 0x86, 0x41, 0xd4, 0xa6, 0x43, 0x32, 0xc0, 0x1b, 0xbf, 0xda, 0x69, 0xba, + 0x5f, 0x69, 0x57, 0xc4, 0xba, 0xb4, 0x03, 0x04, 0x15, 0x28, 0x64, 0x09, 0xc1, 0xc5, 0xd3, 0x0f, + 0xb4, 0xb6, 0x92, 0xf8, 0x71, 0xa1, 0x01, 0x56, 0xd3, 0x83, 0x28, 0x7d, 0x82, 0x56, 0x6b, 0x7e, + 0xa6, 0x62, 0xfd, 0xaa, 0x0c, 0xe7, 0xb2, 0x41, 0xe7, 0x20, 0x59, 0xf0, 0x60, 0xb3, 0x51, 0xac, + 0xcd, 0xe8, 0x27, 0x44, 0xe5, 0x39, 0xc4, 0xb3, 0xea, 0x28, 0x55, 0x05, 0xdf, 0x40, 0xd3, 0xf6, + 0xc0, 0xd9, 0x0c, 0xd8, 0x83, 0x0e, 0x13, 0xf0, 0x8d, 0x5d, 0xaf, 0xd4, 0x56, 0x9f, 0x6c, 0xdc, + 0x91, 0xfb, 0x86, 0x5a, 0x16, 0xfd, 0x53, 0x68, 0xca, 0x72, 0xb9, 0x4a, 0x53, 0x2b, 0x27, 0xe6, + 0x9c, 0xa1, 0xe8, 0x4c, 0xdd, 0xf0, 0xcc, 0xe0, 0xd8, 0x08, 0xa4, 0x4f, 0xaa, 0xf1, 0xe8, 0xbc, + 0xfa, 0xc8, 0xd4, 0x93, 0xc3, 0x44, 0x76, 0x27, 0xbe, 0xbc, 0xf2, 0x04, 0x82, 0x94, 0xbc, 0x64, + 0x41, 0x97, 0xd7, 0x4d, 0xf4, 0x92, 0xe4, 0x5f, 0xc3, 0x8e, 0xc6, 0x1b, 0x80, 0x89, 0x10, 0xdf, + 0x5f, 0x6c, 0x1d, 0xa3, 0x6a, 0xac, 0xd4, 0xfc, 0xc8, +]; + #[test] pub fn test_5c_attestation_verification() { let attested_key_data = verification::verify_certificate_chain( @@ -107,3 +201,18 @@ pub fn test_5c_attestation_verification() { assert_eq!(attested_key_data.touch_policy, 3); assert_eq!(attested_key_data.pin_policy, 1); } + +#[test] +pub fn test_5c_5_7_4_attestation_verification() { + let attested_key_data = verification::verify_certificate_chain( + &YUBIKEY_5C_5_7_4_ATTESTATION, + &YUBIKEY_5C_5_7_4_INTERMEDIATE, + None, + ) + .unwrap(); + + assert_eq!(attested_key_data.serial, 33162554); + assert_eq!(attested_key_data.firmware, "5.7.4"); + assert_eq!(attested_key_data.touch_policy, 2); + assert_eq!(attested_key_data.pin_policy, 1); +}