Describe the issue
verifyAttestationAndroidKey's fallback path (used when no FIDO MDS metadata statement is found for the authenticator's aaguid) rejects valid Android Key Attestation chains from devices using Remote Key Provisioning (RKP).
In February 2026 Google published a new self-signed root for Android Key Attestation, "Key Attestation CA1" (ECDSA P-384), which RKP-enabled devices have used exclusively since 2026-04-10 (see https://android.googleapis.com/attestation/root, which now returns both the legacy root and this new one).
With this change, the x5c array sent by RKP devices no longer includes the self-signed root certificate as its last element. Instead, x5c terminates in an intermediate certificate (e.g. CN=Droid CA2, O=Google LLC) signed by "Key Attestation CA1" — the root itself is never included in x5c (same practice TLS servers already follow by omitting the root).
The fallback code assumes the last certificate in x5c is the root, comparing it by exact string equality:
const x5cNoRootPEM = x5c.slice(0, -1).map(convertCertBufferToPEM);
const x5cRootPEM = x5c.slice(-1).map(convertCertBufferToPEM);
await validateCertificatePath(x5cNoRootPEM, x5cRootPEM);
if (rootCertificates.length > 0 && rootCertificates.indexOf(x5cRootPEM[0]) < 0) {
throw new Error('x5c root certificate was not a known root certificate (Android Key)');
}
Since Droid CA2 is never byte-identical to Key Attestation CA1, this always fails for RKP devices, even with the new root correctly configured via SettingsService.
Reproduction Steps
- Register a passkey via Android Credential Manager on an RKP-enabled device (most current Android hardware).
attestationObject has fmt = "android-key", x5c of 4 certs, last one CN=Droid CA2 issued by CN=Key Attestation CA1, OU=Android, O=Google LLC, C=US.
- Call
verifyRegistrationResponse with rootCertificates containing the new root.
- No FIDO MDS statement exists for this
aaguid, so the fallback path runs.
Expected behavior
Should verify successfully — the chain genuinely reaches the configured root, it's just not included in x5c. Instead throws: x5c root certificate was not a known root certificate (Android Key).
Code Samples + WebAuthn Options and Responses
Real (redacted) failure log:
fmt: android-key
aaguid: b93fd961-f2e6-462f-b122-82002247de78
x5cLength: 4
rootSubject: O=Google LLC, CN=Droid CA2
rootIssuer: CN=Key Attestation CA1, OU=Android, O=Google LLC, C=US
rootValidFrom: Feb 9 19:59:18 2026 GMT
rootValidTo: Feb 8 19:59:18 2029 GMT
Suggested fix (validated locally via patch-package on 13.3.2):
await validateCertificatePath(x5c.map(convertCertBufferToPEM), rootCertificates);
Dependencies
- OS: Android (RKP-enabled devices, current hardware/OS as of 2026)
- Browser: N/A (native Credential Manager)
- Authenticator: Android platform authenticator, aaguid
b93fd961-f2e6-462f-b122-82002247de78
SimpleWebAuthn Libraries
$ npm list --depth=0 | grep @simplewebauthn
├── @simplewebauthn/server@13.2.2 # originally reported on; also reproduced on 13.3.2
Additional context
Validated the fix with a positive test (leaf → intermediate → "Droid CA2", root omitted, against the real self-signed root) and a negative test (leaf → forged root, correctly rejected). Important: this fix is only safe on top of @simplewebauthn/server >= 13.3.2 — see the next message, there's a separate security issue in 13.2.2's validateCertificatePath that makes this same patch dangerous on that version.
Describe the issue
verifyAttestationAndroidKey's fallback path (used when no FIDO MDS metadata statement is found for the authenticator'saaguid) rejects valid Android Key Attestation chains from devices using Remote Key Provisioning (RKP).In February 2026 Google published a new self-signed root for Android Key Attestation, "Key Attestation CA1" (ECDSA P-384), which RKP-enabled devices have used exclusively since 2026-04-10 (see https://android.googleapis.com/attestation/root, which now returns both the legacy root and this new one).
With this change, the
x5carray sent by RKP devices no longer includes the self-signed root certificate as its last element. Instead,x5cterminates in an intermediate certificate (e.g.CN=Droid CA2, O=Google LLC) signed by "Key Attestation CA1" — the root itself is never included inx5c(same practice TLS servers already follow by omitting the root).The fallback code assumes the last certificate in
x5cis the root, comparing it by exact string equality:Since Droid CA2 is never byte-identical to Key Attestation CA1, this always fails for RKP devices, even with the new root correctly configured via
SettingsService.Reproduction Steps
attestationObjecthasfmt = "android-key",x5cof 4 certs, last oneCN=Droid CA2issued byCN=Key Attestation CA1, OU=Android, O=Google LLC, C=US.verifyRegistrationResponsewithrootCertificatescontaining the new root.aaguid, so the fallback path runs.Expected behavior
Should verify successfully — the chain genuinely reaches the configured root, it's just not included in
x5c. Instead throws:x5c root certificate was not a known root certificate (Android Key).Code Samples + WebAuthn Options and Responses
Real (redacted) failure log:
Suggested fix (validated locally via
patch-packageon 13.3.2):Dependencies
b93fd961-f2e6-462f-b122-82002247de78SimpleWebAuthn Libraries
Additional context
Validated the fix with a positive test (leaf → intermediate → "Droid CA2", root omitted, against the real self-signed root) and a negative test (leaf → forged root, correctly rejected). Important: this fix is only safe on top of
@simplewebauthn/server >= 13.3.2— see the next message, there's a separate security issue in 13.2.2'svalidateCertificatePaththat makes this same patch dangerous on that version.