@simplewebauthn/server 14.0.0 → 14.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/esm/authentication/verifyAuthenticationResponse.d.ts.map +1 -1
  2. package/esm/authentication/verifyAuthenticationResponse.js +12 -0
  3. package/esm/errors/index.d.ts +17 -0
  4. package/esm/errors/index.d.ts.map +1 -0
  5. package/esm/errors/index.js +24 -0
  6. package/esm/helpers/convertX509PublicKeyToCOSE.d.ts +1 -1
  7. package/esm/helpers/convertX509PublicKeyToCOSE.d.ts.map +1 -1
  8. package/esm/helpers/convertX509PublicKeyToCOSE.js +22 -6
  9. package/esm/helpers/cose.d.ts +1 -0
  10. package/esm/helpers/cose.d.ts.map +1 -1
  11. package/esm/helpers/cose.js +3 -0
  12. package/esm/helpers/isCertRevoked.d.ts +2 -1
  13. package/esm/helpers/isCertRevoked.d.ts.map +1 -1
  14. package/esm/helpers/isCertRevoked.js +91 -70
  15. package/esm/helpers/iso/isoCrypto/verifyAKP.d.ts.map +1 -1
  16. package/esm/helpers/iso/isoCrypto/verifyAKP.js +2 -1
  17. package/esm/helpers/mapX509SignatureAlgToCOSEAlg.d.ts +0 -3
  18. package/esm/helpers/mapX509SignatureAlgToCOSEAlg.d.ts.map +1 -1
  19. package/esm/helpers/mapX509SignatureAlgToCOSEAlg.js +19 -10
  20. package/esm/helpers/validateCertificatePath.d.ts +1 -0
  21. package/esm/helpers/validateCertificatePath.d.ts.map +1 -1
  22. package/esm/helpers/validateCertificatePath.js +94 -52
  23. package/esm/index.d.ts +1 -0
  24. package/esm/index.d.ts.map +1 -1
  25. package/esm/index.js +1 -0
  26. package/esm/metadata/verifyAttestationWithMetadata.d.ts.map +1 -1
  27. package/esm/metadata/verifyAttestationWithMetadata.js +1 -7
  28. package/esm/registration/verifications/verifyAttestationPacked.js +1 -1
  29. package/esm/registration/verifyRegistrationResponse.d.ts.map +1 -1
  30. package/esm/registration/verifyRegistrationResponse.js +19 -8
  31. package/package.json +4 -2
  32. package/script/authentication/verifyAuthenticationResponse.d.ts.map +1 -1
  33. package/script/authentication/verifyAuthenticationResponse.js +12 -0
  34. package/script/errors/index.d.ts +17 -0
  35. package/script/errors/index.d.ts.map +1 -0
  36. package/script/errors/index.js +29 -0
  37. package/script/helpers/convertX509PublicKeyToCOSE.d.ts +1 -1
  38. package/script/helpers/convertX509PublicKeyToCOSE.d.ts.map +1 -1
  39. package/script/helpers/convertX509PublicKeyToCOSE.js +21 -5
  40. package/script/helpers/cose.d.ts +1 -0
  41. package/script/helpers/cose.d.ts.map +1 -1
  42. package/script/helpers/cose.js +4 -0
  43. package/script/helpers/isCertRevoked.d.ts +2 -1
  44. package/script/helpers/isCertRevoked.d.ts.map +1 -1
  45. package/script/helpers/isCertRevoked.js +91 -70
  46. package/script/helpers/iso/isoCrypto/verifyAKP.d.ts.map +1 -1
  47. package/script/helpers/iso/isoCrypto/verifyAKP.js +2 -1
  48. package/script/helpers/mapX509SignatureAlgToCOSEAlg.d.ts +0 -3
  49. package/script/helpers/mapX509SignatureAlgToCOSEAlg.d.ts.map +1 -1
  50. package/script/helpers/mapX509SignatureAlgToCOSEAlg.js +19 -10
  51. package/script/helpers/validateCertificatePath.d.ts +1 -0
  52. package/script/helpers/validateCertificatePath.d.ts.map +1 -1
  53. package/script/helpers/validateCertificatePath.js +94 -52
  54. package/script/index.d.ts +1 -0
  55. package/script/index.d.ts.map +1 -1
  56. package/script/index.js +1 -0
  57. package/script/metadata/verifyAttestationWithMetadata.d.ts.map +1 -1
  58. package/script/metadata/verifyAttestationWithMetadata.js +1 -7
  59. package/script/registration/verifications/verifyAttestationPacked.js +1 -1
  60. package/script/registration/verifyRegistrationResponse.d.ts.map +1 -1
  61. package/script/registration/verifyRegistrationResponse.js +22 -11
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isCertRevoked = isCertRevoked;
4
+ require("reflect-metadata");
4
5
  const x509_1 = require("@peculiar/x509");
5
6
  const fetch_js_1 = require("./fetch.js");
6
7
  const cacheRevokedCerts = {};
@@ -10,90 +11,110 @@ const cacheRevokedCerts = {};
10
11
  *
11
12
  * CRL certificate structure referenced from https://tools.ietf.org/html/rfc5280#page-117
12
13
  */
