@simplewebauthn/server 13.3.1 → 13.3.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.
@@ -1 +1 @@
1
- {"version":3,"file":"validateCertificatePath.d.ts","sourceRoot":"","sources":["../../src/helpers/validateCertificatePath.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EAAE,EACrB,eAAe,GAAE,MAAM,EAAO,GAC7B,OAAO,CAAC,OAAO,CAAC,CA6HlB"}
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,CAiHlB"}
@@ -1,6 +1,5 @@
1
- import { X509Certificate } from '@peculiar/x509';
1
+ import { X509Certificate, X509ChainBuilder } from '@peculiar/x509';
2
2
  import { isCertRevoked } from './isCertRevoked.js';
3
- import { getWebCrypto } from './iso/isoCrypto/getWebCrypto.js';
4
3
  /**
5
4
  * Traverse an array of PEM certificates and ensure they form a proper chain
6
5
  * @param x5cCertsPEM Typically the result of `x5c.map(convertASN1toPEM)`
@@ -11,7 +10,6 @@ export async function validateCertificatePath(x5cCertsPEM, trustAnchorsPEM = [])
11
10
  // We have no trust anchors to chain back to, so skip path validation
12
11
  return true;
13
12
  }
14
- const WebCrypto = await getWebCrypto();
15
13
  // Prepare to work with x5c certs
16
14
  const x5cCertsParsed = x5cCertsPEM.map((certPEM) => new X509Certificate(certPEM));
17
15
  // Check for any expired or temporally invalid certs in x5c
@@ -64,50 +62,47 @@ export async function validateCertificatePath(x5cCertsPEM, trustAnchorsPEM = [])
64
62
  if (validTrustAnchors.length === 0) {
65
63
  throw new Error('No specified trust anchor was valid for verifying x5c');
66
64
  }
67
- // Try to verify x5c with each trust anchor
68
- let invalidSubjectAndIssuerError = false;
69
- for (const anchor of trustAnchorsParsed) {
65
+ // Try to verify x5c with each valid trust anchor
66
+ let invalidCertificateChain = true;
67
+ for (const anchor of validTrustAnchors) {
70
68
  try {
71
69
  const x5cWithTrustAnchor = x5cCertsParsed.concat([anchor]);
72
- if (new Set(x5cWithTrustAnchor).size !== x5cWithTrustAnchor.length) {
70
+ const numUniqueCerts = new Set(x5cWithTrustAnchor.map((cert) => cert.toString('pem'))).size;
71
+ if (numUniqueCerts !== x5cWithTrustAnchor.length) {
73
72
  throw new Error('Invalid certificate path: found duplicate certificates');
74
73
  }
75
- // Check signatures, and notBefore and notAfter
76
- for (let i = 0; i < x5cWithTrustAnchor.length - 1; i++) {
77
- const subject = x5cWithTrustAnchor[i];
78
- const issuer = x5cWithTrustAnchor[i + 1];
79
- // Leaf or intermediate cert, make sure the next cert in the chain signed it
80
- const issuerSignedSubject = await subject.verify({ publicKey: issuer.publicKey, signatureOnly: true }, WebCrypto);
81
- if (!issuerSignedSubject) {
82
- throw new InvalidSubjectAndIssuer();
83
- }
84
- if (issuer.subject === issuer.issuer) {
85
- // Root cert detected, make sure it signed itself
86
- const issuerSignedIssuer = await issuer.verify({ publicKey: issuer.publicKey, signatureOnly: true }, WebCrypto);
87
- if (!issuerSignedIssuer) {
88
- throw new InvalidSubjectAndIssuer();
89
- }
90
- // Don't process anything else after a root cert
91
- break;
92
- }
74
+ // Break apart x5c to try and build a valid cert chain
75
+ const x5cLeafCert = x5cCertsParsed[0];
76
+ let x5cIntermediates = [];
77
+ if (x5cCertsParsed.length > 1) {
78
+ x5cIntermediates = x5cCertsParsed.slice(1);
79
+ }
80
+ // Order of certs doesn't matter here but for readability
81
+ const chainBuilder = new X509ChainBuilder({ certificates: [...x5cIntermediates, anchor] });
82
+ // Cert chain should be, from index 0: leaf cert -> ...intermediates -> trust anchor
83
+ const chain = await chainBuilder.build(x5cLeafCert);
84
+ // Check if the chain contains (all of the certs in x5c) + (the trust anchor)
85
+ if (chain.length < numUniqueCerts) {
86
+ // Invalid cert chain, try the next trust anchor
87
+ continue;
88
+ }
89
+ // Check if the chain is to the trust anchor
90
+ if (chain[chain.length - 1].subject !== anchor.subject) {
91
+ // Invalid cert chain, try the next trust anchor
92
+ continue;
93
93
  }
94
94
  // If we successfully validated a path then there's no need to continue. Reset any existing
95
95
  // errors that were thrown by earlier trust anchors
96
- invalidSubjectAndIssuerError = false;
96
+ invalidCertificateChain = false;
97
97
  break;
98
98
  }
99
99
  catch (err) {
100
- if (err instanceof InvalidSubjectAndIssuer) {
101
- invalidSubjectAndIssuerError = true;
102
- }
103
- else {
104
- throw new Error('Unexpected error while validating certificate path', { cause: err });
105
- }
100
+ throw new Error('Unexpected error while validating certificate path', { cause: err });
106
101
  }
107
102
  }
108
103
  // We tried multiple trust anchors and none of them worked
109
- if (invalidSubjectAndIssuerError) {
110
- throw new InvalidSubjectAndIssuer();
104
+ if (invalidCertificateChain) {
105
+ throw new InvalidCertificatePath();
111
106
  }
112
107
  return true;
113
108
  }
@@ -130,11 +125,10 @@ function assertCertIsWithinValidTimeWindow(certNotBefore, certNotAfter) {
130
125
  throw new Error('Certificate is not yet valid or expired');
131
126
  }
132
127
  }
133
- // Custom errors to help pass on certain errors
134
- class InvalidSubjectAndIssuer extends Error {
128
+ class InvalidCertificatePath extends Error {
135
129
  constructor() {
136
- const message = 'Subject issuer did not match issuer subject';
130
+ const message = 'x5c could not be chained to any specified trust anchor';
137
131
  super(message);
138
- this.name = 'InvalidSubjectAndIssuer';
132
+ this.name = 'InvalidX5CChain';
139
133
  }
140
134
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@simplewebauthn/server",
3
- "version": "13.3.1",
3
+ "version": "13.3.2",
4
4
  "description": "SimpleWebAuthn for Servers",
5
5
  "keywords": [
6
6
  "typescript",
@@ -1 +1 @@
1
- {"version":3,"file":"validateCertificatePath.d.ts","sourceRoot":"","sources":["../../src/helpers/validateCertificatePath.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC3C,WAAW,EAAE,MAAM,EAAE,EACrB,eAAe,GAAE,MAAM,EAAO,GAC7B,OAAO,CAAC,OAAO,CAAC,CA6HlB"}
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,CAiHlB"}
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.validateCertificatePath = validateCertificatePath;
4
4
  const x509_1 = require("@peculiar/x509");
5
5
  const isCertRevoked_js_1 = require("./isCertRevoked.js");
6
- const getWebCrypto_js_1 = require("./iso/isoCrypto/getWebCrypto.js");
7
6
  /**
8
7
  * Traverse an array of PEM certificates and ensure they form a proper chain
9
8
  * @param x5cCertsPEM Typically the result of `x5c.map(convertASN1toPEM)`
@@ -14,7 +13,6 @@ async function validateCertificatePath(x5cCertsPEM, trustAnchorsPEM = []) {
14
13
  // We have no trust anchors to chain back to, so skip path validation
15
14
  return true;
16
15
  }
17
- const WebCrypto = await (0, getWebCrypto_js_1.getWebCrypto)();
18
16
  // Prepare to work with x5c certs
19
17
  const x5cCertsParsed = x5cCertsPEM.map((certPEM) => new x509_1.X509Certificate(certPEM));
20
18
  // Check for any expired or temporally invalid certs in x5c
@@ -67,50 +65,47 @@ async function validateCertificatePath(x5cCertsPEM, trustAnchorsPEM = []) {
67
65
  if (validTrustAnchors.length === 0) {
68
66
  throw new Error('No specified trust anchor was valid for verifying x5c');
69
67
  }
70
- // Try to verify x5c with each trust anchor
71
- let invalidSubjectAndIssuerError = false;
72
- for (const anchor of trustAnchorsParsed) {
68
+ // Try to verify x5c with each valid trust anchor
69
+ let invalidCertificateChain = true;
70
+ for (const anchor of validTrustAnchors) {
73
71
  try {
74
72
  const x5cWithTrustAnchor = x5cCertsParsed.concat([anchor]);
75
- if (new Set(x5cWithTrustAnchor).size !== x5cWithTrustAnchor.length) {
73
+ const numUniqueCerts = new Set(x5cWithTrustAnchor.map((cert) => cert.toString('pem'))).size;
74
+ if (numUniqueCerts !== x5cWithTrustAnchor.length) {
76
75
  throw new Error('Invalid certificate path: found duplicate certificates');
77
76
  }
78
- // Check signatures, and notBefore and notAfter
79
- for (let i = 0; i < x5cWithTrustAnchor.length - 1; i++) {
80
- const subject = x5cWithTrustAnchor[i];
81
- const issuer = x5cWithTrustAnchor[i + 1];
82
- // Leaf or intermediate cert, make sure the next cert in the chain signed it
83
- const issuerSignedSubject = await subject.verify({ publicKey: issuer.publicKey, signatureOnly: true }, WebCrypto);
84
- if (!issuerSignedSubject) {
85
- throw new InvalidSubjectAndIssuer();
86
- }
87
- if (issuer.subject === issuer.issuer) {
88
- // Root cert detected, make sure it signed itself
89
- const issuerSignedIssuer = await issuer.verify({ publicKey: issuer.publicKey, signatureOnly: true }, WebCrypto);
90
- if (!issuerSignedIssuer) {
91
- throw new InvalidSubjectAndIssuer();
92
- }
93
- // Don't process anything else after a root cert
94
- break;
95
- }
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);
82
+ }
83
+ // Order of certs doesn't matter here but for readability
84
+ const chainBuilder = new x509_1.X509ChainBuilder({ certificates: [...x5cIntermediates, anchor] });
85
+ // Cert chain should be, from index 0: leaf cert -> ...intermediates -> trust anchor
86
+ const chain = await chainBuilder.build(x5cLeafCert);
87
+ // Check if the chain contains (all of the certs in x5c) + (the trust anchor)
88
+ if (chain.length < numUniqueCerts) {
89
+ // Invalid cert chain, try the next trust anchor
90
+ continue;
91
+ }
92
+ // Check if the chain is to the trust anchor
93
+ if (chain[chain.length - 1].subject !== anchor.subject) {
94
+ // Invalid cert chain, try the next trust anchor
95
+ continue;
96
96
  }
97
97
  // If we successfully validated a path then there's no need to continue. Reset any existing
98
98
  // errors that were thrown by earlier trust anchors
99
- invalidSubjectAndIssuerError = false;
99
+ invalidCertificateChain = false;
100
100
  break;
101
101
  }
102
102
  catch (err) {
103
- if (err instanceof InvalidSubjectAndIssuer) {
104
- invalidSubjectAndIssuerError = true;
105
- }
106
- else {
107
- throw new Error('Unexpected error while validating certificate path', { cause: err });
108
- }
103
+ throw new Error('Unexpected error while validating certificate path', { cause: err });
109
104
  }
110
105
  }
111
106
  // We tried multiple trust anchors and none of them worked
112
- if (invalidSubjectAndIssuerError) {
113
- throw new InvalidSubjectAndIssuer();
107
+ if (invalidCertificateChain) {
108
+ throw new InvalidCertificatePath();
114
109
  }
115
110
  return true;
116
111
  }
@@ -133,11 +128,10 @@ function assertCertIsWithinValidTimeWindow(certNotBefore, certNotAfter) {
133
128
  throw new Error('Certificate is not yet valid or expired');
134
129
  }
135
130
  }
136
- // Custom errors to help pass on certain errors
137
- class InvalidSubjectAndIssuer extends Error {
131
+ class InvalidCertificatePath extends Error {
138
132
  constructor() {
139
- const message = 'Subject issuer did not match issuer subject';
133
+ const message = 'x5c could not be chained to any specified trust anchor';
140
134
  super(message);
141
- this.name = 'InvalidSubjectAndIssuer';
135
+ this.name = 'InvalidX5CChain';
142
136
  }
143
137
  }