@@ -72,6 +72,7 @@ func (v *verifier) verify(req VerificationRequest) (*Attestation, error) {
7272
7373 root := v .Root
7474 if root == nil {
75+ // Try verifying with the old root CA first
7576 ca , err := yubicoCA ()
7677 if err != nil {
7778 errs = append (errs , fmt .Errorf ("parsing YubiCo Root CA: %v" , err ))
@@ -82,9 +83,24 @@ func (v *verifier) verify(req VerificationRequest) (*Attestation, error) {
8283
8384 // Verify signatures:
8485 // The Attestation Signer Cert from the yubikey must be signed by YubiCo's attestation root
85- if err := verifySignature (root , req .AttestSignerCert ); err != nil {
86- errs = append (errs , fmt .Errorf ("attestation signer certificate is not signed by the YubiCo OpenPGP Root CA: %v" , err ))
86+ // Try old root first, then new root with intermediates
87+ err := verifySignature (root , req .AttestSignerCert )
88+ if err == nil {
89+ // Old chain verification succeeded
90+ goto verifyCert
8791 }
92+
93+ // If old root fails, try the new certificate chain:
94+ // Root -> Attestation Intermediate B 1 -> OPGP Attestation B 1 -> Device Signer
95+ if err := v .tryNewCertChain (req .AttestSignerCert ); err == nil {
96+ // New chain verification succeeded
97+ goto verifyCert
98+ }
99+
100+ // Both chains failed
101+ errs = append (errs , fmt .Errorf ("attestation signer certificate is not signed by the YubiCo OpenPGP Root CA: %v" , err ))
102+
103+ verifyCert:
88104 // The Attestation Cert must be signed by the Attestation Signer Cert
89105 if err := verifySignature (req .AttestSignerCert , req .AttestCert ); err != nil {
90106 errs = append (errs , fmt .Errorf ("attestation certificate not signed by device's attestation signer key: %v" , err ))
@@ -161,7 +177,41 @@ func verifySignature(parent, c *x509.Certificate) error {
161177 return parent .CheckSignature (c .SignatureAlgorithm , c .RawTBSCertificate , c .Signature )
162178}
163179
164- // yubicoPGPCAPEM is the PEM encoded attestation certificate used by Yubico for OpenPGP keys.
180+ // tryNewCertChain attempts to verify the attestation signer cert using the new (2024+) certificate chain.
181+ // Returns nil if verification succeeds, error otherwise.
182+ func (v * verifier ) tryNewCertChain (signerCert * x509.Certificate ) error {
183+ // Load all certificates in the new chain
184+ newRoot , err := yubicoNewRootCA ()
185+ if err != nil {
186+ return err
187+ }
188+
189+ attestInt , err := yubicoAttestationIntermediateB ()
190+ if err != nil {
191+ return err
192+ }
193+
194+ opgpInt , err := yubicoOPGPIntermediate ()
195+ if err != nil {
196+ return err
197+ }
198+
199+ // Verify: new root -> attestation intermediate B
200+ if err := verifySignature (newRoot , attestInt ); err != nil {
201+ return err
202+ }
203+
204+ // Verify: attestation intermediate B -> OPGP intermediate
205+ if err := verifySignature (attestInt , opgpInt ); err != nil {
206+ return err
207+ }
208+
209+ // Verify: OPGP intermediate -> attestation signer cert
210+ return verifySignature (opgpInt , signerCert )
211+ }
212+
213+ // yubicoPGPCAPEM is the legacy PEM encoded attestation certificate used by Yubico for OpenPGP keys.
214+ // This is the original root CA used by older YubiKeys (pre-2024).
165215//
166216// https://developers.yubico.com/PGP/Attestation.html
167217// https://github.com/Yubico/developers.yubico.com/blob/master/content/PGP/Attestation.adoc
@@ -187,10 +237,110 @@ HhK6CLTg2MfwT0NxS3Am76k2opXSqbk8k5nnNFSYFuvgxunQxUOB+3M+gWHmVTh8
1872377yaamyNndwmhhIAgeA==
188238-----END CERTIFICATE-----`
189239
240+ // yubicoNewRootCAPEM is the new Yubico Attestation Root 1 issued on 2024-12-01.
241+ // Newer YubiKeys (2024+) use a new certificate chain with this root.
242+ //
243+ // https://developers.yubico.com/PKI/yubico-ca-1.pem
244+ const yubicoNewRootCAPEM = `-----BEGIN CERTIFICATE-----
245+ MIIDPjCCAiagAwIBAgIUXzeiEDJEOTt14F5n0o6Zf/bBwiUwDQYJKoZIhvcNAQEN
246+ BQAwJDEiMCAGA1UEAwwZWXViaWNvIEF0dGVzdGF0aW9uIFJvb3QgMTAgFw0yNDEy
247+ MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowJDEiMCAGA1UEAwwZWXViaWNvIEF0
248+ dGVzdGF0aW9uIFJvb3QgMTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
249+ AMZ6/TxM8rIT+EaoPvG81ontMOo/2mQ2RBwJHS0QZcxVaNXvl12LUhBZ5LmiBScI
250+ Zd1Rnx1od585h+/dhK7hEm7JAALkKKts1fO53KGNLZujz5h3wGncr4hyKF0G74b/
251+ U3K9hE5mGND6zqYchCRAHfrYMYRDF4YL0X4D5nGdxvppAy6nkEmtWmMnwO3i0TAu
252+ csrbE485HvGM4r0VpgVdJpvgQjiTJCTIq+D35hwtT8QDIv+nGvpcyi5wcIfCkzyC
253+ imJukhYy6KoqNMKQEdpNiSOvWyDMTMt1bwCvEzpw91u+msUt4rj0efnO9s0ZOwdw
254+ MRDnH4xgUl5ZLwrrPkfC1/0CAwEAAaNmMGQwHQYDVR0OBBYEFNLu71oijTptXCOX
255+ PfKF1SbxJXuSMB8GA1UdIwQYMBaAFNLu71oijTptXCOXPfKF1SbxJXuSMBIGA1Ud
256+ EwEB/wQIMAYBAf8CAQMwDgYDVR0PAQH/BAQDAgGGMA0GCSqGSIb3DQEBDQUAA4IB
257+ AQC3IW/sgB9pZ8apJNjxuGoX+FkILks0wMNrdXL/coUvsrhzsvl6mePMrbGJByJ1
258+ XnquB5sgcRENFxdQFma3mio8Upf1owM1ZreXrJ0mADG2BplqbJnxiyYa+R11reIF
259+ TWeIhMNcZKsDZrFAyPuFjCWSQvJmNWe9mFRYFgNhXJKkXIb5H1XgEDlwiedYRM7V
260+ olBNlld6pRFKlX8ust6OTMOeADl2xNF0m1LThSdeuXvDyC1g9+ILfz3S6OIYgc3i
261+ roRcFD354g7rKfu67qFAw9gC4yi0xBTPrY95rh4/HqaUYCA/L8ldRk6H7Xk35D+W
262+ Vpmq2Sh/xT5HiFuhf4wJb0bK
263+ -----END CERTIFICATE-----`
264+
265+ // yubicoAttestationIntermediateBPEM is the middle intermediate in the new cert chain.
266+ // Chain: Root -> Attestation Intermediate B 1 -> OPGP Attestation B 1 -> Device Signer
267+ //
268+ // https://developers.yubico.com/PKI/yubico-intermediate.pem
269+ const yubicoAttestationIntermediateBPEM = `-----BEGIN CERTIFICATE-----
270+ MIIDSDCCAjCgAwIBAgIUDqERw+4RnGSggxgUewJFEPDRZ3YwDQYJKoZIhvcNAQEL
271+ BQAwJDEiMCAGA1UEAwwZWXViaWNvIEF0dGVzdGF0aW9uIFJvb3QgMTAgFw0yNDEy
272+ MDEwMDAwMDBaGA85OTk5MTIzMTIzNTk1OVowLjEsMCoGA1UEAwwjWXViaWNvIEF0
273+ dGVzdGF0aW9uIEludGVybWVkaWF0ZSBCIDEwggEiMA0GCSqGSIb3DQEBAQUAA4IB
274+ DwAwggEKAoIBAQDI7XnH+ZvDwMCQU8M8ZeV5qscublvVYaaRt3Ybaxn9godLx5sw
275+ H0lXrdgjh5h7FpVgCgYYX7E4bl1vbzULemrMWT8N3WMGUe8QAJbBeioV7W/E+hTZ
276+ P/0SKJVa3ewKBo6ULeMnfQZDrVORAk8wTLq2v5Llj5vMj7JtOotKa9J7nHS8kLmz
277+ XXSaj0SwEPh5OAZUTNV4zs1bvoTAQQWrL4/J9QuKt6WCFE5nUNiRQcEbVF8mlqK2
278+ bx2z6okVltyDVLCxYbpUTELvY1usR3DTGPUoIClOm4crpwnDRLVHvjYePGBB//pE
279+ yzxA/gcScxjwaH1ZUw9bnSbHyurKqbTa1KvjAgMBAAGjZjBkMB0GA1UdDgQWBBTq
280+ t0KQngx7ZHrbVHwDunxOn9ihYTAfBgNVHSMEGDAWgBTS7u9aIo06bVwjlz3yhdUm
281+ 8SV7kjASBgNVHRMBAf8ECDAGAQH/AgECMA4GA1UdDwEB/wQEAwIBhjANBgkqhkiG
282+ 9w0BAQsFAAOCAQEAqQaCWMxTGqVVX7Sk7kkJmUueTSYKuU6+KBBSgwIRnlw9K7He
283+ 1IpxZ0hdwpPNikKjmcyFgFPzhImwHJgxxuT90Pw3vYOdcJJNktDg35PXOfzSn15c
284+ FAx1RO0mPTmIb8dXiEWOpzoXvdwXDM41ZaCDYMT7w4IQtMyvE7xUBZq2bjtAnq/N
285+ DUA7be4H8H3ipC+/+NKlUrcUh+j48K67WI0u1m6FeQueBA7n06j825rqDqsaLs9T
286+ b7KAHAw8PmrWaNPG2kjKerxPEfecivlFawp2RWZvxrVtn3TV2SBxyCJCkXsND05d
287+ CErVHSJIs+BdtTVNY9AwtyPmnyb0v4mSTzvWdw==
288+ -----END CERTIFICATE-----`
289+
290+ // yubicoOPGPIntermediatePEM contains the intermediate certificate used in the new cert chain.
291+ // This intermediate is signed by Yubico Attestation Intermediate B 1 which is signed by
292+ // Yubico Attestation Root 1, and signs the device attestation signers.
293+ //
294+ // https://developers.yubico.com/PKI/yubico-intermediate.pem
295+ const yubicoOPGPIntermediatePEM = `-----BEGIN CERTIFICATE-----
296+ MIIDSjCCAjKgAwIBAgIUbeEhxjsv7XjQwdAQIi5G5i+4qhIwDQYJKoZIhvcNAQEL
297+ BQAwLjEsMCoGA1UEAwwjWXViaWNvIEF0dGVzdGF0aW9uIEludGVybWVkaWF0ZSBC
298+ IDEwIBcNMjQxMjAxMDAwMDAwWhgPOTk5OTEyMzEyMzU5NTlaMCYxJDAiBgNVBAMM
299+ G1l1YmljbyBPUEdQIEF0dGVzdGF0aW9uIEIgMTCCASIwDQYJKoZIhvcNAQEBBQAD
300+ ggEPADCCAQoCggEBAMe9oJ6kuLQOlnUoyWzDaum4m23s3cR5jn0gVQSV6VPsQP8Q
301+ d7wYiW/GiDUPAT4N/NqKdhcqX/5hazrbsKA+gCDU1E+zWunl0J0Fo5B0OCXQfxtA
302+ 0LhFHORvpJ1yz7HsRgEYScO7/rO2ip0bPbaKy4MG4UhyzKgzwmujOO7nmf6BcMil
303+ 8ZZRJbQOuEWsignM5EKuCrymyK3+R9Y+8NGjh/zb14Not9+JvwDgUYnHW+hip9si
304+ UOzC2X8QYA/yBUCqTYGUePfC4ZOB0ZSi/HYtxhSnOTcDY6C+AcFnOCvCKD8t4Rdd
305+ z6dFJINQgsATnfHycB22cUamIB9hBb9xXZYg36sCAwEAAaNmMGQwHQYDVR0OBBYE
306+ FI1QCVLy1KcdxIkdZMMkn+wzyN0XMB8GA1UdIwQYMBaAFOq3QpCeDHtkettUfAO6
307+ fE6f2KFhMBIGA1UdEwEB/wQIMAYBAf8CAQEwDgYDVR0PAQH/BAQDAgGGMA0GCSqG
308+ SIb3DQEBCwUAA4IBAQCRtalpNipOThRLO8o0/4WVLIjlC8yiPBLsVMuXHuXhTdhW
309+ ubRUSazhHr7tTRShPJ/OeWiiap9aZtZe7FUgTIOdaR0oI4Tp5Cu4TUJTLQEUqtA9
310+ HSU6bP485aRJi26hDD+h2AYplmEeVNEWj8PUIAp3N8mKMMqIkjB7d0QN14fze/Nb
311+ REzHU6SVvuJo11jfHpJTfpbpCqvcVl8bMPUbdtOvqc1ibkj7O7OmTDACqTT1f3yQ
312+ Zj0PbreP1qN9jv7kDAxT9O2yVSgXNXbz/Ygl121TkGWjXRQ8B3PW2Z3+n7B8ETAd
313+ 8fJ0/5guPgvO2VQHQv8H9U3tsqSq/siosMJ8KtS5
314+ -----END CERTIFICATE-----`
315+
190316func yubicoCA () (* x509.Certificate , error ) {
191317 b , _ := pem .Decode ([]byte (yubicoPGPCAPEM ))
192318 if b == nil {
193319 return nil , fmt .Errorf ("failed to decode yubico pem data" )
194320 }
195321 return x509 .ParseCertificate (b .Bytes )
196322}
323+
324+ func yubicoNewRootCA () (* x509.Certificate , error ) {
325+ b , _ := pem .Decode ([]byte (yubicoNewRootCAPEM ))
326+ if b == nil {
327+ return nil , fmt .Errorf ("failed to decode new yubico root CA pem data" )
328+ }
329+ return x509 .ParseCertificate (b .Bytes )
330+ }
331+
332+ func yubicoAttestationIntermediateB () (* x509.Certificate , error ) {
333+ b , _ := pem .Decode ([]byte (yubicoAttestationIntermediateBPEM ))
334+ if b == nil {
335+ return nil , fmt .Errorf ("failed to decode yubico attestation intermediate B pem data" )
336+ }
337+ return x509 .ParseCertificate (b .Bytes )
338+ }
339+
340+ func yubicoOPGPIntermediate () (* x509.Certificate , error ) {
341+ b , _ := pem .Decode ([]byte (yubicoOPGPIntermediatePEM ))
342+ if b == nil {
343+ return nil , fmt .Errorf ("failed to decode yubico OPGP intermediate pem data" )
344+ }
345+ return x509 .ParseCertificate (b .Bytes )
346+ }
0 commit comments