@blamejs/pki 0.8.7 → 0.8.9
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 +124 -76
- package/README.md +3 -2
- package/index.js +5 -0
- package/lib/acme.js +15 -78
- package/lib/cmc-build.js +1 -1
- package/lib/cmc-verify.js +3 -3
- package/lib/cmp-session.js +1 -1
- package/lib/cms-sign.js +3 -3
- package/lib/cms-verify.js +5 -5
- package/lib/constants.js +11 -0
- package/lib/est.js +1 -1
- package/lib/framework-error.js +6 -0
- package/lib/guard-async.js +53 -2
- package/lib/guard-encoding.js +74 -0
- package/lib/guard-identifier.js +74 -1
- package/lib/guard-intrinsic.js +2 -0
- package/lib/guard-limits.js +40 -1
- package/lib/guard-list.js +25 -0
- package/lib/guard-name.js +133 -16
- package/lib/guard-text.js +33 -1
- package/lib/http-transport.js +39 -7
- package/lib/identity-match.js +561 -0
- package/lib/ip-utils.js +52 -1
- package/lib/key.js +250 -5
- package/lib/ocsp.js +125 -22
- package/lib/oid.js +1 -1
- package/lib/path-validate.js +199 -61
- package/lib/revocation-fetch.js +772 -0
- package/lib/schema-all.js +124 -0
- package/lib/schema-attrcert.js +4 -2
- package/lib/schema-crmf.js +4 -2
- package/lib/schema-pkcs1.js +335 -0
- package/lib/schema-pkcs8.js +15 -0
- package/lib/schema-pkix.js +322 -40
- package/lib/schema-sec1.js +244 -0
- package/lib/schema-tsp.js +4 -2
- package/lib/schema-x509.js +4 -2
- package/lib/trust.js +2 -2
- package/lib/tsp-sign.js +1 -1
- package/lib/webauthn.js +4 -4
- package/lib/webcrypto.js +17 -5
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/key.js
CHANGED
|
@@ -25,6 +25,8 @@ var constants = require("./constants");
|
|
|
25
25
|
var oid = require("./oid");
|
|
26
26
|
var pbes2 = require("./pbes2");
|
|
27
27
|
var pkcs8 = require("./schema-pkcs8");
|
|
28
|
+
var schemaPkcs1 = require("./schema-pkcs1");
|
|
29
|
+
var schemaSec1 = require("./schema-sec1");
|
|
28
30
|
var pkix = require("./schema-pkix");
|
|
29
31
|
var schema = require("./schema-engine");
|
|
30
32
|
var webcrypto = require("./webcrypto");
|
|
@@ -64,6 +66,60 @@ var INFER_ALG = _create(null);
|
|
|
64
66
|
function _isCryptoKey(x) { return webcrypto.isCryptoKeyLike(x); }
|
|
65
67
|
function _algName(a) { return typeof a === "string" ? a : (a && a.name); }
|
|
66
68
|
|
|
69
|
+
/** @internal The curve each SEC1 registry name is known by in WebCrypto, which names its curves
|
|
70
|
+
* the way RFC 5480 sec. 2.1.1.1 writes them and not the way the ASN.1 registry does. A curve with
|
|
71
|
+
* no row is one the engine cannot take, and saying so names the curve. */
|
|
72
|
+
var WEBCRYPTO_CURVE = _assign(_create(null), {
|
|
73
|
+
"prime256v1": "P-256", "secp384r1": "P-384", "secp521r1": "P-521",
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
/** @internal The byte doors the two named formats come in through. Each keeps its own PEM label,
|
|
77
|
+
* so an `EC PRIVATE KEY` block handed in as `pkcs1` is refused by label before it is read. */
|
|
78
|
+
var _PKCS1_ENTRY = _assign(_create(null), { pemLabel: "RSA PRIVATE KEY", PemError: PemError, ErrorClass: KeyError, prefix: "key" });
|
|
79
|
+
var _SEC1_ENTRY = _assign(_create(null), { pemLabel: "EC PRIVATE KEY", PemError: PemError, ErrorClass: KeyError, prefix: "key" });
|
|
80
|
+
var _PKCS1_PUBLIC_ENTRY = _assign(_create(null), { pemLabel: "RSA PUBLIC KEY", PemError: PemError, ErrorClass: KeyError, prefix: "key" });
|
|
81
|
+
|
|
82
|
+
/** @internal RFC 8017 App. A.1 gives the PKCS#1 encoding two structures, `RSAPrivateKey` and
|
|
83
|
+
* `RSAPublicKey`, with an armor label each. `opts.format` names the encoding, so both labels are
|
|
84
|
+
* that encoding: the one on the armor says which structure to expect, and the structure the bytes
|
|
85
|
+
* carry has to agree, so a key armored under the label of the half it is not is refused instead of
|
|
86
|
+
* being read as the other half. Bare DER carries no label and is classified by its structure. */
|
|
87
|
+
function _pkcs1Der(input) {
|
|
88
|
+
var armored = typeof input === "string" ||
|
|
89
|
+
(guard.bytes.isByteSource(input) && pkix.isPemArmor(guard.bytes.source(input, KeyError, "key/bad-input", "import")));
|
|
90
|
+
if (!armored) return pkix.coerceToDer(input, _PKCS1_ENTRY);
|
|
91
|
+
var der, saidPublic;
|
|
92
|
+
try { der = pkix.coerceToDer(input, _PKCS1_ENTRY); saidPublic = false; }
|
|
93
|
+
catch (e) {
|
|
94
|
+
if (!e || e.code !== "pem/label-mismatch") throw e;
|
|
95
|
+
der = pkix.coerceToDer(input, _PKCS1_PUBLIC_ENTRY);
|
|
96
|
+
saidPublic = true;
|
|
97
|
+
}
|
|
98
|
+
if (_looksLikeRsaPublic(der) !== saidPublic) {
|
|
99
|
+
throw _err("key/bad-input", "the PEM label says this is an RSA " + (saidPublic ? "PUBLIC" : "PRIVATE") +
|
|
100
|
+
" KEY and the bytes carry the other half (RFC 8017 App. A.1)");
|
|
101
|
+
}
|
|
102
|
+
return der;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** @internal PKCS#1 and SEC1 both carry a key and no algorithm identifier, so importing one means
|
|
106
|
+
* wrapping it in the structure that does. RFC 5958 sec. 2 for the private half, RFC 5280
|
|
107
|
+
* sec. 4.1.2.7 for the public one. The inner bytes are carried through unchanged: what the caller
|
|
108
|
+
* handed in is what the engine reads. */
|
|
109
|
+
function _pkcs1ToPkcs8(der) {
|
|
110
|
+
return b.sequence([b.integer(0n),
|
|
111
|
+
b.sequence([b.oid(O("rsaEncryption")), b.nullValue()]),
|
|
112
|
+
b.octetString(der)]);
|
|
113
|
+
}
|
|
114
|
+
function _pkcs1ToSpki(der) {
|
|
115
|
+
return b.sequence([b.sequence([b.oid(O("rsaEncryption")), b.nullValue()]), b.bitString(der, 0)]);
|
|
116
|
+
}
|
|
117
|
+
function _sec1ToPkcs8(der, curveOid) {
|
|
118
|
+
return b.sequence([b.integer(0n),
|
|
119
|
+
b.sequence([b.oid(O("ecPublicKey")), b.oid(curveOid)]),
|
|
120
|
+
b.octetString(der)]);
|
|
121
|
+
}
|
|
122
|
+
|
|
67
123
|
async function _toPrivateKeyDer(input) {
|
|
68
124
|
if (_isCryptoKey(input)) {
|
|
69
125
|
if (input.type !== "private") throw _err("key/bad-input", "a private CryptoKey is required (got a " + input.type + " key)");
|
|
@@ -210,8 +266,15 @@ function _decryptPbes2(encAlg, ciphertext, password, opts) {
|
|
|
210
266
|
* A key created non-extractable is refused with that as the reason,
|
|
211
267
|
* as is one whose implementation keeps its material out of this process's reach entirely.
|
|
212
268
|
*
|
|
269
|
+
* `format` also writes the encodings an operator's other tools read: `pkcs1` for the RFC 8017 App. A.1
|
|
270
|
+
* `RSAPrivateKey` or `RSAPublicKey`, `sec1` for the RFC 5915 `ECPrivateKey`, and `jwk` for an RFC 7517
|
|
271
|
+
* JSON Web Key. The two DER forms are pulled out of the wrapper the engine wrote, so the inner bytes are
|
|
272
|
+
* the engine's own; a SEC1 key is given the `[0] parameters` field the standalone form needs, which
|
|
273
|
+
* RFC 5915 sec. 3 omits while the key sits inside a PKCS#8. A key whose algorithm the encoding does not
|
|
274
|
+
* describe, an Ed25519 key asked for as PKCS#1 for instance, is refused with `key/bad-input`.
|
|
275
|
+
*
|
|
213
276
|
* @opts
|
|
214
|
-
* - `format` (string) -- `der` (default) or `
|
|
277
|
+
* - `format` (string) -- `der` (default), `pem`, `pkcs1`, `sec1` or `jwk`.
|
|
215
278
|
* - `label` (string) -- the PEM label (defaults `PRIVATE KEY` / `PUBLIC KEY` by key type).
|
|
216
279
|
* @example
|
|
217
280
|
* var pair = await pki.key.generate("Ed25519");
|
|
@@ -229,11 +292,21 @@ async function export_(key, opts) {
|
|
|
229
292
|
if (key.type === "private") defaultLabel = "PRIVATE KEY";
|
|
230
293
|
else if (key.type === "public") defaultLabel = "PUBLIC KEY";
|
|
231
294
|
else throw _err("key/bad-input", "export supports asymmetric (private / public) CryptoKeys only");
|
|
232
|
-
var der = await webcrypto.exportAnyKey(key, _err, "key/bad-input");
|
|
233
295
|
var fmt = opts.format || "der";
|
|
296
|
+
if (fmt === "jwk") {
|
|
297
|
+
try {
|
|
298
|
+
return await webcrypto.exportAnyKey(key, _err, "key/bad-input", "jwk");
|
|
299
|
+
} catch (e) {
|
|
300
|
+
if (e && e.isPkiError) throw e;
|
|
301
|
+
throw _err("key/bad-input", "exporting the key as a JWK failed", e);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
var der = await webcrypto.exportAnyKey(key, _err, "key/bad-input");
|
|
234
305
|
if (fmt === "der") return der;
|
|
235
306
|
if (fmt === "pem") return pkix.pemEncode(der, opts.label || defaultLabel, PemError);
|
|
236
|
-
|
|
307
|
+
if (fmt === "pkcs1") return _toPkcs1(der, key);
|
|
308
|
+
if (fmt === "sec1") return _toSec1(der, key);
|
|
309
|
+
throw _err("key/bad-input", "unsupported format " + guard.text.showValue(opts.format) + " (der / pem / pkcs1 / sec1 / jwk)");
|
|
237
310
|
}
|
|
238
311
|
|
|
239
312
|
/**
|
|
@@ -250,8 +323,16 @@ async function export_(key, opts) {
|
|
|
250
323
|
* SLH-DSA); RSA and EC are ambiguous between signing and key agreement, so `opts.algorithm` must be supplied
|
|
251
324
|
* for them (without it the import fails closed). Default key usages follow the algorithm and key type.
|
|
252
325
|
*
|
|
326
|
+
* `opts.format` reads an encoding that is not PKCS#8 or SubjectPublicKeyInfo: `pkcs1` for the RFC 8017
|
|
327
|
+
* App. A.1 `RSA PRIVATE KEY` and `RSA PUBLIC KEY`, `sec1` for the RFC 5915 `EC PRIVATE KEY`, and `jwk`
|
|
328
|
+
* for an RFC 7517 JSON Web Key. Neither DER encoding names an algorithm, so `opts.algorithm` is required
|
|
329
|
+
* with both and nothing is inferred; the key is wrapped in the structure that does name one and handed to
|
|
330
|
+
* the engine with its bytes unchanged. A multi-prime RSA key reads as a structure through
|
|
331
|
+
* `pki.schema.pkcs1.parse` and is refused here with `key/unsupported-key`.
|
|
332
|
+
*
|
|
253
333
|
* @opts
|
|
254
|
-
* - `algorithm` (string | object) -- the WebCrypto algorithm (required for RSA / EC; overrides inference).
|
|
334
|
+
* - `algorithm` (string | object) -- the WebCrypto algorithm (required for RSA / EC / pkcs1 / sec1 / jwk; overrides inference).
|
|
335
|
+
* - `format` (string) -- `pkcs1`, `sec1` or `jwk`; omit to read PKCS#8 or SubjectPublicKeyInfo.
|
|
255
336
|
* - `usages` (string[]) -- key usages (default derived from the algorithm and public/private type).
|
|
256
337
|
* - `extractable` (boolean) -- default false.
|
|
257
338
|
* - `password` (string | Buffer) -- decrypt an `ENCRYPTED PRIVATE KEY` first.
|
|
@@ -260,12 +341,26 @@ async function export_(key, opts) {
|
|
|
260
341
|
* var spki = await pki.key.export(pair.publicKey);
|
|
261
342
|
* var pub = await pki.key.import(spki); // Ed25519 -- algorithm inferred
|
|
262
343
|
*/
|
|
263
|
-
var _IMPORT_OPTS = _assign(_create(null), { algorithm: 1, extractable: 1, password: 1, usages: 1 });
|
|
344
|
+
var _IMPORT_OPTS = _assign(_create(null), { algorithm: 1, extractable: 1, password: 1, usages: 1, format: 1 });
|
|
345
|
+
var _IMPORT_FORMATS = _assign(_create(null), { pkcs1: 1, sec1: 1, jwk: 1 });
|
|
346
|
+
|
|
347
|
+
/** @internal Which named encoding the caller asked for, or null when they named none and the door
|
|
348
|
+
* detects PKCS#8 or SubjectPublicKeyInfo as it always has. A value outside the set is refused
|
|
349
|
+
* here, before any byte of the input is read. */
|
|
350
|
+
function _namedImportFormat(value) {
|
|
351
|
+
if (value == null) return null;
|
|
352
|
+
var name = typeof value === "string" ? guard.text.keyOf(value) : null;
|
|
353
|
+
if (name === null || _IMPORT_FORMATS[name] !== 1) {
|
|
354
|
+
throw _err("key/bad-input", "opts.format is pkcs1, sec1 or jwk; leave it out to read PKCS#8 or SubjectPublicKeyInfo. Got " + guard.text.showValue(value));
|
|
355
|
+
}
|
|
356
|
+
return name;
|
|
357
|
+
}
|
|
264
358
|
|
|
265
359
|
async function import_(input, opts) {
|
|
266
360
|
opts = guard.identifier.optionsObject(opts, _err, "key/bad-input", "pki.key.import options");
|
|
267
361
|
guard.identifier.assertKnownKeys(opts, _IMPORT_OPTS, _err, "key/bad-input",
|
|
268
362
|
"pki.key.import has an unknown option: ");
|
|
363
|
+
if (_namedImportFormat(opts.format) !== null) return await _importNamedFormat(input, opts);
|
|
269
364
|
var detected = _detectKeyInput(input);
|
|
270
365
|
if (detected.format === "encrypted") {
|
|
271
366
|
if (opts.password == null) throw _err("key/bad-input", "an ENCRYPTED PRIVATE KEY requires opts.password to import");
|
|
@@ -1014,6 +1109,156 @@ function _inferAlgorithm(detected) {
|
|
|
1014
1109
|
" (RSA and EC are ambiguous between signing and key agreement -- pass opts.algorithm)");
|
|
1015
1110
|
}
|
|
1016
1111
|
|
|
1112
|
+
/** @internal Import from an encoding the caller named. PKCS#1 and SEC1 carry no algorithm, so
|
|
1113
|
+
* `opts.algorithm` is required for both and there is no inference to fall back on. JWK carries its
|
|
1114
|
+
* own key type and goes to the engine as it arrived. */
|
|
1115
|
+
async function _importNamedFormat(input, opts) {
|
|
1116
|
+
var fmt = guard.text.keyOf(opts.format);
|
|
1117
|
+
var extractable = opts.extractable != null ? opts.extractable : false;
|
|
1118
|
+
if (fmt === "jwk") {
|
|
1119
|
+
if (input === null || typeof input !== "object" || _isArray(input)) {
|
|
1120
|
+
throw _err("key/bad-input", "a JWK is a JSON object (RFC 7517 sec. 4)");
|
|
1121
|
+
}
|
|
1122
|
+
if (typeof input.kty !== "string" || input.kty === "") {
|
|
1123
|
+
throw _err("key/bad-input", "a JWK carries a kty naming its key type (RFC 7517 sec. 4.1)");
|
|
1124
|
+
}
|
|
1125
|
+
if (opts.algorithm == null) throw _err("key/bad-input", "importing a JWK needs opts.algorithm");
|
|
1126
|
+
var jwkPublic = input.d === undefined && input.priv === undefined;
|
|
1127
|
+
var jwkUsages = opts.usages || _importUsages(_algName(opts.algorithm), jwkPublic);
|
|
1128
|
+
try {
|
|
1129
|
+
return await subtle.importKey("jwk", input, opts.algorithm, extractable, jwkUsages);
|
|
1130
|
+
} catch (e) {
|
|
1131
|
+
if (e && e.isPkiError) throw e;
|
|
1132
|
+
throw _err("key/bad-input", "importing the JWK failed", e);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
|
|
1136
|
+
if (opts.algorithm == null) {
|
|
1137
|
+
throw _err("key/bad-input", "a " + (fmt === "pkcs1" ? "PKCS#1" : "SEC1") +
|
|
1138
|
+
" key names no algorithm (RFC 8017 App. A.1, RFC 5915 sec. 3), so opts.algorithm says what it is");
|
|
1139
|
+
}
|
|
1140
|
+
/** @internal `opts.format` names the ENCODING, and RFC 8017 App. A.1 gives that encoding two
|
|
1141
|
+
* structures with an armor label each. So both labels are read, and the structure the bytes
|
|
1142
|
+
* carry decides which half arrived; a label naming the half the bytes are not is refused
|
|
1143
|
+
* rather than read as the other. SEC1 has one structure and keeps its single label. */
|
|
1144
|
+
var der;
|
|
1145
|
+
if (fmt === "pkcs1") {
|
|
1146
|
+
der = _pkcs1Der(input);
|
|
1147
|
+
} else {
|
|
1148
|
+
der = pkix.coerceToDer(input, _SEC1_ENTRY);
|
|
1149
|
+
}
|
|
1150
|
+
var wrapped, isPublic = false;
|
|
1151
|
+
if (fmt === "pkcs1") {
|
|
1152
|
+
var isPub = _looksLikeRsaPublic(der);
|
|
1153
|
+
isPublic = isPub;
|
|
1154
|
+
if (isPub) {
|
|
1155
|
+
schemaPkcs1.parsePublic(der);
|
|
1156
|
+
wrapped = _pkcs1ToSpki(der);
|
|
1157
|
+
} else {
|
|
1158
|
+
var rsa = schemaPkcs1.parse(der);
|
|
1159
|
+
if (rsa.otherPrimeInfos.length > 0) {
|
|
1160
|
+
throw _err("key/unsupported-key", "a multi-prime RSA key is readable as a structure and has no arm in this toolkit's crypto engine (RFC 8017 App. A.1.2)");
|
|
1161
|
+
}
|
|
1162
|
+
wrapped = _pkcs1ToPkcs8(der);
|
|
1163
|
+
}
|
|
1164
|
+
} else {
|
|
1165
|
+
var ec = schemaSec1.parse(der);
|
|
1166
|
+
var named = WEBCRYPTO_CURVE[ec.curve];
|
|
1167
|
+
if (named === undefined) {
|
|
1168
|
+
throw _err("key/unsupported-key", "the curve " + ec.curve + " has no WebCrypto name in this toolkit");
|
|
1169
|
+
}
|
|
1170
|
+
var wanted = opts.algorithm && opts.algorithm.namedCurve;
|
|
1171
|
+
if (wanted != null && wanted !== named) {
|
|
1172
|
+
throw _err("key/bad-input", "the key names the curve " + named + " and opts.algorithm names " + guard.text.showValue(wanted));
|
|
1173
|
+
}
|
|
1174
|
+
wrapped = _sec1ToPkcs8(der, ec.curveOid);
|
|
1175
|
+
}
|
|
1176
|
+
var usages = opts.usages || _importUsages(_algName(opts.algorithm), isPublic);
|
|
1177
|
+
try {
|
|
1178
|
+
return await subtle.importKey(isPublic ? "spki" : "pkcs8", wrapped, opts.algorithm, extractable, usages);
|
|
1179
|
+
} catch (e) {
|
|
1180
|
+
if (e && e.isPkiError) throw e;
|
|
1181
|
+
throw _err("key/bad-input", "importKey failed", e);
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
/** @internal The inverse of the wrap: pull the inner key out of the PKCS#8 or SPKI the engine
|
|
1186
|
+
* wrote. The bytes come back as they went in, since the engine carries the inner structure
|
|
1187
|
+
* unchanged and this reads it rather than re-encoding it. */
|
|
1188
|
+
function _toPkcs1(der, key) {
|
|
1189
|
+
var algOid = _wrappedAlgorithmOid(der, key);
|
|
1190
|
+
if (algOid !== O("rsaEncryption")) {
|
|
1191
|
+
throw _err("key/bad-input", "PKCS#1 is an RSA encoding (RFC 8017 App. A.1) and this key is " + _algName(key.algorithm));
|
|
1192
|
+
}
|
|
1193
|
+
var node = asn1.decode(der);
|
|
1194
|
+
if (key.type === "public") {
|
|
1195
|
+
var bits = asn1.read.bitString(node.children[1]);
|
|
1196
|
+
if (bits.unusedBits !== 0) throw _err("key/bad-input", "the exported public key does not hold whole octets");
|
|
1197
|
+
schemaPkcs1.parsePublic(bits.bytes);
|
|
1198
|
+
return bits.bytes;
|
|
1199
|
+
}
|
|
1200
|
+
var inner = asn1.read.octetString(node.children[2]);
|
|
1201
|
+
schemaPkcs1.parse(inner);
|
|
1202
|
+
return inner;
|
|
1203
|
+
}
|
|
1204
|
+
function _toSec1(der, key) {
|
|
1205
|
+
var algOid = _wrappedAlgorithmOid(der, key);
|
|
1206
|
+
if (algOid !== O("ecPublicKey")) {
|
|
1207
|
+
throw _err("key/bad-input", "SEC1 is an elliptic-curve private-key encoding (RFC 5915 sec. 3) and this key is " + _algName(key.algorithm));
|
|
1208
|
+
}
|
|
1209
|
+
if (key.type !== "private") {
|
|
1210
|
+
throw _err("key/bad-input", "SEC1 encodes a private key; export the public half as SubjectPublicKeyInfo");
|
|
1211
|
+
}
|
|
1212
|
+
var node = asn1.decode(der);
|
|
1213
|
+
var inner = asn1.read.octetString(node.children[2]);
|
|
1214
|
+
/** @internal RFC 5915 sec. 3 says the [0] parameters field SHOULD be omitted when the key sits
|
|
1215
|
+
* inside a PKCS#8, whose AlgorithmIdentifier already names the curve, and SHOULD be present when
|
|
1216
|
+
* the key stands alone. So the curve is carried across from the wrapper here: without it the
|
|
1217
|
+
* standalone key would not say which curve its scalar belongs to. */
|
|
1218
|
+
var curveOid = asn1.read.oid(node.children[1].children[1]);
|
|
1219
|
+
var innerNode = asn1.decode(inner);
|
|
1220
|
+
var hasParams = false;
|
|
1221
|
+
for (var i = 0; i < innerNode.children.length; i++) {
|
|
1222
|
+
if (innerNode.children[i].tagClass === "context" && innerNode.children[i].tagNumber === 0) hasParams = true;
|
|
1223
|
+
}
|
|
1224
|
+
if (!hasParams) {
|
|
1225
|
+
var stored;
|
|
1226
|
+
for (var j = 0; j < innerNode.children.length; j++) {
|
|
1227
|
+
var c = innerNode.children[j];
|
|
1228
|
+
if (c.tagClass === "context" && c.tagNumber === 1) stored = asn1.read.bitString(c.children[0]);
|
|
1229
|
+
}
|
|
1230
|
+
inner = schemaSec1.encode({
|
|
1231
|
+
version: 1,
|
|
1232
|
+
privateKey: asn1.read.octetString(innerNode.children[1]),
|
|
1233
|
+
curveOid: curveOid,
|
|
1234
|
+
publicKey: stored === undefined ? null : stored.bytes,
|
|
1235
|
+
});
|
|
1236
|
+
}
|
|
1237
|
+
schemaSec1.parse(inner);
|
|
1238
|
+
return inner;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/** @internal The algorithm the wrapper names, read from whichever wrapper the key type produced:
|
|
1242
|
+
* a PrivateKeyInfo names it second, a SubjectPublicKeyInfo first. */
|
|
1243
|
+
function _wrappedAlgorithmOid(der, key) {
|
|
1244
|
+
var node;
|
|
1245
|
+
try { node = asn1.decode(der); } catch (e) { throw _err("key/bad-input", "the exported key is not DER", e); }
|
|
1246
|
+
var algSeq = key.type === "public" ? node.children[0] : node.children[1];
|
|
1247
|
+
if (!algSeq || !algSeq.children || algSeq.children.length === 0) {
|
|
1248
|
+
throw _err("key/bad-input", "the exported key names no algorithm");
|
|
1249
|
+
}
|
|
1250
|
+
return asn1.read.oid(algSeq.children[0]);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
/** @internal An RSAPublicKey is two INTEGERs and an RSAPrivateKey is nine or ten, so the two
|
|
1254
|
+
* encodings are told apart by counting the children of the top-level SEQUENCE. Neither door reads
|
|
1255
|
+
* the other's structure, and the count decides which one this is before either runs. */
|
|
1256
|
+
function _looksLikeRsaPublic(der) {
|
|
1257
|
+
var node;
|
|
1258
|
+
try { node = asn1.decode(der); } catch (e) { throw _err("key/bad-input", "the input is not DER", e); }
|
|
1259
|
+
return !!(node && node.children && node.children.length === 2);
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1017
1262
|
function _importUsages(name, isPublic) {
|
|
1018
1263
|
var n = _toUpperCase(_String(name == null ? "" : name));
|
|
1019
1264
|
if (n === "X25519" || n === "X448" || n === "ECDH") return isPublic ? [] : ["deriveBits", "deriveKey"];
|
package/lib/ocsp.js
CHANGED
|
@@ -39,6 +39,7 @@ var subtle = webcrypto.webcrypto.subtle;
|
|
|
39
39
|
var guard = require("./guard-all");
|
|
40
40
|
var constants = require("./constants");
|
|
41
41
|
var frameworkError = require("./framework-error");
|
|
42
|
+
var E = _err;
|
|
42
43
|
|
|
43
44
|
var OcspError = frameworkError.OcspError;
|
|
44
45
|
var b = asn1.build;
|
|
@@ -651,30 +652,21 @@ function verify(response, opts) {
|
|
|
651
652
|
issuerCert = _certOf(opts.issuer, "the issuer certificate");
|
|
652
653
|
time = opts.time == null ? new Date() : _asDate(opts.time);
|
|
653
654
|
} catch (e) { return Promise.reject(e); }
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
}
|
|
668
|
-
return guard.verdict.of(out, { valid: out.status === "good" });
|
|
655
|
+
/** @internal The nonce rule is applied by the acceptance gate rather than restated here, so this
|
|
656
|
+
* door and every other consumer of that gate answer alike. The bytes are snapshotted here so a
|
|
657
|
+
* fault in them carries this door's own code; a value that is not bytes is passed through for the
|
|
658
|
+
* gate to fail closed on, which is what a caller asking for a nonce with something that cannot be
|
|
659
|
+
* one has earned. */
|
|
660
|
+
var reqNonce = opts.requestNonce;
|
|
661
|
+
if (guard.bytes.isByteSource(reqNonce)) {
|
|
662
|
+
try { reqNonce = guard.bytes.snapshotSource(reqNonce, OcspError, "ocsp/bad-input", "opts.requestNonce"); }
|
|
663
|
+
catch (e) { return Promise.reject(e); }
|
|
664
|
+
}
|
|
665
|
+
return pathValidate.verifyOcspResponse(parsed, cert, issuerCert, time,
|
|
666
|
+
{ historicalMode: opts.historicalMode === true, requestNonce: reqNonce }).then(function (verdict) {
|
|
667
|
+
return guard.verdict.of(verdict, { valid: verdict.status === "good" });
|
|
669
668
|
});
|
|
670
669
|
}
|
|
671
|
-
function _responseNonce(parsed) {
|
|
672
|
-
var br = parsed && parsed.basicResponse;
|
|
673
|
-
var exts = (br && br.responseExtensions) || [];
|
|
674
|
-
for (var i = 0; i < exts.length; i++) if (exts[i].oid === OID_OCSP_NONCE) return exts[i].nonce || null;
|
|
675
|
-
return null;
|
|
676
|
-
}
|
|
677
|
-
|
|
678
670
|
var _VERIFY_REQUEST_OPTS = Object.assign(Object.create(null), { certs: 1 });
|
|
679
671
|
function _requestFromBytes(request) {
|
|
680
672
|
if (guard.bytes.isByteSource(request)) request = guard.bytes.snapshotSource(request, OcspError, "ocsp/bad-input", "the OCSP request");
|
|
@@ -828,8 +820,119 @@ async function verifyRequest(request, opts) {
|
|
|
828
820
|
"the OCSP request signature does not verify over tbsRequest under any opts.certs entry");
|
|
829
821
|
}
|
|
830
822
|
|
|
823
|
+
/** @internal RFC 5019 sec. 5 measures the GET decision over "the scheme and delimiters (http://),
|
|
824
|
+
* server name and base64-encoded OCSPRequest structure", which is the whole encoded URL. */
|
|
825
|
+
var HTTP_GET_MAX_URL_BYTES = 255;
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* @primitive pki.ocsp.httpRequest
|
|
829
|
+
* @signature pki.ocsp.httpRequest(der, responderUrl, opts?) -> { method, url, headers, body }
|
|
830
|
+
* @since 0.8.8
|
|
831
|
+
* @status stable
|
|
832
|
+
* @spec RFC 6960, RFC 5019
|
|
833
|
+
* @related pki.ocsp.buildRequest, pki.path.fetchingChecker
|
|
834
|
+
*
|
|
835
|
+
* Shape a DER `OCSPRequest` into the HTTP request RFC 6960 Appendix A.1 defines, without
|
|
836
|
+
* opening a socket: the result is the method, the URL, the headers and the body a caller
|
|
837
|
+
* hands to its own transport.
|
|
838
|
+
*
|
|
839
|
+
* A GET carries the request in the URL, as the base64 of the DER percent-encoded into one
|
|
840
|
+
* path segment appended to the responder URI. RFC 5019 sec. 5 requires GET when the whole
|
|
841
|
+
* encoded URL is 255 bytes or fewer, counting the scheme, the delimiters, the server name and
|
|
842
|
+
* the encoded request, and that is what is measured; anything longer is a POST carrying
|
|
843
|
+
* `Content-Type: application/ocsp-request` and the DER as its body. The base64 carries no CR
|
|
844
|
+
* and no LF, which that section also requires.
|
|
845
|
+
*
|
|
846
|
+
* The encoding escapes every byte outside RFC 3986 sec. 2.3's unreserved set, so the `+`, `/`
|
|
847
|
+
* and `=` of base64 become triplets and a request cannot reach past the segment it was placed
|
|
848
|
+
* in. A responder URL carrying a query or a fragment is refused, since a path segment appended
|
|
849
|
+
* after either would not be part of the path.
|
|
850
|
+
*
|
|
851
|
+
* @opts method `"GET"` or `"POST"` to override the sec. 5 measurement. A GET asked for on a
|
|
852
|
+
* request too long to carry that way is refused rather than truncated.
|
|
853
|
+
* @example
|
|
854
|
+
* var ca = await pki.key.generate("Ed25519");
|
|
855
|
+
* var caDer = await pki.x509.sign({ subject: "Example CA", subjectPublicKey: await pki.key.export(ca.publicKey),
|
|
856
|
+
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
|
|
857
|
+
* extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign"], subjectKeyIdentifier: true } },
|
|
858
|
+
* { key: await pki.key.export(ca.privateKey) });
|
|
859
|
+
* var req = await pki.ocsp.buildRequest({ cert: caDer, issuer: caDer }, { profile: "lightweight" });
|
|
860
|
+
* var http = pki.ocsp.httpRequest(req, "http://ocsp.example/");
|
|
861
|
+
* console.log(http.method, http.url.length <= 255);
|
|
862
|
+
*/
|
|
863
|
+
function httpRequest(der, responderUrl, opts) {
|
|
864
|
+
opts = guard.identifier.optionsObject(opts, E, "ocsp/bad-input", "httpRequest: opts");
|
|
865
|
+
guard.identifier.assertKnownKeys(opts, HTTP_REQUEST_OPTS, E, "ocsp/bad-input",
|
|
866
|
+
"pki.ocsp.httpRequest has an unknown option (the only option is `method`). The unknown option was: ");
|
|
867
|
+
var body = guard.bytes.snapshotSource(der, OcspError, "ocsp/bad-input", "httpRequest: the OCSPRequest DER");
|
|
868
|
+
if (body.length === 0) throw E("ocsp/bad-input", "httpRequest: the OCSPRequest DER is empty");
|
|
869
|
+
if (typeof responderUrl !== "string" || responderUrl === "") {
|
|
870
|
+
throw E("ocsp/bad-input", "httpRequest: the responder URL must be a non-empty string");
|
|
871
|
+
}
|
|
872
|
+
var parts = guard.name.uriParts(responderUrl);
|
|
873
|
+
if (parts === null || !parts.hasAuthority || parts.host === null) {
|
|
874
|
+
throw E("ocsp/bad-input", "httpRequest: the responder URL must carry a scheme and a host (RFC 5280 sec. 4.2.2.1)");
|
|
875
|
+
}
|
|
876
|
+
if (responderUrl.indexOf("?") !== -1 || responderUrl.indexOf("#") !== -1) {
|
|
877
|
+
throw E("ocsp/bad-input", "httpRequest: the responder URL carries a query or a fragment, and a path segment appended after either would not be part of the path (RFC 3986 sec. 3.3)");
|
|
878
|
+
}
|
|
879
|
+
/** @internal The bound below is a byte count, and a character is not a byte above US-ASCII. A
|
|
880
|
+
* URI is US-ASCII by RFC 3986 sec. 2; a responder URL carrying more than that is an IRI, whose
|
|
881
|
+
* conversion to a URI is RFC 3987 work this toolkit does not do, so it is refused rather than
|
|
882
|
+
* measured as though the two were the same length. */
|
|
883
|
+
if (!guard.encoding.isAsciiText(responderUrl)) {
|
|
884
|
+
throw E("ocsp/bad-input", "httpRequest: the responder URL must be US-ASCII (RFC 3986 sec. 2); percent-encode the rest before passing it, since the RFC 5019 sec. 5 size bound counts the bytes that go on the wire");
|
|
885
|
+
}
|
|
886
|
+
var asked = opts.method === undefined ? null : opts.method;
|
|
887
|
+
if (asked !== null && asked !== "GET" && asked !== "POST") {
|
|
888
|
+
throw E("ocsp/bad-input", "httpRequest: opts.method must be \"GET\" or \"POST\"");
|
|
889
|
+
}
|
|
890
|
+
/** @internal The base64 goes through the buffer encoder, which emits one line and therefore no
|
|
891
|
+
* CR and no LF, which RFC 5019 sec. 5 states as a MUST NOT. */
|
|
892
|
+
var b64 = body.toString("base64");
|
|
893
|
+
var segment = guard.encoding.uriPathSegment(b64, E, "ocsp/bad-input", "the encoded OCSPRequest");
|
|
894
|
+
/** @internal The URL is handed to a transport, which parses it before opening a socket, and RFC
|
|
895
|
+
* 3986 sec. 5.2.4 removes a `.` or `..` segment during that parse. Appending to an unnormalized
|
|
896
|
+
* path would therefore build one URL and send another, at a path the responder was never named
|
|
897
|
+
* at. Normalizing the base first makes what this returns what goes on the wire, which is also
|
|
898
|
+
* the string the sec. 5 size bound has to be measured on. The appended segment is base64 under
|
|
899
|
+
* the sec. 2.3 unreserved set, which holds no `.`, so it cannot become a dot segment itself. */
|
|
900
|
+
var normalized;
|
|
901
|
+
try { normalized = new URL(responderUrl).href; }
|
|
902
|
+
catch (e) { throw E("ocsp/bad-input", "httpRequest: the responder URL does not parse as a URL: " + guard.text.describeThrown(e), e); }
|
|
903
|
+
var base = normalized.charAt(normalized.length - 1) === "/"
|
|
904
|
+
? normalized : normalized + "/";
|
|
905
|
+
var getUrl = base + segment;
|
|
906
|
+
/** @internal Both parts are US-ASCII by the checks above, so a character is a byte and the
|
|
907
|
+
* count is the one RFC 5019 sec. 5 states. */
|
|
908
|
+
var getUrlBytes = Buffer.byteLength(getUrl, "ascii");
|
|
909
|
+
var fitsGet = getUrlBytes <= HTTP_GET_MAX_URL_BYTES;
|
|
910
|
+
if (asked === "GET" && !fitsGet) {
|
|
911
|
+
throw E("ocsp/bad-input", "httpRequest: the encoded GET URL is " + getUrlBytes +
|
|
912
|
+
" bytes, over the " + HTTP_GET_MAX_URL_BYTES + " RFC 5019 sec. 5 allows; send this request as a POST");
|
|
913
|
+
}
|
|
914
|
+
var useGet = asked === "GET" || (asked === null && fitsGet);
|
|
915
|
+
if (useGet) {
|
|
916
|
+
return guard.verdict.of({ method: "GET", url: getUrl, headers: _frozenHeaders({ accept: "application/ocsp-response" }), body: null });
|
|
917
|
+
}
|
|
918
|
+
return guard.verdict.of({
|
|
919
|
+
method: "POST", url: normalized,
|
|
920
|
+
headers: _frozenHeaders({ "content-type": "application/ocsp-request", accept: "application/ocsp-response" }),
|
|
921
|
+
body: body,
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
var HTTP_REQUEST_OPTS = Object.assign(Object.create(null), { method: 1 });
|
|
926
|
+
|
|
927
|
+
function _frozenHeaders(h) {
|
|
928
|
+
var out = Object.create(null), k;
|
|
929
|
+
for (k in h) { if (Object.prototype.hasOwnProperty.call(h, k)) out[k] = h[k]; }
|
|
930
|
+
return Object.freeze(out);
|
|
931
|
+
}
|
|
932
|
+
|
|
831
933
|
module.exports = {
|
|
832
934
|
buildRequest: buildRequest,
|
|
935
|
+
httpRequest: httpRequest,
|
|
833
936
|
sign: sign,
|
|
834
937
|
buildErrorResponse: buildErrorResponse,
|
|
835
938
|
verify: verify,
|
package/lib/oid.js
CHANGED
|
@@ -116,7 +116,7 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
116
116
|
"id-cct-PKIData": 2, "id-cct-PKIResponse": 3,
|
|
117
117
|
} },
|
|
118
118
|
|
|
119
|
-
pkixOn: { base: [1, 3, 6, 1, 5, 5, 7, 8], of: { hardwareModuleName: 4, smtpUtf8Mailbox: 9, macAddress: 12 } },
|
|
119
|
+
pkixOn: { base: [1, 3, 6, 1, 5, 5, 7, 8], of: { hardwareModuleName: 4, dnsSRV: 7, smtpUtf8Mailbox: 9, macAddress: 12 } },
|
|
120
120
|
|
|
121
121
|
cabfPolicy: { base: [2, 23, 140, 1], of: {
|
|
122
122
|
"ev-guidelines": 1, "domain-validated": [2, 1], "organization-validated": [2, 2], "individual-validated": [2, 3] } },
|