13
- async function isCertRevoked(cert) {
14
- const { extensions } = cert;
14
+ async function isCertRevoked(certificate, issuer) {
15
+ if (!issuer) {
16
+ // We don't have a public key to verify any CRL signature so don't bother continuing
17
+ return false;
18
+ }
19
+ const { extensions } = certificate;
15
20
  if (!extensions) {
16
21
  return false;
17
22
  }
18
- let extAuthorityKeyID;
19
- let extSubjectKeyID;
20
- let extCRLDistributionPoints;
21
- extensions.forEach((ext) => {
22
- if (ext instanceof x509_1.AuthorityKeyIdentifierExtension) {
23
- extAuthorityKeyID = ext;
24
- }
25
- else if (ext instanceof x509_1.SubjectKeyIdentifierExtension) {
26
- extSubjectKeyID = ext;
27
- }
28
- else if (ext instanceof x509_1.CRLDistributionPointsExtension) {
29
- extCRLDistributionPoints = ext;
30
- }
23
+ const extCRLDistributionPoints = extensions.find((ext) => ext instanceof x509_1.CRLDistributionPointsExtension);
24
+ // Get all CRL URLs from within the certificate
25
+ const crlURLs = [];
26
+ extCRLDistributionPoints?.distributionPoints?.forEach((dPoint) => {
27
+ dPoint.distributionPoint?.fullName?.forEach((fullName) => {
28
+ if (fullName.uniformResourceIdentifier) {
29
+ crlURLs.push(fullName.uniformResourceIdentifier);
30
+ }
31
+ });
31
32
  });
32
- // Check to see if we've got cached info for the cert's CA
33
- let keyIdentifier = undefined;
34
- if (extAuthorityKeyID && extAuthorityKeyID.keyId) {
35
- keyIdentifier = extAuthorityKeyID.keyId;
33
+ // If no URL(s) is provided then we have nothing to check
34
+ if (!(crlURLs.length > 0)) {
35
+ return false;
36
36
  }
37
- else if (extSubjectKeyID) {
38
- /**
39
- * We might be dealing with a self-signed root certificate. Check the
40
- * Subject key Identifier extension next.
41
- */
42
- keyIdentifier = extSubjectKeyID.keyId;
37
+ // Get the issuer's key identifier for part of the cache key and to check against the
38
+ // certificate's and CRL's AuthorityKeyIdentifier extension
39
+ const { keyIdentifier: issuerKeyIdentifier, fromExtension: issuerKeyIdentifierFromExtension, } = await getIssuerKeyIdentifier(issuer);
40
+ const certificateExtAuthorityKeyID = certificate.extensions.find((ext) => ext instanceof x509_1.AuthorityKeyIdentifierExtension);
41
+ // When present, assert that certificates's AuthorityKeyIdentifier mirrors
42
+ // the issuer's SubjectKeyIdentifier
43
+ if (issuerKeyIdentifierFromExtension &&
44
+ certificateExtAuthorityKeyID?.keyId &&
45
+ certificateExtAuthorityKeyID.keyId !== issuerKeyIdentifier) {
46
+ throw new Error(`Certificate's AuthorityKeyIdentifier did not match issuer's SubjectKeyIdentifier "${issuerKeyIdentifier}"`);
43
47
  }
44
- if (keyIdentifier) {
45
- const cached = cacheRevokedCerts[keyIdentifier];
48
+ for (const crlURL of crlURLs) {
49
+ // Key off of issuer public key and CRL URL in case multiple CRLs are specified
50
+ const cacheKey = `${issuerKeyIdentifier}|${crlURL}`;
51
+ const cached = cacheRevokedCerts[cacheKey];
46
52
  if (cached) {
47
53
  const now = new Date();
48
54
  // If there's a nextUpdate then make sure we're before it
49
55
  if (!cached.nextUpdate || cached.nextUpdate > now) {
50
- return cached.revokedCerts.indexOf(cert.serialNumber) >= 0;
56
+ return cached.revokedCerts.indexOf(certificate.serialNumber) >= 0;
51
57
  }
52
58
  }
53
- }
54
- const crlURL = extCRLDistributionPoints?.distributionPoints?.[0].distributionPoint?.fullName?.[0]
55
- .uniformResourceIdentifier;
56
- // If no URL is provided then we have nothing to check
57
- if (!crlURL) {
58
- return false;
59
- }
60
- // Download and read the CRL
61
- let certListBytes;
62
- try {
63
- const respCRL = await (0, fetch_js_1.fetch)(crlURL);
64
- certListBytes = await respCRL.arrayBuffer();
65
- }
66
- catch (_err) {
67
- return false;
68
- }
69
- let data;
70
- try {
71
- data = new x509_1.X509Crl(certListBytes);
72
- }
73
- catch (_err) {
74
- // Something was malformed with the CRL, so pass
75
- return false;
76
- }
77
- const newCached = {
78
- revokedCerts: [],
79
- nextUpdate: undefined,
80
- };
81
- // nextUpdate
82
- if (data.nextUpdate) {
83
- newCached.nextUpdate = data.nextUpdate;
84
- }
85
- // revokedCertificates
86
- const revokedCerts = data.entries;
87
- if (revokedCerts) {
88
- for (const cert of revokedCerts) {
89
- const revokedHex = cert.serialNumber;
90
- newCached.revokedCerts.push(revokedHex);
59
+ // Download and read the CRL
60
+ let crlBytes;
61
+ try {
62
+ const respCRL = await (0, fetch_js_1.fetch)(crlURL);
63
+ crlBytes = await respCRL.arrayBuffer();
64
+ }
65
+ catch (_err) {
66
+ // Some kind of network error occurred so try the next URL
67
+ continue;
68
+ }
69
+ let certCRL;
70
+ try {
71
+ certCRL = new x509_1.X509Crl(crlBytes);
72
+ }
73
+ catch (_err) {
74
+ // Something was malformed with the CRL so try the next URL
75
+ continue;
76
+ }
77
+ const certCRLVerified = await certCRL.verify({ publicKey: issuer.publicKey });
78
+ if (!certCRLVerified) {
79
+ throw new Error(`CRL from ${crlURL} failed signature verification against issuer "${issuer.subject}"`);
91
80
  }
92
- // Cache the results
93
- if (keyIdentifier) {
94
- cacheRevokedCerts[keyIdentifier] = newCached;
81
+ // When present, assert that the CRL's AuthorityKeyIdentifier mirrors
82
+ // the issuer's SubjectKeyIdentifier
83
+ const crlAKI = certCRL.getExtension(x509_1.AuthorityKeyIdentifierExtension);
84
+ if (issuerKeyIdentifierFromExtension &&
85
+ crlAKI?.keyId &&
86
+ crlAKI.keyId !== issuerKeyIdentifier) {
87
+ throw new Error(`CRL from ${crlURL} contained an AuthorityKeyIdentifier that did not match issuer's SubjectKeyIdentifier "${issuerKeyIdentifier}"`);
88
+ }
89
+ // Cache the CRL only if a nextUpdate has been provided
90
+ if (certCRL.nextUpdate) {
91
+ cacheRevokedCerts[cacheKey] = {
92
+ revokedCerts: certCRL.entries.map((entry) => entry.serialNumber),
93
+ nextUpdate: certCRL.nextUpdate,
94
+ };
95
+ }
96
+ // Finally, check entries for revocation status
97
+ const revoked = certCRL.findRevoked(certificate);
98
+ if (revoked) {
99
+ return true;
95
100
  }
96
- return newCached.revokedCerts.indexOf(cert.serialNumber) >= 0;
97
101
  }
98
102
  return false;
99
103
  }
104
+ /**
105
+ * Determine a unique key ID for the issuer certificate, based either on its
106
+ * SubjectKeyIdentifierExtension or by generating the same value using the issuer's public key
107
+ */
108
+ async function getIssuerKeyIdentifier(issuer) {
109
+ const issuerSKI = issuer.extensions?.find((ext) => ext instanceof x509_1.SubjectKeyIdentifierExtension);
110
+ if (issuerSKI?.keyId) {
111
+ return { keyIdentifier: issuerSKI.keyId, fromExtension: true };
112
+ }
113
+ /**
114
+ * The issuer carries no SubjectKeyIdentifier extension, so derive one from its public key
115
+ * (RFC 5280, method 1). This is good enough to keep unrelated CAs from colliding when computing
116
+ * the revocation cache key, but it's not authoritative enough to reject a certificate over.
117
+ */
118
+ const computed = await x509_1.SubjectKeyIdentifierExtension.create(issuer.publicKey);
119
+ return { keyIdentifier: computed.keyId, fromExtension: false };
120
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"verifyAKP.d.ts","sourceRoot":"","sources":["../../../../src/helpers/iso/isoCrypto/verifyAKP.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,gBAAgB,EAAa,MAAM,eAAe,CAAC;AAK3E,OAAO,KAAK,EAAmB,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE5E,wBAAwB;AACxB,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,aAAa,EAAE,gBAAgB,CAAC;IAChC,SAAS,EAAE,WAAW,CAAC;IACvB,IAAI,EAAE,WAAW,CAAC;CACnB,GAAG,OAAO,CAAC,OAAO,CAAC,CAkDnB"}
1
+ {"version":3,"file":"verifyAKP.d.ts","sourceRoot":"","sources":["../../../../src/helpers/iso/isoCrypto/verifyAKP.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,KAAK,gBAAgB,EAAa,MAAM,eAAe,CAAC;AAM3E,OAAO,KAAK,EAAmB,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE5E,wBAAwB;AACxB,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,aAAa,EAAE,gBAAgB,CAAC;IAChC,SAAS,EAAE,WAAW,CAAC;IACvB,IAAI,EAAE,WAAW,CAAC;CACnB,GAAG,OAAO,CAAC,OAAO,CAAC,CA+CnB"}
@@ -6,6 +6,7 @@ const index_js_1 = require("../../index.js");
6
6
  const importJWKKey_js_1 = require("./importJWKKey.js");
7
7
  const getWebCrypto_js_1 = require("./getWebCrypto.js");
8
8
  const mapCoseAlgToWebCryptoKeyAlgName_js_1 = require("./mapCoseAlgToWebCryptoKeyAlgName.js");
9
+ const index_js_2 = require("../../../errors/index.js");
9
10
  /** AKP, a.k.a ML-DSA */
10
11
  async function verifyAKP(opts) {
11
12
  const { cosePublicKey, signature, data } = opts;
@@ -40,7 +41,7 @@ async function verifyAKP(opts) {
40
41
  catch (err) {
41
42
  const _err = err;
42
43
  if (_err.name === 'NotSupportedError') {
43
- throw new Error(`This runtime's WebCrypto.subtle does not support use of ${webCryptoAlg}. See the \`cause\` property of this error for more info`, { cause: err });
44
+ throw new index_js_2.PQCNotSupportedError(alg);
44
45
  }
45
46
  else {
46
47
  throw err;
@@ -1,9 +1,6 @@
1
1
  import { COSEALG } from './cose.js';
2
2
  /**
3
3
  * Map X.509 signature algorithm OIDs to COSE algorithm IDs
4
- *
5
- * - EC2 OIDs: https://oidref.com/1.2.840.10045.4.3
6
- * - RSA OIDs: https://oidref.com/1.2.840.113549.1.1
7
4
  */
8
5
  export declare function mapX509SignatureAlgToCOSEAlg(signatureAlgorithm: string): COSEALG;
9
6
  //# sourceMappingURL=mapX509SignatureAlgToCOSEAlg.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"mapX509SignatureAlgToCOSEAlg.d.ts","sourceRoot":"","sources":["../../src/helpers/mapX509SignatureAlgToCOSEAlg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAC1C,kBAAkB,EAAE,MAAM,GACzB,OAAO,CAwBT"}
1
+ {"version":3,"file":"mapX509SignatureAlgToCOSEAlg.d.ts","sourceRoot":"","sources":["../../src/helpers/mapX509SignatureAlgToCOSEAlg.ts"],"names":[],"mappings":"AAQA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC;;GAEG;AACH,wBAAgB,4BAA4B,CAC1C,kBAAkB,EAAE,MAAM,GACzB,OAAO,CA8BT"}
@@ -1,36 +1,45 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.mapX509SignatureAlgToCOSEAlg = mapX509SignatureAlgToCOSEAlg;
4
+ const asn1_ecc_1 = require("@peculiar/asn1-ecc");
5
+ const asn1_rsa_1 = require("@peculiar/asn1-rsa");
6
+ const asn1_x509_post_quantum_1 = require("@peculiar/asn1-x509-post-quantum");
4
7
  const cose_js_1 = require("./cose.js");
5
8
  /**
6
9
  * Map X.509 signature algorithm OIDs to COSE algorithm IDs
7
- *
8
- * - EC2 OIDs: https://oidref.com/1.2.840.10045.4.3
9
- * - RSA OIDs: https://oidref.com/1.2.840.113549.1.1
10
10
  */
11
11
  function mapX509SignatureAlgToCOSEAlg(signatureAlgorithm) {
12
12
  let alg;
13
- if (signatureAlgorithm === '1.2.840.10045.4.3.2') {
13
+ if (signatureAlgorithm === asn1_ecc_1.id_ecdsaWithSHA256) {
14
14
  alg = cose_js_1.COSEALG.ES256;
15
15
  }
16
- else if (signatureAlgorithm === '1.2.840.10045.4.3.3') {
16
+ else if (signatureAlgorithm === asn1_ecc_1.id_ecdsaWithSHA384) {
17
17
  alg = cose_js_1.COSEALG.ES384;
18
18
  }
19
- else if (signatureAlgorithm === '1.2.840.10045.4.3.4') {
19
+ else if (signatureAlgorithm === asn1_ecc_1.id_ecdsaWithSHA512) {
20
20
  alg = cose_js_1.COSEALG.ES512;
21
21
  }
22
- else if (signatureAlgorithm === '1.2.840.113549.1.1.11') {
22
+ else if (signatureAlgorithm === asn1_rsa_1.id_sha256WithRSAEncryption) {
23
23
  alg = cose_js_1.COSEALG.RS256;
24
24
  }
25
- else if (signatureAlgorithm === '1.2.840.113549.1.1.12') {
25
+ else if (signatureAlgorithm === asn1_rsa_1.id_sha384WithRSAEncryption) {
26
26
  alg = cose_js_1.COSEALG.RS384;
27
27
  }
28
- else if (signatureAlgorithm === '1.2.840.113549.1.1.13') {
28
+ else if (signatureAlgorithm === asn1_rsa_1.id_sha512WithRSAEncryption) {
29
29
  alg = cose_js_1.COSEALG.RS512;
30
30
  }
31
- else if (signatureAlgorithm === '1.2.840.113549.1.1.5') {
31
+ else if (signatureAlgorithm === asn1_rsa_1.id_sha1WithRSAEncryption) {
32
32
  alg = cose_js_1.COSEALG.RS1;
33
33
  }
34
+ else if (signatureAlgorithm === asn1_x509_post_quantum_1.id_ml_dsa_44) {
35
+ alg = cose_js_1.COSEALG.ML_DSA_44;
36
+ }
37
+ else if (signatureAlgorithm === asn1_x509_post_quantum_1.id_ml_dsa_65) {
38
+ alg = cose_js_1.COSEALG.ML_DSA_65;
39
+ }
40
+ else if (signatureAlgorithm === asn1_x509_post_quantum_1.id_ml_dsa_87) {
41
+ alg = cose_js_1.COSEALG.ML_DSA_87;
42
+ }
34
43
  else {
35
44
  throw new Error(`Unable to map X.509 signature algorithm ${signatureAlgorithm} to a COSE algorithm`);
36
45
  }
@@ -1,3 +1,4 @@
1
+ import 'reflect-metadata';
1
2
  /**
2
3
  * Traverse an array of PEM certificates and ensure they form a proper chain
3
4
  * @param x5cCertsPEM Typically the result of `x5c.map(convertASN1toPEM)`
@@ -1 +1 @@
1
- {"version":3,"file":"validateCertificatePath.d.ts","sourceRoot":"","sources":["../../src/helpers/validateCertificatePath.ts"],"names":[],"mappings":"AAIA;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EAAE,EACrB,eAAe,GAAE,MAAM,EAAO,GAC7B,OAAO,CAAC,OAAO,CAAC,CAqIlB"}
1
+ {"version":3,"file":"validateCertificatePath.d.ts","sourceRoot":"","sources":["../../src/helpers/validateCertificatePath.ts"],"names":[],"mappings":"AAAA,OAAO,kBAAkB,CAAC;AAM1B;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EAAE,EACrB,eAAe,GAAE,MAAM,EAAO,GAC7B,OAAO,CAAC,OAAO,CAAC,CAiLlB"}
@@ -1,8 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateCertificatePath = validateCertificatePath;
4
+ require("reflect-metadata");
4
5
  const x509_1 = require("@peculiar/x509");
5
6
  const isCertRevoked_js_1 = require("./isCertRevoked.js");
7
+ const index_js_1 = require("../errors/index.js");
6
8
  /**
7
9
  * Traverse an array of PEM certificates and ensure they form a proper chain
8
10
  * @param x5cCertsPEM Typically the result of `x5c.map(convertASN1toPEM)`
@@ -13,48 +15,25 @@ async function validateCertificatePath(x5cCertsPEM, trustAnchorsPEM = []) {
13
15
  // We have no trust anchors to chain back to, so skip path validation
14
16
  return true;
15
17
  }
16
- // Prepare to work with x5c certs
17
- const x5cCertsParsed = x5cCertsPEM.map((certPEM) => new x509_1.X509Certificate(certPEM));
18
- // Check for any expired or temporally invalid certs in x5c
19
- for (let i = 0; i < x5cCertsParsed.length; i++) {
20
- const cert = x5cCertsParsed[i];
21
- const certPEM = x5cCertsPEM[i];
22
- try {
23
- await assertCertNotRevoked(cert);
24
- }
25
- catch (_err) {
26
- throw new Error(`Found revoked certificate in x5c:\n${certPEM}`);
27
- }
28
- try {
29
- assertCertIsWithinValidTimeWindow(cert.notBefore, cert.notAfter);
30
- }
31
- catch (_err) {
32
- throw new Error(`Found certificate out of validity period in x5c:\n${certPEM}`);
33
- }
34
- }
35
18
  // Prepare to work with trust anchor certs
36
19
  const trustAnchorsParsed = trustAnchorsPEM.map((certPEM) => {
37
20
  try {
38
21
  return new x509_1.X509Certificate(certPEM);
39
22
  }
40
23
  catch (err) {
41
- const _err = err;
42
- throw new Error(`Could not parse trust anchor certificate:\n${certPEM}`, { cause: _err });
24
+ throw new index_js_1.SimpleWebAuthnError({
25
+ message: `Could not parse trust anchor certificate:\n${certPEM}`,
26
+ code: 'CERTIFICATE_PATH_VERIFICATION_FAILED',
27
+ cause: err,
28
+ });
43
29
  }
44
30
  });
45
- // Filter out any expired or temporally invalid trust anchors certs
31
+ // Filter out any temporally invalid trust anchors certs
46
32
  const validTrustAnchors = [];
47
33
  for (let i = 0; i < trustAnchorsParsed.length; i++) {
48
34
  const cert = trustAnchorsParsed[i];
49
35
  try {
50
- await assertCertNotRevoked(cert);
51
- }
52
- catch (_err) {
53
- // Continue processing the other certs
54
- continue;
55
- }
56
- try {
57
- assertCertIsWithinValidTimeWindow(cert.notBefore, cert.notAfter);
36
+ assertCertIsWithinValidTimeWindow(cert);
58
37
  }
59
38
  catch (_err) {
60
39
  // Continue processing the other certs
@@ -63,22 +42,31 @@ async function validateCertificatePath(x5cCertsPEM, trustAnchorsPEM = []) {
63
42
  validTrustAnchors.push(cert);
64
43
  }
65
44
  if (validTrustAnchors.length === 0) {
66
- throw new Error('No specified trust anchor was valid for verifying x5c');
45
+ throw new index_js_1.SimpleWebAuthnError({
46
+ message: 'No specified trust anchor was valid for verifying x5c',
47
+ code: 'CERTIFICATE_PATH_VERIFICATION_FAILED',
48
+ });
49
+ }
50
+ // Prepare to work with x5c certs
51
+ const x5cCertsParsed = x5cCertsPEM.map((certPEM) => new x509_1.X509Certificate(certPEM));
52
+ // Break apart x5c into leaf + intermediates
53
+ const x5cLeafCert = x5cCertsParsed[0];
54
+ let x5cIntermediates = [];
55
+ if (x5cCertsParsed.length > 1) {
56
+ x5cIntermediates = x5cCertsParsed.slice(1);
67
57
  }
68
58
  // Try to verify x5c with each valid trust anchor
69
59
  let invalidCertificateChain = true;
60
+ let validatedChain = undefined;
70
61
  for (const anchor of validTrustAnchors) {
71
62
  try {
72
63
  const x5cWithTrustAnchor = x5cCertsParsed.concat([anchor]);
73
64
  const numUniqueCerts = new Set(x5cWithTrustAnchor.map((cert) => cert.toString('pem'))).size;
74
65
  if (numUniqueCerts !== x5cWithTrustAnchor.length) {
75
- throw new Error('Invalid certificate path: found duplicate certificates');
76
- }
77
- // Break apart x5c to try and build a valid cert chain
78
- const x5cLeafCert = x5cCertsParsed[0];
79
- let x5cIntermediates = [];
80
- if (x5cCertsParsed.length > 1) {
81
- x5cIntermediates = x5cCertsParsed.slice(1);
66
+ throw new index_js_1.SimpleWebAuthnError({
67
+ message: 'Invalid certificate path: found duplicate certificates',
68
+ code: 'CERTIFICATE_PATH_VERIFICATION_FAILED',
69
+ });
82
70
  }
83
71
  // Order of certs doesn't matter here but for readability
84
72
  const chainBuilder = new x509_1.X509ChainBuilder({ certificates: [...x5cIntermediates, anchor] });
@@ -112,27 +100,87 @@ async function validateCertificatePath(x5cCertsPEM, trustAnchorsPEM = []) {
112
100
  // Invalid cert chain, try the next trust anchor
113
101
  continue;
114
102
  }
115
- // If we successfully validated a path then there's no need to continue. Reset any existing
116
- // errors that were thrown by earlier trust anchors
103
+ if (certBeforeLastSignedByAnchor) {
104
+ /**
105
+ * Swap out the last cert in the chain with the anchor that we've already verified
106
+ * chains to the second-to-last certificate. This makes it easier to verify the chain
107
+ * as a standard certificate chain.
108
+ */
109
+ chain[chain.length - 1] = anchor;
110
+ }
111
+ // We successfully validated a chain so there's no need to continue
117
112
  invalidCertificateChain = false;
113
+ validatedChain = chain;
118
114
  break;
119
115
  }
120
116
  catch (err) {
121
- throw new Error('Unexpected error while validating certificate path', { cause: err });
117
+ throw new index_js_1.SimpleWebAuthnError({
118
+ message: 'Unexpected error while validating certificate path',
119
+ code: 'CERTIFICATE_PATH_VERIFICATION_FAILED',
120
+ cause: err,
121
+ });
122
122
  }
123
123
  }
124
+ if (validatedChain) {
125
+ // Check for any temporally invalid or expired certs in the chain
126
+ for (let i = 0; i < validatedChain.length; i++) {
127
+ const cert = validatedChain[i];
128
+ try {
129
+ assertCertIsWithinValidTimeWindow(cert);
130
+ }
131
+ catch (_err) {
132
+ throw new index_js_1.SimpleWebAuthnError({
133
+ message: `Found certificate out of validity period:\n${cert.toString()}`,
134
+ code: 'CERTIFICATE_PATH_VERIFICATION_FAILED',
135
+ });
136
+ }
137
+ /**
138
+ * Checking revocation is very expensive so do it only at the end when we're certain a cert
139
+ * is otherwise valid
140
+ */
141
+ let issuerCert = undefined;
142
+ if (i < validatedChain.length - 1) {
143
+ // Issuer cert is simply the next cert in the chain
144
+ issuerCert = validatedChain[i + 1];
145
+ }
146
+ else {
147
+ // `cert` is the anchor. Only try to verify its revocation status if it's a root certificate
148
+ if (await cert.isSelfSigned()) {
149
+ issuerCert = cert;
150
+ }
151
+ }
152
+ try {
153
+ await assertCertNotRevoked(cert, issuerCert);
154
+ }
155
+ catch (err) {
156
+ throw new index_js_1.SimpleWebAuthnError({
157
+ message: `The following certificate failed revocation status check\n${cert.toString()}`,
158
+ code: 'CERTIFICATE_PATH_VERIFICATION_FAILED',
159
+ cause: err,
160
+ });
161
+ }
162
+ }
163
+ }
164
+ else {
165
+ invalidCertificateChain = true;
166
+ }
124
167
  // We tried multiple trust anchors and none of them worked
125
168
  if (invalidCertificateChain) {
126
- throw new InvalidCertificatePath();
169
+ throw new index_js_1.SimpleWebAuthnError({
170
+ message: 'x5c could not be chained to any specified trust anchor',
171
+ code: 'CERTIFICATE_PATH_VERIFICATION_FAILED',
172
+ });
127
173
  }
128
174
  return true;
129
175
  }
130
176
  /**
131
177
  * Check if the certificate is revoked or not. If it is, raise an error
178
+ *
179
+ * @throws Error - Wrap this in a SimpleWebAuthnError so RPs can identify issues here
132
180
  */
133
- async function assertCertNotRevoked(certificate) {
181
+ async function assertCertNotRevoked(certificate, issuerCert) {
134
182
  // Check for certificate revocation
135
- const subjectCertRevoked = await (0, isCertRevoked_js_1.isCertRevoked)(certificate);
183
+ const subjectCertRevoked = await (0, isCertRevoked_js_1.isCertRevoked)(certificate, issuerCert);
136
184
  if (subjectCertRevoked) {
137
185
  throw new Error('Found revoked certificate in certificate path');
138
186
  }
@@ -140,16 +188,10 @@ async function assertCertNotRevoked(certificate) {
140
188
  /**
141
189
  * Require the cert to be within its notBefore and notAfter time window
142
190
  */
143
- function assertCertIsWithinValidTimeWindow(certNotBefore, certNotAfter) {
191
+ function assertCertIsWithinValidTimeWindow(certificate) {
192
+ const { notBefore: certNotBefore, notAfter: certNotAfter } = certificate;
144
193
  const now = new Date(Date.now());
145
194
  if (certNotBefore > now || certNotAfter < now) {
146
195
  throw new Error('Certificate is not yet valid or expired');
147
196
  }
148
197
  }
149
- class InvalidCertificatePath extends Error {
150
- constructor() {
151
- const message = 'x5c could not be chained to any specified trust anchor';
152
- super(message);
153
- this.name = 'InvalidX5CChain';
154
- }
155
- }
package/script/index.d.ts CHANGED
@@ -6,4 +6,5 @@ export * from './services/metadataService.js';
6
6
  export * from './services/settingsService.js';
7
7
  export * from './metadata/mdsTypes.js';
8
8
  export * from './types/index.js';
9
+ export * from './errors/index.js';
9
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,+CAA+C,CAAC;AAC9D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,mDAAmD,CAAC;AAClE,cAAc,kDAAkD,CAAC;AACjE,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,+CAA+C,CAAC;AAC9D,cAAc,8CAA8C,CAAC;AAC7D,cAAc,mDAAmD,CAAC;AAClE,cAAc,kDAAkD,CAAC;AACjE,cAAc,+BAA+B,CAAC;AAC9C,cAAc,+BAA+B,CAAC;AAC9C,cAAc,wBAAwB,CAAC;AACvC,cAAc,kBAAkB,CAAC;AACjC,cAAc,mBAAmB,CAAC"}
package/script/index.js CHANGED
@@ -22,3 +22,4 @@ __exportStar(require("./services/metadataService.js"), exports);
22
22
  __exportStar(require("./services/settingsService.js"), exports);
23
23
  __exportStar(require("./metadata/mdsTypes.js"), exports);
24
24
  __exportStar(require("./types/index.js"), exports);
25
+ __exportStar(require("./errors/index.js"), exports);
@@ -1 +1 @@
1
- {"version":3,"file":"verifyAttestationWithMetadata.d.ts","sourceRoot":"","sources":["../../src/metadata/verifyAttestationWithMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAIhE,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,OAAO,EAEZ,OAAO,EAER,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;GAGG;AACH,wBAAsB,6BAA6B,CAAC,EAClD,SAAS,EACT,mBAAmB,EACnB,GAAG,EACH,uBAAuB,GACxB,EAAE;IACD,SAAS,EAAE,iBAAiB,CAAC;IAC7B,mBAAmB,EAAE,WAAW,CAAC;IACjC,GAAG,EAAE,WAAW,EAAE,GAAG,eAAe,EAAE,CAAC;IACvC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC,GAAG,OAAO,CAAC,OAAO,CAAC,CAoJnB;AAED,KAAK,QAAQ,GAAG;IACd,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE;KAAG,GAAG,IAAI,OAAO,GAAG,QAAQ;CAe9D,CAAC"}
1
+ {"version":3,"file":"verifyAttestationWithMetadata.d.ts","sourceRoot":"","sources":["../../src/metadata/verifyAttestationWithMetadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAIhE,OAAO,EACL,KAAK,OAAO,EACZ,KAAK,OAAO,EAEZ,OAAO,EAER,MAAM,oBAAoB,CAAC;AAC5B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD;;;GAGG;AACH,wBAAsB,6BAA6B,CAAC,EAClD,SAAS,EACT,mBAAmB,EACnB,GAAG,EACH,uBAAuB,GACxB,EAAE;IACD,SAAS,EAAE,iBAAiB,CAAC;IAC7B,mBAAmB,EAAE,WAAW,CAAC;IACjC,GAAG,EAAE,WAAW,EAAE,GAAG,eAAe,EAAE,CAAC;IACvC,uBAAuB,CAAC,EAAE,MAAM,CAAC;CAClC,GAAG,OAAO,CAAC,OAAO,CAAC,CA6InB;AAED,KAAK,QAAQ,GAAG;IACd,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,EAAE,OAAO,CAAC;IACb,GAAG,CAAC,EAAE,OAAO,CAAC;CACf,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB,EAAE;KAAG,GAAG,IAAI,OAAO,GAAG,QAAQ;CAe9D,CAAC"}
@@ -110,13 +110,7 @@ async function verifyAttestationWithMetadata({ statement, credentialPublicKey, x
110
110
  authenticatorIsSelfReferencing = true;
111
111
  }
112
112
  if (!authenticatorIsSelfReferencing) {
113
- try {
114
- await (0, validateCertificatePath_js_1.validateCertificatePath)(authenticatorCerts, statementRootCerts);
115
- }
116
- catch (err) {
117
- const _err = err;
118
- throw new Error(`Could not validate certificate path with any metadata root certificates: ${_err.message}`);
119
- }
113
+ await (0, validateCertificatePath_js_1.validateCertificatePath)(authenticatorCerts, statementRootCerts);
120
114
  }
121
115
  return true;
122
116
  }
@@ -94,7 +94,7 @@ async function verifyAttestationPacked(options) {
94
94
  }
95
95
  catch (err) {
96
96
  const _err = err;
97
- throw new Error(`${_err.message} (Packed|Full)`);
97
+ throw new Error(`${_err.message} (Packed|Full)`, { cause: _err });
98
98
  }
99
99
  }
100
100
  verified = await (0, verifySignature_js_1.verifySignature)({
@@ -1 +1 @@
1
- {"version":3,"file":"verifyRegistrationResponse.d.ts","sourceRoot":"","sources":["../../src/registration/verifyRegistrationResponse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,wBAAwB,EACxB,WAAW,EACX,kBAAkB,EACnB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAE1B,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAE,4CAA4C,EAAE,MAAM,6CAA6C,CAAC;AAoBhH;;GAEG;AACH,MAAM,MAAM,8BAA8B,GAAG,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;AAE9F;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE;IACP,QAAQ,EAAE,wBAAwB,CAAC;IACnC,iBAAiB,EAAE,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAChF,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACjC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,mCAAmC,CAAC,EAAE,OAAO,CAAC;CAC/C,GACA,OAAO,CAAC,4BAA4B,CAAC,CAqPvC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,EAAE,KAAK,CAAC;IAChB,gBAAgB,CAAC,EAAE,KAAK,CAAC;CAC1B,GAAG;IACF,QAAQ,EAAE,IAAI,CAAC;IACf,gBAAgB,EAAE;QAChB,GAAG,EAAE,iBAAiB,CAAC;QACvB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,kBAAkB,CAAC;QAC/B,cAAc,EAAE,YAAY,CAAC;QAC7B,iBAAiB,EAAE,WAAW,CAAC;QAC/B,YAAY,EAAE,OAAO,CAAC;QACtB,oBAAoB,EAAE,oBAAoB,CAAC;QAC3C,kBAAkB,EAAE,OAAO,CAAC;QAC5B,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,6BAA6B,CAAC,EAAE,4CAA4C,CAAC;KAC9E,CAAC;CACH,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,QAAQ,EAAE,WAAW,CAAC;IACtB,cAAc,EAAE,WAAW,CAAC;IAC5B,YAAY,EAAE,WAAW,CAAC;IAC1B,mBAAmB,EAAE,WAAW,CAAC;IACjC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,WAAW,CAAC;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,mCAAmC,CAAC,EAAE,OAAO,CAAC;CAC/C,CAAC"}
1
+ {"version":3,"file":"verifyRegistrationResponse.d.ts","sourceRoot":"","sources":["../../src/registration/verifyRegistrationResponse.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,oBAAoB,EACpB,wBAAwB,EACxB,WAAW,EACX,kBAAkB,EACnB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EACL,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAE1B,MAAM,uCAAuC,CAAC;AAC/C,OAAO,KAAK,EAAE,4CAA4C,EAAE,MAAM,6CAA6C,CAAC;AAoBhH;;GAEG;AACH,MAAM,MAAM,8BAA8B,GAAG,UAAU,CAAC,OAAO,0BAA0B,CAAC,CAAC,CAAC,CAAC,CAAC;AAE9F;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,0BAA0B,CAC9C,OAAO,EAAE;IACP,QAAQ,EAAE,wBAAwB,CAAC;IACnC,iBAAiB,EAAE,MAAM,GAAG,CAAC,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC;IAChF,cAAc,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAClC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACjC,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,qBAAqB,CAAC,EAAE,MAAM,EAAE,CAAC;IACjC,mCAAmC,CAAC,EAAE,OAAO,CAAC;CAC/C,GACA,OAAO,CAAC,4BAA4B,CAAC,CA8PvC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,MAAM,MAAM,4BAA4B,GAAG;IACzC,QAAQ,EAAE,KAAK,CAAC;IAChB,gBAAgB,CAAC,EAAE,KAAK,CAAC;CAC1B,GAAG;IACF,QAAQ,EAAE,IAAI,CAAC;IACf,gBAAgB,EAAE;QAChB,GAAG,EAAE,iBAAiB,CAAC;QACvB,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,kBAAkB,CAAC;QAC/B,cAAc,EAAE,YAAY,CAAC;QAC7B,iBAAiB,EAAE,WAAW,CAAC;QAC/B,YAAY,EAAE,OAAO,CAAC;QACtB,oBAAoB,EAAE,oBAAoB,CAAC;QAC3C,kBAAkB,EAAE,OAAO,CAAC;QAC5B,MAAM,EAAE,MAAM,CAAC;QACf,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,6BAA6B,CAAC,EAAE,4CAA4C,CAAC;KAC9E,CAAC;CACH,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,6BAA6B,GAAG;IAC1C,MAAM,EAAE,WAAW,CAAC;IACpB,OAAO,EAAE,oBAAoB,CAAC;IAC9B,QAAQ,EAAE,WAAW,CAAC;IACtB,cAAc,EAAE,WAAW,CAAC;IAC5B,YAAY,EAAE,WAAW,CAAC;IAC1B,mBAAmB,EAAE,WAAW,CAAC;IACjC,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,QAAQ,EAAE,WAAW,CAAC;IACtB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,mCAAmC,CAAC,EAAE,OAAO,CAAC;CAC/C,CAAC"}