@blamejs/pki 0.9.5 → 0.9.6
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/CHANGELOG.md +37 -0
- package/README.md +7 -6
- package/bin/pki.js +110 -34
- package/lib/acme.js +91 -46
- package/lib/attrcert-sign.js +4 -3
- package/lib/cmp-build.js +16 -6
- package/lib/cmp-session.js +19 -3
- package/lib/cmp-verify.js +25 -2
- package/lib/cms-compress.js +3 -3
- package/lib/cms-digest.js +3 -3
- package/lib/cms-verify.js +18 -8
- package/lib/constants.js +1 -0
- package/lib/crl-sign.js +3 -3
- package/lib/csr-sign.js +1 -1
- package/lib/ct.js +16 -3
- package/lib/est.js +44 -29
- package/lib/guard-all.js +2 -0
- package/lib/guard-distrust.js +224 -0
- package/lib/guard-json.js +1 -1
- package/lib/http-transport.js +299 -36
- package/lib/jose.js +1 -1
- package/lib/lint.js +67 -7
- package/lib/ocsp-verify.js +9 -3
- package/lib/ocsp.js +8 -4
- package/lib/oid.js +4 -0
- package/lib/path-validate.js +112 -16
- package/lib/possession.js +6 -2
- package/lib/related-cert.js +2 -2
- package/lib/revocation-fetch.js +38 -9
- package/lib/scep.js +28 -13
- package/lib/schema-pkix.js +3 -3
- package/lib/sigstore.js +11 -6
- package/lib/smime.js +18 -13
- package/lib/tls-cert-compress.js +6 -5
- package/lib/trust.js +121 -20
- package/lib/trustanchor-build.js +16 -1
- package/lib/tsp-sign.js +7 -1
- package/lib/webauthn-mds.js +29 -8
- package/lib/webauthn.js +46 -14
- package/lib/x509-sign.js +8 -4
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
// @internal
|
|
5
|
+
|
|
6
|
+
var util = require("util");
|
|
7
|
+
var intrinsic = require("./guard-intrinsic");
|
|
8
|
+
var constants = require("./constants");
|
|
9
|
+
var gBytes = require("./guard-bytes");
|
|
10
|
+
var gList = require("./guard-list");
|
|
11
|
+
var gCrypto = require("./guard-crypto");
|
|
12
|
+
|
|
13
|
+
var _freeze = intrinsic.freeze;
|
|
14
|
+
var _isArray = intrinsic.isArray;
|
|
15
|
+
var _create = intrinsic.create;
|
|
16
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
17
|
+
var _getOwnPropertyDescriptor = intrinsic.getOwnPropertyDescriptor;
|
|
18
|
+
var _bufToString = intrinsic.bufToString;
|
|
19
|
+
var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
|
|
20
|
+
var _fromCharCode = intrinsic.fromCharCode;
|
|
21
|
+
var _isProxy = util.types.isProxy;
|
|
22
|
+
|
|
23
|
+
var DIGEST_BYTES = 32;
|
|
24
|
+
var DIGEST_HEX = 64;
|
|
25
|
+
|
|
26
|
+
/** @internal One hexadecimal character, folded to lower case, or -1. The fold is arithmetic on the
|
|
27
|
+
* character code, so the normalized digest is not the answer of a `toLowerCase` read off the live
|
|
28
|
+
* prototype. */
|
|
29
|
+
function _lowerHexCode(c) {
|
|
30
|
+
if (c >= 48 && c <= 57) return c;
|
|
31
|
+
if (c >= 97 && c <= 102) return c;
|
|
32
|
+
if (c >= 65 && c <= 70) return c + 32;
|
|
33
|
+
return -1;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function _hexEntry(s, E, code, where) {
|
|
37
|
+
if (s.length !== DIGEST_HEX) {
|
|
38
|
+
throw E(code, where + " must be a SHA-256 digest of a SubjectPublicKeyInfo: 64 hexadecimal characters or 32 bytes, got " + s.length + " characters");
|
|
39
|
+
}
|
|
40
|
+
var out = "";
|
|
41
|
+
for (var i = 0; i < DIGEST_HEX; i++) {
|
|
42
|
+
var c = _lowerHexCode(_charCodeAt(s, i));
|
|
43
|
+
if (c === -1) throw E(code, where + " must be 64 hexadecimal characters with no separators; character " + i + " is not a hexadecimal digit");
|
|
44
|
+
out += _fromCharCode(c);
|
|
45
|
+
}
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function _bytesEntry(v, E, code, where) {
|
|
50
|
+
var copy = gBytes.snapshotSource(v, E, code, where);
|
|
51
|
+
if (copy.length !== DIGEST_BYTES) {
|
|
52
|
+
throw E(code, where + " must be a SHA-256 digest of a SubjectPublicKeyInfo: 32 bytes or 64 hexadecimal characters, got " + copy.length + " bytes");
|
|
53
|
+
}
|
|
54
|
+
return _bufToString(copy, "hex");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The caller's list of distrusted keys, read once and returned as a frozen list of lower-case hex
|
|
58
|
+
* digests with duplicates removed, or `null` when the option is absent or `null`. Each entry is the
|
|
59
|
+
* SHA-256 digest of a certificate's DER SubjectPublicKeyInfo, given as 64 hexadecimal characters or
|
|
60
|
+
* as 32 bytes.
|
|
61
|
+
*
|
|
62
|
+
* Every slot is read through its descriptor, so a hole, an accessor and a Proxy are refused rather
|
|
63
|
+
* than read: a hole is answered by the array prototype and an accessor answers each read separately,
|
|
64
|
+
* and either one makes the list the check uses differ from the list the caller wrote. The list is a
|
|
65
|
+
* refusal list, so reading it as shorter than it is lets through a key the caller distrusted.
|
|
66
|
+
*
|
|
67
|
+
* @enforced-by behavioral -- the defect is a door that does not call this, which has no rename-proof
|
|
68
|
+
* code shape; the guard is the RED vector each verb carries, driving a listed key through the
|
|
69
|
+
* shipped verb and asserting the refusal.
|
|
70
|
+
*/
|
|
71
|
+
function keys(value, E, code, label) {
|
|
72
|
+
if (value === undefined || value === null) return null;
|
|
73
|
+
if (typeof value !== "object" || _isProxy(value) || !_isArray(value)) {
|
|
74
|
+
throw E(code, label + " must be an array of SHA-256 SubjectPublicKeyInfo digests");
|
|
75
|
+
}
|
|
76
|
+
var n = gList.count(value, "keys");
|
|
77
|
+
if (n > constants.LIMITS.DISTRUSTED_KEYS_MAX) {
|
|
78
|
+
throw E(code, label + " has " + n + " entries, more than the " + constants.LIMITS.DISTRUSTED_KEYS_MAX + " this toolkit reads");
|
|
79
|
+
}
|
|
80
|
+
var seen = _create(null);
|
|
81
|
+
var out = [];
|
|
82
|
+
for (var i = 0; i < n; i++) {
|
|
83
|
+
var where = label + "[" + i + "]";
|
|
84
|
+
var d = _getOwnPropertyDescriptor(value, i);
|
|
85
|
+
if (!d) throw E(code, where + " is a hole; every entry must be present");
|
|
86
|
+
if (d.get || d.set) throw E(code, where + " is an accessor; every entry must be a data property");
|
|
87
|
+
var v = d.value;
|
|
88
|
+
var hex;
|
|
89
|
+
if (typeof v === "string") hex = _hexEntry(v, E, code, where);
|
|
90
|
+
else if (gBytes.isByteSource(v)) hex = _bytesEntry(v, E, code, where);
|
|
91
|
+
else throw E(code, where + " must be 64 hexadecimal characters or 32 bytes");
|
|
92
|
+
if (_hasOwn(seen, hex)) continue;
|
|
93
|
+
seen[hex] = true;
|
|
94
|
+
gList.append(out, hex);
|
|
95
|
+
}
|
|
96
|
+
return _freeze(out);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The lower-case hex SHA-256 of `spkiBytes`, the DER SubjectPublicKeyInfo exactly as a certificate
|
|
100
|
+
* or an anchor carries it.
|
|
101
|
+
*
|
|
102
|
+
* @enforced-by behavioral -- a digest has no rename-proof shape that separates this use from the
|
|
103
|
+
* others `guard-crypto` serves; the guard is the membership vector, which fails if the bytes hashed
|
|
104
|
+
* are not the ones listed.
|
|
105
|
+
*/
|
|
106
|
+
function digest(spkiBytes) {
|
|
107
|
+
return gCrypto.digest("sha256", spkiBytes, { output: "hex" });
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** @internal The codec and the registry, reached through inline requires: both load this module
|
|
111
|
+
* through guard-all, so both are loaded by the time a digest is asked for. */
|
|
112
|
+
function _codec() {
|
|
113
|
+
return {
|
|
114
|
+
asn1: require("./asn1-der"), // allow:inline-require -- circular load: asn1-der -> guard-all -> this module
|
|
115
|
+
oid: require("./oid"), // allow:inline-require -- circular load: oid -> guard-all -> this module
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** @internal The SubjectPublicKeyInfo with an identifier that restricts the key's use replaced by the
|
|
120
|
+
* key's own: id-ecDH and id-ecMQV (RFC 5480 sec. 2.1.2) become ecPublicKey with the curve kept, and
|
|
121
|
+
* RSASSA-PSS and RSAES-OAEP (RFC 4055) become rsaEncryption with NULL. The subjectPublicKey bits are
|
|
122
|
+
* the same key in each pair. Any other identifier is returned unchanged. */
|
|
123
|
+
function _unrestricted(spkiBytes, c) {
|
|
124
|
+
var b = c.asn1.build;
|
|
125
|
+
var root = c.asn1.decode(spkiBytes);
|
|
126
|
+
var alg = root.children[0];
|
|
127
|
+
var name = c.oid.name(c.asn1.read.oid(alg.children[0]));
|
|
128
|
+
if (name === "ecDH" || name === "ecMQV") {
|
|
129
|
+
var params = alg.children.length > 1 ? [b.raw(alg.children[1].bytes)] : [];
|
|
130
|
+
return b.sequence([b.sequence(intrinsic.concatList([b.oid(c.oid.byName("ecPublicKey"))], params)), b.raw(root.children[1].bytes)]);
|
|
131
|
+
}
|
|
132
|
+
if (name === "rsassaPss" || name === "rsaesOaep") {
|
|
133
|
+
return b.sequence([b.sequence([b.oid(c.oid.byName("rsaEncryption")), b.nullValue()]), b.raw(root.children[1].bytes)]);
|
|
134
|
+
}
|
|
135
|
+
return spkiBytes;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** @internal The key in canonical form, or null when node does not read the bytes as a public key.
|
|
139
|
+
* An elliptic curve key is written with ecPublicKey, its named curve and an uncompressed point,
|
|
140
|
+
* whatever point form or parameter form it was read in. Any other key node writes as a JWK is
|
|
141
|
+
* written the way node writes a key it builds from one, which puts an RSA key under rsaEncryption
|
|
142
|
+
* with NULL. A key with neither form is re-exported as node read it. */
|
|
143
|
+
function _canonicalSpki(spkiBytes) {
|
|
144
|
+
try {
|
|
145
|
+
var c = _codec();
|
|
146
|
+
var spki = _unrestricted(spkiBytes, c);
|
|
147
|
+
var key = gCrypto.publicKey({ key: spki, format: "der", type: "spki" });
|
|
148
|
+
if (gCrypto.keyTypeOf(key) === "ec") {
|
|
149
|
+
var details = gCrypto.keyDetailsOf(key);
|
|
150
|
+
var curveOid = details && typeof details.namedCurve === "string" ? c.oid.byName(details.namedCurve) : undefined;
|
|
151
|
+
if (curveOid !== undefined) {
|
|
152
|
+
var b = c.asn1.build;
|
|
153
|
+
var point = c.asn1.read.bitString(c.asn1.decode(spki).children[1]).bytes;
|
|
154
|
+
var uncompressed = gCrypto.ecConvertPoint(point, details.namedCurve, "uncompressed");
|
|
155
|
+
return b.sequence([b.sequence([b.oid(c.oid.byName("ecPublicKey")), b.oid(curveOid)]), b.bitString(uncompressed, 0)]);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
var jwk = null;
|
|
159
|
+
try { jwk = gCrypto.exportKey(key, { format: "jwk" }); }
|
|
160
|
+
catch (_je) {
|
|
161
|
+
// allow:swallow-unverified a key type with no JWK form is re-exported as node read it
|
|
162
|
+
jwk = null;
|
|
163
|
+
}
|
|
164
|
+
if (jwk !== null) key = gCrypto.publicKey({ key: jwk, format: "jwk" });
|
|
165
|
+
return gCrypto.exportKey(key, { type: "spki", format: "der" });
|
|
166
|
+
} catch (_e) {
|
|
167
|
+
// allow:swallow-unverified a key node does not read has no second encoding, and the raw digest still decides
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/** The digest a distrust entry names a key by: the lower-case hex SHA-256 of the key's
|
|
173
|
+
* SubjectPublicKeyInfo in canonical form, an uncompressed EC point with its named curve and an RSA
|
|
174
|
+
* key with NULL parameters. That is how nearly every certificate encodes a key, so for those it is
|
|
175
|
+
* the digest of the bytes the certificate carries. A key node does not read is digested as given.
|
|
176
|
+
*
|
|
177
|
+
* @enforced-by behavioral -- a digest has no rename-proof shape; the guard is the two-direction
|
|
178
|
+
* re-encoding vectors in guard-distrust.test.js.
|
|
179
|
+
*/
|
|
180
|
+
function keyDigest(spkiBytes) {
|
|
181
|
+
var canonical = _canonicalSpki(spkiBytes);
|
|
182
|
+
return digest(canonical !== null ? canonical : spkiBytes);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/** Whether the key `spkiBytes` names is on `list`, a value `keys` returned. A `null` or empty list
|
|
186
|
+
* answers `false`. The digest of the bytes as given is compared, and so is `keyDigest`, so an entry
|
|
187
|
+
* `keyDigest` computed matches the key however a certificate encodes it (a compressed EC point, RSA
|
|
188
|
+
* parameters without NULL), and an entry computed over raw bytes matches a certificate with the same
|
|
189
|
+
* bytes.
|
|
190
|
+
*
|
|
191
|
+
* @enforced-by behavioral -- membership has no rename-proof code shape; the RED vectors are the
|
|
192
|
+
* listed-key refusals at each verb, and the re-encodings in guard-distrust.test.js in both directions.
|
|
193
|
+
*/
|
|
194
|
+
function has(list, spkiBytes) {
|
|
195
|
+
if (list === null || list === undefined || list.length === 0 || spkiBytes === null || spkiBytes === undefined) return false;
|
|
196
|
+
if (gList.contains(list, digest(spkiBytes))) return true;
|
|
197
|
+
return gList.contains(list, keyDigest(spkiBytes));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Every entry of two lists that `keys` returned, once, as a frozen list; `null` when both are
|
|
201
|
+
* `null`. A transport default and a per-request list are combined this way, so a request cannot
|
|
202
|
+
* remove a key the default distrusts by naming a shorter list.
|
|
203
|
+
*
|
|
204
|
+
* @enforced-by behavioral -- a union has no rename-proof code shape; the RED vector is a request
|
|
205
|
+
* list that omits a default entry and the default entry still being refused.
|
|
206
|
+
*/
|
|
207
|
+
function union(a, b) {
|
|
208
|
+
if ((a === null || a === undefined) && (b === null || b === undefined)) return null;
|
|
209
|
+
var seen = _create(null);
|
|
210
|
+
var out = [];
|
|
211
|
+
var parts = [a || [], b || []];
|
|
212
|
+
for (var p = 0; p < 2; p++) {
|
|
213
|
+
var part = parts[p];
|
|
214
|
+
for (var i = 0, n = gList.count(part, "union"); i < n; i++) {
|
|
215
|
+
var hex = part[i];
|
|
216
|
+
if (_hasOwn(seen, hex)) continue;
|
|
217
|
+
seen[hex] = true;
|
|
218
|
+
gList.append(out, hex);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return _freeze(out);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
module.exports = _freeze({ keys: keys, digest: digest, keyDigest: keyDigest, has: has, union: union });
|
package/lib/guard-json.js
CHANGED
|
@@ -24,7 +24,7 @@ var _hexVal = require("./guard-encoding").hexNibble;
|
|
|
24
24
|
// @enforced-by json-parse-not-via-guard
|
|
25
25
|
function parse(input, ErrorClass, spec) {
|
|
26
26
|
function E(code, message, cause) { return new ErrorClass(code, message, cause); }
|
|
27
|
-
if (spec.maxBytes
|
|
27
|
+
if (spec.maxBytes == null || spec.maxDepth == null) {
|
|
28
28
|
throw new TypeError("guard.json.parse: spec.maxBytes and spec.maxDepth are required");
|
|
29
29
|
}
|
|
30
30
|
var maxBytes = limits.cap(spec.maxBytes, "guard.json.parse spec.maxBytes", undefined);
|