diff options
-rw-r--r-- | packages/server/package.json | 2 | ||||
-rw-r--r-- | packages/server/src/helpers/isCertRevoked.ts | 26 | ||||
-rw-r--r-- | packages/server/src/helpers/validateCertificatePath.ts | 2 | ||||
-rw-r--r-- | packages/server/src/metadata/verifyAttestationWithMetadata.test.ts | 62 |
4 files changed, 83 insertions, 9 deletions
diff --git a/packages/server/package.json b/packages/server/package.json index 3910672..55e7918 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -64,7 +64,7 @@ "devDependencies": { "@simplewebauthn/typescript-types": "*", "@types/debug": "^4.1.7", - "@types/jsrsasign": "^8.0.13", + "@types/jsrsasign": "^10.5.4", "@types/node": "^18.11.9" } } 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/helpers/validateCertificatePath.ts b/packages/server/src/helpers/validateCertificatePath.ts index ed82eac..661c9e7 100644 --- a/packages/server/src/helpers/validateCertificatePath.ts +++ b/packages/server/src/helpers/validateCertificatePath.ts @@ -118,7 +118,7 @@ async function _validatePath(certificates: string[]): Promise<boolean> { const Signature = new crypto.Signature({ alg }); Signature.init(issuerPem); // TODO: `updateHex()` takes approximately two seconds per execution, can we improve this? - Signature.updateHex(subjectCertStruct); + Signature.updateHex(subjectCertStruct ?? ''); if (!Signature.verify(signatureHex)) { throw new Error('Invalid certificate path: invalid signature'); 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); +}) |