@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.
- package/esm/authentication/verifyAuthenticationResponse.d.ts.map +1 -1
- package/esm/authentication/verifyAuthenticationResponse.js +12 -0
- package/esm/errors/index.d.ts +17 -0
- package/esm/errors/index.d.ts.map +1 -0
- package/esm/errors/index.js +24 -0
- package/esm/helpers/convertX509PublicKeyToCOSE.d.ts +1 -1
- package/esm/helpers/convertX509PublicKeyToCOSE.d.ts.map +1 -1
- package/esm/helpers/convertX509PublicKeyToCOSE.js +22 -6
- package/esm/helpers/cose.d.ts +1 -0
- package/esm/helpers/cose.d.ts.map +1 -1
- package/esm/helpers/cose.js +3 -0
- package/esm/helpers/isCertRevoked.d.ts +2 -1
- package/esm/helpers/isCertRevoked.d.ts.map +1 -1
- package/esm/helpers/isCertRevoked.js +91 -70
- package/esm/helpers/iso/isoCrypto/verifyAKP.d.ts.map +1 -1
- package/esm/helpers/iso/isoCrypto/verifyAKP.js +2 -1
- package/esm/helpers/mapX509SignatureAlgToCOSEAlg.d.ts +0 -3
- package/esm/helpers/mapX509SignatureAlgToCOSEAlg.d.ts.map +1 -1
- package/esm/helpers/mapX509SignatureAlgToCOSEAlg.js +19 -10
- package/esm/helpers/validateCertificatePath.d.ts +1 -0
- package/esm/helpers/validateCertificatePath.d.ts.map +1 -1
- package/esm/helpers/validateCertificatePath.js +94 -52
- package/esm/index.d.ts +1 -0
- package/esm/index.d.ts.map +1 -1
- package/esm/index.js +1 -0
- package/esm/metadata/verifyAttestationWithMetadata.d.ts.map +1 -1
- package/esm/metadata/verifyAttestationWithMetadata.js +1 -7
- package/esm/registration/verifications/verifyAttestationPacked.js +1 -1
- package/esm/registration/verifyRegistrationResponse.d.ts.map +1 -1
- package/esm/registration/verifyRegistrationResponse.js +19 -8
- package/package.json +4 -2
- package/script/authentication/verifyAuthenticationResponse.d.ts.map +1 -1
- package/script/authentication/verifyAuthenticationResponse.js +12 -0
- package/script/errors/index.d.ts +17 -0
- package/script/errors/index.d.ts.map +1 -0
- package/script/errors/index.js +29 -0
- package/script/helpers/convertX509PublicKeyToCOSE.d.ts +1 -1
- package/script/helpers/convertX509PublicKeyToCOSE.d.ts.map +1 -1
- package/script/helpers/convertX509PublicKeyToCOSE.js +21 -5
- package/script/helpers/cose.d.ts +1 -0
- package/script/helpers/cose.d.ts.map +1 -1
- package/script/helpers/cose.js +4 -0
- package/script/helpers/isCertRevoked.d.ts +2 -1
- package/script/helpers/isCertRevoked.d.ts.map +1 -1
- package/script/helpers/isCertRevoked.js +91 -70
- package/script/helpers/iso/isoCrypto/verifyAKP.d.ts.map +1 -1
- package/script/helpers/iso/isoCrypto/verifyAKP.js +2 -1
- package/script/helpers/mapX509SignatureAlgToCOSEAlg.d.ts +0 -3
- package/script/helpers/mapX509SignatureAlgToCOSEAlg.d.ts.map +1 -1
- package/script/helpers/mapX509SignatureAlgToCOSEAlg.js +19 -10
- package/script/helpers/validateCertificatePath.d.ts +1 -0
- package/script/helpers/validateCertificatePath.d.ts.map +1 -1
- package/script/helpers/validateCertificatePath.js +94 -52
- package/script/index.d.ts +1 -0
- package/script/index.d.ts.map +1 -1
- package/script/index.js +1 -0
- package/script/metadata/verifyAttestationWithMetadata.d.ts.map +1 -1
- package/script/metadata/verifyAttestationWithMetadata.js +1 -7
- package/script/registration/verifications/verifyAttestationPacked.js +1 -1
- package/script/registration/verifyRegistrationResponse.d.ts.map +1 -1
- 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(
|
|
14
|
-
|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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
|
-
//
|
|
33
|
-
|
|
34
|
-
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
-
|
|
45
|
-
|
|
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(
|
|
56
|
+
return cached.revokedCerts.indexOf(certificate.serialNumber) >= 0;
|
|
51
57
|
}
|
|
52
58
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
//
|
|
93
|
-
|
|
94
|
-
|
|
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;
|
|
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
|
|
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":"
|
|
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 ===
|
|
13
|
+
if (signatureAlgorithm === asn1_ecc_1.id_ecdsaWithSHA256) {
|
|
14
14
|
alg = cose_js_1.COSEALG.ES256;
|
|
15
15
|
}
|
|
16
|
-
else if (signatureAlgorithm ===
|
|
16
|
+
else if (signatureAlgorithm === asn1_ecc_1.id_ecdsaWithSHA384) {
|
|
17
17
|
alg = cose_js_1.COSEALG.ES384;
|
|
18
18
|
}
|
|
19
|
-
else if (signatureAlgorithm ===
|
|
19
|
+
else if (signatureAlgorithm === asn1_ecc_1.id_ecdsaWithSHA512) {
|
|
20
20
|
alg = cose_js_1.COSEALG.ES512;
|
|
21
21
|
}
|
|
22
|
-
else if (signatureAlgorithm ===
|
|
22
|
+
else if (signatureAlgorithm === asn1_rsa_1.id_sha256WithRSAEncryption) {
|
|
23
23
|
alg = cose_js_1.COSEALG.RS256;
|
|
24
24
|
}
|
|
25
|
-
else if (signatureAlgorithm ===
|
|
25
|
+
else if (signatureAlgorithm === asn1_rsa_1.id_sha384WithRSAEncryption) {
|
|
26
26
|
alg = cose_js_1.COSEALG.RS384;
|
|
27
27
|
}
|
|
28
|
-
else if (signatureAlgorithm ===
|
|
28
|
+
else if (signatureAlgorithm === asn1_rsa_1.id_sha512WithRSAEncryption) {
|
|
29
29
|
alg = cose_js_1.COSEALG.RS512;
|
|
30
30
|
}
|
|
31
|
-
else if (signatureAlgorithm ===
|
|
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 +1 @@
|
|
|
1
|
-
{"version":3,"file":"validateCertificatePath.d.ts","sourceRoot":"","sources":["../../src/helpers/validateCertificatePath.ts"],"names":[],"mappings":"
|
|
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
|
-
|
|
42
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
|
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
|
|
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(
|
|
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
package/script/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
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;
|
|
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"}
|