diff options
author | Matthew Miller <matthew@millerti.me> | 2022-12-15 09:01:30 -0800 |
---|---|---|
committer | Matthew Miller <matthew@millerti.me> | 2022-12-15 09:01:30 -0800 |
commit | 83cfe7c5740ea7b2d57467c346d696877e3afd14 (patch) | |
tree | d122f13ac60dfe58065f26523a4bace972983d92 /packages/server/src | |
parent | cf678437a3c521d58d520e97cba3585bf7c62985 (diff) |
Check Subject Key ID if Auth Key ID isn't set
This means we're potentially dealing with self-signed root certificate. This is a slight tweak based on a deeper reading of RFC 5280 Section 4.2.1.1 and 4.2.1.2
Diffstat (limited to 'packages/server/src')
-rw-r--r-- | packages/server/src/helpers/isCertRevoked.ts | 26 | ||||
-rw-r--r-- | packages/server/src/metadata/verifyAttestationWithMetadata.test.ts | 62 |
2 files changed, 81 insertions, 7 deletions
diff --git a/packages/server/src/helpers/isCertRevoked.ts b/packages/server/src/helpers/isCertRevoked.ts index 1ea3a8a..2d7f5d6 100644 --- a/packages/server/src/helpers/isCertRevoked.ts +++ b/packages/server/src/helpers/isCertRevoked.ts @@ -27,15 +27,27 @@ export async function isCertRevoked(cert: X509): Promise<boolean> { const certSerialHex = cert.getSerialNumberHex(); // Check to see if we've got cached info for the cert's CA - let certAuthKeyID: { kid: { hex: string } } | null = null; + let keyIdentifier: jsrsasign.AuthorityKeyIdentifierResult | jsrsasign.ExtSubjectKeyIdentifier | undefined = undefined; try { - certAuthKeyID = cert.getExtAuthorityKeyIdentifier() as { kid: { hex: string } } | null; + keyIdentifier = cert.getExtAuthorityKeyIdentifier(); } catch (err) { - return false; + // pass + } + + /** + * We might be dealing with a self-signed root certificate. Check the + * Subject key Identifier extension next. + */ + if (!keyIdentifier) { + try { + keyIdentifier = cert.getExtSubjectKeyIdentifier(); + } catch (err) { + // pass + } } - if (certAuthKeyID) { - const cached = cacheRevokedCerts[certAuthKeyID.kid.hex]; + if (keyIdentifier) { + const cached = cacheRevokedCerts[keyIdentifier.kid.hex]; if (cached) { const now = new Date(); // If there's a nextUpdate then make sure we're before it @@ -91,8 +103,8 @@ export async function isCertRevoked(cert: X509): Promise<boolean> { } // Cache the results - if (certAuthKeyID) { - cacheRevokedCerts[certAuthKeyID.kid.hex] = newCached; + if (keyIdentifier) { + cacheRevokedCerts[keyIdentifier.kid.hex] = newCached; } return newCached.revokedCerts.indexOf(certSerialHex) >= 0; diff --git a/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts b/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts index c76fb1d..f0a1bdb 100644 --- a/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts +++ b/packages/server/src/metadata/verifyAttestationWithMetadata.test.ts @@ -167,3 +167,65 @@ test('should not validate certificate path when authenticator is self-referencin expect(verified).toEqual(true); }); + +test('should verify idmelon attestation with updated root certificate', async () => { + /** + * See https://github.com/MasterKale/SimpleWebAuthn/issues/302 for more context, basically + * IDmelon's root cert in FIDO MDS was missing an extension. I worked with IDmelon to generate a + * new root certificate, which is included below and will eventually get rolled out via FIDO MDS. + */ + const metadataStatement: MetadataStatement = { + "legalHeader": "Submission of this statement and retrieval and use of this statement indicates acceptance of the appropriate agreement located at https://fidoalliance.org/metadata/metadata-legal-terms/.", + "aaguid": "820d89ed-d65a-409e-85cb-f73f0578f82a", + "description": "Vancosys iOS Authenticator", + "authenticatorVersion": 2, + "protocolFamily": "fido2", + "schema": 3, + "upv": [{ "major": 1, "minor": 0 }], + "authenticationAlgorithms": ["secp256r1_ecdsa_sha256_raw"], + "publicKeyAlgAndEncodings": ["cose"], + "attestationTypes": ["basic_full"], + "userVerificationDetails": [ + [ + { "userVerificationMethod": "faceprint_internal" }, + { "userVerificationMethod": "voiceprint_internal" }, + { "userVerificationMethod": "passcode_internal" }, + { "userVerificationMethod": "eyeprint_internal" }, + { "userVerificationMethod": "handprint_internal" }, + { "userVerificationMethod": "fingerprint_internal" }, + { "userVerificationMethod": "pattern_internal" }, + { "userVerificationMethod": "location_internal" }, + { "userVerificationMethod": "presence_internal" }, + ] + ], + "keyProtection": ["hardware", "secure_element"], + "matcherProtection": ["on_chip"], + "cryptoStrength": 128, + "attachmentHint": ["external"], + "tcDisplay": [], + "attestationRootCertificates": [ + "MIIByzCCAXGgAwIBAgIJANmMNK6jVpuuMAoGCCqGSM49BAMCMEExJDAiBgNVBAoMG1ZhbmNvc3lzIERhdGEgU2VjdXJpdHkgSW5jLjEZMBcGA1UEAwwQVmFuY29zeXMgUm9vdCBDQTAgFw0yMjEyMTQxODQxMDlaGA8yMDcyMTIwMTE4NDEwOVowQTEkMCIGA1UECgwbVmFuY29zeXMgRGF0YSBTZWN1cml0eSBJbmMuMRkwFwYDVQQDDBBWYW5jb3N5cyBSb290IENBMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEalYgEopnKScAm+d9f1XpGB3zbkZCD3hZEKuxTclpBYlj4ypNRg0gMSa7geBgd6nck50YaVhdy75uIc2wbWX8t6NQME4wHQYDVR0OBBYEFOxyf0cDs8Yl+VnWSZ1uYJAKkFeVMB8GA1UdIwQYMBaAFOxyf0cDs8Yl+VnWSZ1uYJAKkFeVMAwGA1UdEwQFMAMBAf8wCgYIKoZIzj0EAwIDSAAwRQIhAO2XuiRDXxy/UkWhsuZQYNUXeOj08AeTWADAqXvcA30hAiBi2cdGd61PNwHDTYjXPenPcD8S0rFTDncNWfs3E/WDXA==" + ], + "icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAMAAABEpIrGAAAAM1BMVEUtmc3y+fyWzOZis9rK5fI6n9B8v+Cw2ezl8vlHptNVrNbX7Paj0ulvud293++JxuP///89HRvpAAAAEXRSTlP/////////////////////ACWtmWIAAABsSURBVHgBxdPBCoAwDIPh/yDise//tIIQCZo6RNGdtuWDstFSg/UOgMiADQBJ6J4iCwS4BgzBuEQHCoFa+mdM+qijsDMVhBfdoRFaAL4nAe6AeghODYPnsaNyLuAqg5AHwO9AYu5BmqEPhncFmecvM5KKQHMAAAAASUVORK5CYII=", + "authenticatorGetInfo": { + "versions": ["FIDO_2_0"], + "extensions": ["hmac-secret"], + "aaguid": "820d89edd65a409e85cbf73f0578f82a", + "options": { "plat": false, "rk": true, "up": true, "uv": true }, + "maxMsgSize": 2048 + } + }; + + const x5c = [ + 'MIIB6TCCAY+gAwIBAgIJAJz56pzvu76hMAoGCCqGSM49BAMCMEExJDAiBgNVBAoMG1ZhbmNvc3lzIERhdGEgU2VjdXJpdHkgSW5jLjEZMBcGA1UEAwwQVmFuY29zeXMgUm9vdCBDQTAgFw0xODEyMjIxNzQzMjhaGA8yMDY4MTIwOTE3NDMyOFowfDELMAkGA1UEBhMCQ0ExJDAiBgNVBAoMG1ZhbmNvc3lzIERhdGEgU2VjdXJpdHkgSW5jLjEiMCAGA1UECwwZQXV0aGVudGljYXRvciBBdHRlc3RhdGlvbjEjMCEGA1UEAwwaVmFuY29zeXMgaU9TIEF1dGhlbnRpY2F0b3IwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAATZVzbtmSJbzsoN6mfdD+t1LV52zB+LWN0UusmV9+sRmfNB49adqPQ+h0JOlEwfL4zkbMmuDr6JhBRKJ5/c0SkeozMwMTAMBgNVHRMBAf8EAjAAMCEGCysGAQQBguUcAQEEBBIEEIINie3WWkCehcv3PwV4+CowCgYIKoZIzj0EAwIDSAAwRQIgV7++U2fQyy6Qido7fDhsi5Grrt76LTgZ5XJlA9UKEVECIQDJO0YHevdU77VlZ+Of58oKMjWD3SkzC1SWSlhl3nezHQ==' + ]; + const credentialPublicKey = 'pQECAyYgASFYINuJbeLdkZwgKtUw2VSopICTTO5PKdj95GXJ7JCsQi7iIlggxygEp0_P0oMXhfw2BjtL0M7-yIpnk5uSHc0oNkXfdJw'; + + const verified = await verifyAttestationWithMetadata({ + statement: metadataStatement, + credentialPublicKey: isoBase64URL.toBuffer(credentialPublicKey), + x5c, + }); + + expect(verified).toEqual(true); +}) |