summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--packages/server/package.json2
-rw-r--r--packages/server/src/helpers/isCertRevoked.ts26
-rw-r--r--packages/server/src/helpers/validateCertificatePath.ts2
-rw-r--r--packages/server/src/metadata/verifyAttestationWithMetadata.test.ts62
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);
+})