@blamejs/pki 0.5.6 → 0.5.7
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 +23 -1
- package/MIGRATING.md +22 -0
- package/lib/attrcert-sign.js +5 -1
- package/lib/cmc-build.js +8 -13
- package/lib/cmc-verify.js +2 -2
- package/lib/cmp-build.js +7 -2
- package/lib/cmp-session.js +4 -2
- package/lib/cmp-verify.js +1 -1
- package/lib/cms-compress.js +1 -2
- package/lib/cms-decrypt.js +2 -4
- package/lib/cms-encrypt.js +1 -2
- package/lib/cms-sign.js +33 -6
- package/lib/cms-verify.js +46 -9
- package/lib/crl-sign.js +9 -3
- package/lib/crmf-sign.js +5 -1
- package/lib/csr-sign.js +5 -1
- package/lib/est.js +5 -4
- package/lib/guard-bytes.js +368 -5
- package/lib/guard-parsed.js +71 -2
- package/lib/ocsp.js +19 -7
- package/lib/pkcs12-build.js +21 -6
- package/lib/pki-build.js +2 -3
- package/lib/schema-cms.js +133 -0
- package/lib/sign-scheme.js +7 -4
- package/lib/tsp-sign.js +5 -1
- package/lib/validator-tpm.js +1 -1
- package/lib/webauthn-mds.js +1 -1
- package/lib/webauthn.js +1 -1
- package/lib/x509-sign.js +11 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/guard-bytes.js
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
// primitives whose input boundaries compose these guards (pki.asn1.decode,
|
|
7
7
|
// pki.cbor.decode, pki.ct.parseSctList, pki.webcrypto.*).
|
|
8
8
|
//
|
|
9
|
+
var async = require("./guard-async");
|
|
10
|
+
|
|
9
11
|
// guard-bytes -- fail-closed coercion of an untrusted byte-source input to a
|
|
10
12
|
// Buffer view. One of the enforced choke points of the guard family: a
|
|
11
13
|
// codebase-patterns detector requires every byte-input boundary to route
|
|
@@ -23,6 +25,26 @@
|
|
|
23
25
|
// are per-format (a multi-MB CRL is legitimate, a Merkle proof is tiny), so they
|
|
24
26
|
// live in guard-params and each decoder's own cap.
|
|
25
27
|
|
|
28
|
+
// Choosing between `view` and `snapshot` at a boundary: a byte argument that is read only
|
|
29
|
+
// during the synchronous pass that received it can be re-VIEWED, because nothing can run
|
|
30
|
+
// between the check and the use. One that survives that pass -- stored in state, compared
|
|
31
|
+
// after an await, embedded into output the verb assembles later -- must be SNAPSHOT, or the
|
|
32
|
+
// caller can still rewrite the bytes after they were validated. The exceptions are the two
|
|
33
|
+
// places holding a caller's SECRET, where a copy is the worse defect: a password or a private
|
|
34
|
+
// key is re-viewed and left borrowed only when this module also clears the copy it made, so
|
|
35
|
+
// no plaintext duplicate outlives the derivation.
|
|
36
|
+
|
|
37
|
+
// The guard family threads a caller's typed error under two conventions: most guards take a
|
|
38
|
+
// FACTORY (`E(code, message, cause)`, no `new`) because the engines that call them -- sign-scheme,
|
|
39
|
+
// composite-sig, pki-build -- carry a bound factory that prefixes the caller's domain onto the
|
|
40
|
+
// code; guard-bytes and guard-header take the error CLASS. A boundary that has only one of the two
|
|
41
|
+
// would otherwise have to hand-roll the re-view to reach this guard at all, which is precisely what
|
|
42
|
+
// the re-inline detector forbids, so accept either: a class (its prototype is an Error) is
|
|
43
|
+
// constructed, anything else is called.
|
|
44
|
+
function _raise(E, code, message, cause) {
|
|
45
|
+
return (E.prototype instanceof Error) ? new E(code, message, cause) : E(code, message, cause);
|
|
46
|
+
}
|
|
47
|
+
|
|
26
48
|
// view(input, ErrorClass, code, label) -> Buffer view | throws ErrorClass(code, msg, cause)
|
|
27
49
|
// Accepts a Buffer / Uint8Array -- the DER / CBOR / CT / Merkle input contract.
|
|
28
50
|
// ErrorClass MUST be a withCause PkiError subclass (the raw detach failure is
|
|
@@ -34,10 +56,10 @@ function view(input, ErrorClass, code, label) {
|
|
|
34
56
|
try {
|
|
35
57
|
return Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
36
58
|
} catch (e) {
|
|
37
|
-
throw
|
|
59
|
+
throw _raise(ErrorClass, code, label + ": input is not a usable byte view (detached backing buffer?)", e);
|
|
38
60
|
}
|
|
39
61
|
}
|
|
40
|
-
throw
|
|
62
|
+
throw _raise(ErrorClass, code, label + ": expected a Buffer / Uint8Array");
|
|
41
63
|
}
|
|
42
64
|
|
|
43
65
|
// source(input, ErrorClass, code, label) -> Buffer | throws ErrorClass
|
|
@@ -55,10 +77,10 @@ function source(input, ErrorClass, code, label) {
|
|
|
55
77
|
try {
|
|
56
78
|
return isAb ? Buffer.from(input) : Buffer.from(input.buffer, input.byteOffset, input.byteLength);
|
|
57
79
|
} catch (e) {
|
|
58
|
-
throw
|
|
80
|
+
throw _raise(ErrorClass, code, label + ": input is not a usable byte source (detached backing buffer?)", e);
|
|
59
81
|
}
|
|
60
82
|
}
|
|
61
|
-
throw
|
|
83
|
+
throw _raise(ErrorClass, code, label + ": expected a BufferSource (ArrayBuffer / TypedArray / Buffer)");
|
|
62
84
|
}
|
|
63
85
|
|
|
64
86
|
// snapshot(input, ErrorClass, code, label) -> private Buffer copy | throws ErrorClass
|
|
@@ -97,4 +119,345 @@ function snapshotSource(input, ErrorClass, code, label) {
|
|
|
97
119
|
return Buffer.from(source(input, ErrorClass, code, label));
|
|
98
120
|
}
|
|
99
121
|
|
|
100
|
-
|
|
122
|
+
// snapshotDeep(value, ErrorClass, code, label) -> a private copy of every byte leaf | throws
|
|
123
|
+
//
|
|
124
|
+
// `snapshot` closes the parse-then-verify window for ONE buffer. This closes it for a whole
|
|
125
|
+
// caller-supplied SPEC -- the object a producing verb is handed and then reads across several
|
|
126
|
+
// promise turns while it resolves a key, hashes, and signs. Every byte the verb ends up
|
|
127
|
+
// encoding reaches it through some field of that object, and the caller still owns all of them,
|
|
128
|
+
// so a spec validated at entry and read again after the first turn need not describe the same
|
|
129
|
+
// certificate, CRL, or request. It is the same defect as an aliased buffer, one level up: the
|
|
130
|
+
// value that passed the checks and the value that gets signed are two different reads.
|
|
131
|
+
//
|
|
132
|
+
// Copies byte views (through `snapshotSource`, so a detached backing store is refused rather
|
|
133
|
+
// than copied as empty), arrays, dates, and PLAIN objects. Everything else -- a string, number,
|
|
134
|
+
// bigint, boolean, a CryptoKey, any class instance -- is passed through by reference, because a
|
|
135
|
+
// non-plain object is a platform or library handle whose identity is what makes it work; cloning
|
|
136
|
+
// one would break it. `opts.maxDepth` bounds a cyclic or hostile structure; the cap is set far
|
|
137
|
+
// above any real spec (the deepest is a certificate policy's UserNotice noticeRef, around 12).
|
|
138
|
+
//
|
|
139
|
+
// **A spec that carries SECRET material must pass `opts.collect`**, an array this fills with every
|
|
140
|
+
// buffer it copied. Copying a password or a private key produces a plaintext duplicate, and a
|
|
141
|
+
// module whose ownership rules say "a caller's Buffer is borrowed, so leave it alone" will not
|
|
142
|
+
// clear it -- the copy outlives the operation with no one accountable for it. Collect the copies
|
|
143
|
+
// and zeroize them in a `finally`, so the fix for one defect does not open another.
|
|
144
|
+
// @enforced-by behavioral -- a copy has no rename-proof code shape to detect (any `Buffer.from(x)`
|
|
145
|
+
// is one). The guards are the RED vector that rewrites a field of the caller's spec after the
|
|
146
|
+
// verb has returned its promise and asserts the emitted artifact carries the entry value, and
|
|
147
|
+
// the vector that holds a reference to every copy and asserts it reads all-zero after the call.
|
|
148
|
+
function snapshotDeep(value, ErrorClass, code, label, opts) {
|
|
149
|
+
var o = opts || {};
|
|
150
|
+
var cap = o.maxDepth == null ? 64 : o.maxDepth;
|
|
151
|
+
return _deep(value, ErrorClass, code, label, cap, 0, o.collect || null);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Is this object a HANDLE -- something a copy would break rather than duplicate?
|
|
155
|
+
//
|
|
156
|
+
// The line is not "does it have Object.prototype". A caller's own class instance used as an
|
|
157
|
+
// options bag is data, and passing it through by reference on the strength of its prototype left
|
|
158
|
+
// exactly the window this exists to close: `cms.sign` accepts any non-Buffer object as options, so
|
|
159
|
+
// an instance whose `signedAttributes` flipped to false after the call still reached the signing
|
|
160
|
+
// turn. What a copy genuinely breaks is an object whose meaning lives somewhere other than its own
|
|
161
|
+
// properties -- a key handle, a live collection, a pending result. Those are named here rather than
|
|
162
|
+
// inferred, because inference gets it wrong in both directions: this engine's own CryptoKey carries
|
|
163
|
+
// its Node key handle as an own property, so "has no own keys" would have copied it into a shell
|
|
164
|
+
// that cannot sign, while a URL has none and would have copied into one that cannot be read.
|
|
165
|
+
// isCryptoKeyLike is the toolkit's own answer to "is this a key handle" -- the same one key.js and
|
|
166
|
+
// jose.js ask -- so a foreign implementation's CryptoKey is recognized here too.
|
|
167
|
+
// The WebCrypto surface a CryptoKey presents, and nothing else. A real one -- this engine's, the
|
|
168
|
+
// platform's, or another implementation's -- carries exactly these (its key material lives in an
|
|
169
|
+
// internal slot or a non-enumerable own property). A caller's options bag that happens to satisfy
|
|
170
|
+
// the structural predicate carries its own fields alongside, and those are what give it away.
|
|
171
|
+
var _CRYPTO_KEY_SURFACE = { type: 1, extractable: 1, algorithm: 1, usages: 1 };
|
|
172
|
+
// The same, for the other kinds whose state cannot be copied: what belongs to the kind, so that
|
|
173
|
+
// anything else on the object is recognizably the caller's own and gets the refusal below.
|
|
174
|
+
var _ERROR_SURFACE = { message: 1, stack: 1, name: 1, cause: 1 };
|
|
175
|
+
var _REGEXP_SURFACE = { lastIndex: 1, source: 1, flags: 1, global: 1, ignoreCase: 1, multiline: 1,
|
|
176
|
+
sticky: 1, unicode: 1, unicodeSets: 1, hasIndices: 1, dotAll: 1 };
|
|
177
|
+
var _THENABLE_SURFACE = { then: 1, catch: 1, finally: 1 };
|
|
178
|
+
|
|
179
|
+
// Is this an object whose state this module cannot read, and therefore cannot copy?
|
|
180
|
+
//
|
|
181
|
+
// Everything else is copied. Enumerating "handle shapes" to pass through was the wrong shape of
|
|
182
|
+
// answer and lost repeatedly: every kind named as a handle turned out to be usable as an options
|
|
183
|
+
// bag with fields glued on, and each new shape reopened the window for exactly the fields that
|
|
184
|
+
// were glued on. There are only two honest outcomes for a caller's argument -- copy it, or refuse
|
|
185
|
+
// it -- and this names the small set where copying is impossible, so the refusal below can be the
|
|
186
|
+
// rule for all of them at once rather than a shape to be found later.
|
|
187
|
+
// The surface an opaque kind is defined by, or null when the value is not one of them. A plain
|
|
188
|
+
// object literal is never opaque whatever it looks like: it is data this module can read and copy,
|
|
189
|
+
// and isCryptoKeyLike is structural by design so a literal can wear the key shape.
|
|
190
|
+
function _opaqueSurface(v, ErrorClass, code, label) {
|
|
191
|
+
var proto = Object.getPrototypeOf(v);
|
|
192
|
+
if (proto === Object.prototype || proto === null) return null;
|
|
193
|
+
if (v instanceof WeakMap || v instanceof WeakSet) return {}; // deliberately not enumerable
|
|
194
|
+
if (v instanceof Error) return _ERROR_SURFACE;
|
|
195
|
+
if (v instanceof RegExp) return _REGEXP_SURFACE;
|
|
196
|
+
if (require("./webcrypto").isCryptoKeyLike(v)) return _CRYPTO_KEY_SURFACE; // allow:inline-require -- circular load: webcrypto requires this module
|
|
197
|
+
// Asking whether it is a thenable READS a property, and a caller's accessor can throw -- the
|
|
198
|
+
// same fault the copy itself types, so it is typed here too rather than escaping raw from a
|
|
199
|
+
// question this module asked on its own behalf.
|
|
200
|
+
var then;
|
|
201
|
+
try { then = v.then; }
|
|
202
|
+
catch (e) { throw _raise(ErrorClass, code, label + ": reading \"then\" threw", e); }
|
|
203
|
+
if (typeof then === "function") return _THENABLE_SURFACE; // a pending result, not a value
|
|
204
|
+
return null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Is this opaque object safe to pass through -- carrying nothing beyond the surface its kind is
|
|
208
|
+
// defined by? Anything more is the case with no safe answer: the object cannot be copied, and
|
|
209
|
+
// passing it through would leave that data the caller's to change after the checks have read it.
|
|
210
|
+
// Every reachable name is examined, not only the own ones -- a verb reads a field by name and does
|
|
211
|
+
// not care where on the chain it sits.
|
|
212
|
+
function _opaqueIsSafeToPass(v, surface) {
|
|
213
|
+
var keys = _reachableKeys(v);
|
|
214
|
+
for (var i = 0; i < keys.length; i++) {
|
|
215
|
+
if (surface[keys[i]]) continue;
|
|
216
|
+
// Only what the CALLER put there. An implementation's own internals are non-enumerable by
|
|
217
|
+
// construction -- this engine's key handle, a platform object's slots -- and so are the methods
|
|
218
|
+
// on any prototype, built-in or class-declared. What a caller adds, they add by assignment,
|
|
219
|
+
// which is enumerable; that is the case the refusal is for, whatever the value's type. A
|
|
220
|
+
// callable value is no exemption: an option is read for what it is, and `signedAttributes`
|
|
221
|
+
// holding a function is neither false nor a method of anything.
|
|
222
|
+
if (!_wasEnumerable(v, keys[i])) continue;
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
return true;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Every property name a `v.field` lookup could resolve: own and inherited, enumerable or not.
|
|
229
|
+
//
|
|
230
|
+
// The enumerable-only version of this was wrong three times running, each time in the same
|
|
231
|
+
// direction -- own keys missed an inherited field, enumerable keys missed a non-enumerable one --
|
|
232
|
+
// and each miss left the caller's object reachable behind a copy that looked complete. A verb reads
|
|
233
|
+
// an option BY NAME, so the set that has to be fixed is the set a name lookup can reach, and
|
|
234
|
+
// nothing narrower. Object.prototype's own members are excluded: they are the language's, not the
|
|
235
|
+
// caller's, and copying them onto every spec would shadow the prototype for no gain.
|
|
236
|
+
function _reachableKeys(v) {
|
|
237
|
+
var names = [];
|
|
238
|
+
var seen = Object.create(null);
|
|
239
|
+
for (var o = v; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) {
|
|
240
|
+
var own = Object.getOwnPropertyNames(o);
|
|
241
|
+
for (var i = 0; i < own.length; i++) {
|
|
242
|
+
if (own[i] === "constructor" || seen[own[i]]) continue;
|
|
243
|
+
seen[own[i]] = true;
|
|
244
|
+
names.push(own[i]);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return names;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// A private copy of a byte value that is the SAME KIND as what the caller passed. The kind is
|
|
251
|
+
// load-bearing: each verb's own field validators decide which byte forms that field accepts, and
|
|
252
|
+
// most accept only Buffer / Uint8Array. Handing them a Buffer made from a DataView or a
|
|
253
|
+
// Uint16Array would have those inputs quietly accepted -- reinterpreted through a platform's own
|
|
254
|
+
// element layout -- where they were rejected before. Copying is not the place to decide what a
|
|
255
|
+
// field takes, so the copy preserves the type and the validator still sees what it was given.
|
|
256
|
+
function _copyBytesSameKind(v, ErrorClass, code, label, collect) {
|
|
257
|
+
var src = source(v, ErrorClass, code, label); // re-views; a detached backing store is refused
|
|
258
|
+
var owned = new ArrayBuffer(src.length);
|
|
259
|
+
new Uint8Array(owned).set(src);
|
|
260
|
+
// What `release` clears: a Buffer over the whole private store, whatever kind is handed back.
|
|
261
|
+
if (collect) collect.push(Buffer.from(owned, 0, src.length));
|
|
262
|
+
if (Buffer.isBuffer(v)) return Buffer.from(owned, 0, src.length);
|
|
263
|
+
if (v instanceof ArrayBuffer) return owned;
|
|
264
|
+
if (v instanceof DataView) return new DataView(owned);
|
|
265
|
+
return new v.constructor(owned, 0, src.length / v.BYTES_PER_ELEMENT);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function _deep(v, ErrorClass, code, label, cap, depth, collect) {
|
|
269
|
+
if (depth > cap) throw _raise(ErrorClass, code, label + " is nested too deeply to copy");
|
|
270
|
+
if (v == null || typeof v !== "object") return v;
|
|
271
|
+
if (Buffer.isBuffer(v) || ArrayBuffer.isView(v) || v instanceof ArrayBuffer) {
|
|
272
|
+
var bytesCopy = _copyBytesSameKind(v, ErrorClass, code, label, collect);
|
|
273
|
+
// A byte value can carry named properties too, and a verb reads an option by name whatever the
|
|
274
|
+
// argument's type: `opts = new Uint8Array(0); opts.pem = true` is an options object as far as
|
|
275
|
+
// `opts.pem` is concerned. An ArrayBuffer is extensible and takes them the same way.
|
|
276
|
+
_copyNamed(v, bytesCopy, ErrorClass, code, label, cap, depth, collect);
|
|
277
|
+
return bytesCopy;
|
|
278
|
+
}
|
|
279
|
+
// A parsed structure carrying guard-parsed's provenance record is a HANDLE: the record is keyed
|
|
280
|
+
// on the object's identity, so a copy of it carries none and every door that decides integrity
|
|
281
|
+
// refuses the copy. It is also safe to leave alone -- that door re-derives from the bytes the
|
|
282
|
+
// record names, so nothing done to the object since matters. That holds for it AS A PARSE
|
|
283
|
+
// RESULT; a caller who adds an option to one and passes it where options are read by name has
|
|
284
|
+
// added a field no door re-derives, so `AsProduced` is what is asked rather than `isRecorded`.
|
|
285
|
+
// Required inline because guard-parsed requires this module; at call time it is fully loaded.
|
|
286
|
+
if (require("./guard-parsed").isRecordedAsProduced(v)) return v; // allow:inline-require -- circular load: guard-parsed requires this module
|
|
287
|
+
if (v instanceof Date) return new Date(v.getTime());
|
|
288
|
+
if (Array.isArray(v)) {
|
|
289
|
+
var arr = [];
|
|
290
|
+
for (var i = 0; i < v.length; i++) arr.push(_deep(v[i], ErrorClass, code, label, cap, depth + 1, collect));
|
|
291
|
+
// An array can carry NAMED properties too, and a verb reads an option by name whatever the
|
|
292
|
+
// argument's type: `opts = []; opts.pem = true` is an options object as far as `opts.pem` is
|
|
293
|
+
// concerned. Copying only the indexed elements dropped those fields, which silently changed
|
|
294
|
+
// what the verb was asked to do rather than failing.
|
|
295
|
+
_copyNamed(v, arr, ErrorClass, code, label, cap, depth, collect);
|
|
296
|
+
return arr;
|
|
297
|
+
}
|
|
298
|
+
// The only kinds this module cannot read the state of. One carrying nothing of its own is passed
|
|
299
|
+
// through -- there is no data on it to fix, and a copy would break it. One carrying its own
|
|
300
|
+
// fields has no safe handling at all: it cannot be copied, and passing it through would leave
|
|
301
|
+
// those fields the caller's to rewrite after the checks read them. That is refused rather than
|
|
302
|
+
// half-handled, which is what keeps this from being another shape to be found later.
|
|
303
|
+
var surface = _opaqueSurface(v, ErrorClass, code, label);
|
|
304
|
+
if (surface) {
|
|
305
|
+
if (_opaqueIsSafeToPass(v, surface)) return v;
|
|
306
|
+
throw _raise(ErrorClass, code, label + ": a " + (v.constructor && v.constructor.name || "value") +
|
|
307
|
+
" carrying its own fields cannot be used here -- its state cannot be copied, so those fields " +
|
|
308
|
+
"would stay changeable after they were checked; pass the fields as a plain object");
|
|
309
|
+
}
|
|
310
|
+
if (v instanceof Map) return _copyEntries(v, new Map(), ErrorClass, code, label, cap, depth, collect);
|
|
311
|
+
if (v instanceof Set) return _copyEntries(v, new Set(), ErrorClass, code, label, cap, depth, collect);
|
|
312
|
+
// The prototype is kept so an instance's methods still resolve. A null-prototype dictionary --
|
|
313
|
+
// what `JSON.parse` or an explicit `Object.create(null)` produces -- must not come back
|
|
314
|
+
// inheriting from Object.prototype either, which is why the prototype is carried across rather
|
|
315
|
+
// than assumed.
|
|
316
|
+
var out = Object.create(Object.getPrototypeOf(v));
|
|
317
|
+
_copyNamed(v, out, ErrorClass, code, label, cap, depth, collect);
|
|
318
|
+
return out;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Which names to carry across, given what the copy is.
|
|
322
|
+
//
|
|
323
|
+
// For a plain object or a class instance -- the shapes an options bag actually takes -- it is every
|
|
324
|
+
// name a lookup could resolve, inherited included, because that is what the verb reads.
|
|
325
|
+
//
|
|
326
|
+
// For an array, a byte view, a Map or a Set, it is the caller's OWN added names only. Their
|
|
327
|
+
// prototypes are full of accessors that describe the kind rather than the caller (`length`,
|
|
328
|
+
// `buffer`, `size`, and Node's `parent`, which hands back the 64 KiB arena a small Buffer was
|
|
329
|
+
// allocated from) -- every one of those is already correct on the copy, and reading them would
|
|
330
|
+
// copy things the caller never passed. Enumerating the accessors to skip is the wrong way round;
|
|
331
|
+
// what the caller added is exactly what is own and not an index.
|
|
332
|
+
function _namesToCopy(src, dst) {
|
|
333
|
+
var indexed = Array.isArray(dst) || ArrayBuffer.isView(dst);
|
|
334
|
+
var kind = indexed || dst instanceof Map || dst instanceof Set || dst instanceof ArrayBuffer;
|
|
335
|
+
if (!kind) return _reachableKeys(src);
|
|
336
|
+
var own = Object.getOwnPropertyNames(src);
|
|
337
|
+
var out = [];
|
|
338
|
+
for (var i = 0; i < own.length; i++) {
|
|
339
|
+
if (own[i] === "length" || (indexed && String(Number(own[i])) === own[i])) continue;
|
|
340
|
+
out.push(own[i]);
|
|
341
|
+
}
|
|
342
|
+
return out;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// A Map or a Set is data the caller can still change, entry by entry, so it is copied like any
|
|
346
|
+
// other -- entries first, then the named properties one can carry alongside them.
|
|
347
|
+
function _copyEntries(src, dst, ErrorClass, code, label, cap, depth, collect) {
|
|
348
|
+
src.forEach(function (value, key) {
|
|
349
|
+
var copiedValue = _deep(value, ErrorClass, code, label, cap, depth + 1, collect);
|
|
350
|
+
if (dst instanceof Set) dst.add(copiedValue);
|
|
351
|
+
else dst.set(_deep(key, ErrorClass, code, label, cap, depth + 1, collect), copiedValue);
|
|
352
|
+
});
|
|
353
|
+
_copyNamed(src, dst, ErrorClass, code, label, cap, depth, collect);
|
|
354
|
+
return dst;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
// Copy every value a name lookup on `src` could reach onto `dst`, as an OWN data property.
|
|
358
|
+
//
|
|
359
|
+
// Own matters: a key literally named `__proto__` would otherwise re-point `dst`, so the field the
|
|
360
|
+
// caller passed disappears from Object.keys and an unknown-option check walks a different object
|
|
361
|
+
// than the verb goes on to read. Shadowing matters for the same reason it is done at all -- an own
|
|
362
|
+
// copy is what makes an inherited value stop tracking the caller's prototype. A method is left
|
|
363
|
+
// where it is: copying a function would only move it, and it is the prototype's behaviour rather
|
|
364
|
+
// than the caller's data.
|
|
365
|
+
function _copyNamed(src, dst, ErrorClass, code, label, cap, depth, collect) {
|
|
366
|
+
var keys = _namesToCopy(src, dst);
|
|
367
|
+
for (var k = 0; k < keys.length; k++) {
|
|
368
|
+
var value;
|
|
369
|
+
// Reading a caller's property can run a caller's accessor, and one that throws is a bad input
|
|
370
|
+
// like any other -- the fault gets this boundary's typed code with the raw error as its cause,
|
|
371
|
+
// rather than escaping as itself from inside a verb the caller called for something else.
|
|
372
|
+
try { value = src[keys[k]]; }
|
|
373
|
+
catch (e) { throw _raise(ErrorClass, code, label + ": reading " + JSON.stringify(keys[k]) + " threw", e); }
|
|
374
|
+
// A function is carried across by reference rather than copied -- there is nothing in it to
|
|
375
|
+
// copy -- but it is still carried. Dropping it changed what the caller passed: an unknown spec
|
|
376
|
+
// field whose value happened to be a function stopped reaching the verb's own key check, so a
|
|
377
|
+
// typo that used to be refused was silently accepted.
|
|
378
|
+
Object.defineProperty(dst, keys[k], {
|
|
379
|
+
value: typeof value === "function" ? value
|
|
380
|
+
: _deep(value, ErrorClass, code, label, cap, depth + 1, collect),
|
|
381
|
+
writable: true, enumerable: _wasEnumerable(src, keys[k]), configurable: true,
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Whether a name lookup on `v` would have found this property as an enumerable one, anywhere on
|
|
387
|
+
// the chain -- so the copy's own keys enumerate the way the original's did.
|
|
388
|
+
function _wasEnumerable(v, name) {
|
|
389
|
+
for (var o = v; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) {
|
|
390
|
+
var d = Object.getOwnPropertyDescriptor(o, name);
|
|
391
|
+
if (d) return !!d.enumerable;
|
|
392
|
+
}
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// fixArguments(ErrorClass, code, args) -> { values, release }
|
|
397
|
+
//
|
|
398
|
+
// The whole rule for a verb that spans a promise turn, in one call. `args` is a list of
|
|
399
|
+
// `[value, label]` pairs -- every argument the verb was handed. Each is deep-copied, so no field
|
|
400
|
+
// of any of them, at any depth, is still the caller's to change once the verb has begun; `release`
|
|
401
|
+
// clears every copy that was made and belongs in a `finally`, so a copied password or private key
|
|
402
|
+
// does not outlive the call.
|
|
403
|
+
//
|
|
404
|
+
// Both halves are needed and neither is optional. Copying only the argument that looks like data
|
|
405
|
+
// leaves a secret nested inside the one that looks like a handle -- `opts.mac.secret` sitting a
|
|
406
|
+
// level below a shallow copy -- readable and rewritable across the turn. Copying without releasing
|
|
407
|
+
// answers that by duplicating the secret instead. Deciding per field which is which is the judgment
|
|
408
|
+
// that keeps going wrong, so there is no per-field decision here: everything is copied, everything
|
|
409
|
+
// copied is cleared, and the only things left alone are the ones a copy would BREAK -- a CryptoKey
|
|
410
|
+
// or any class instance (its identity is what makes it work) and a recorded parse result (its
|
|
411
|
+
// provenance is keyed to the object).
|
|
412
|
+
// @enforced-by behavioral -- there is no rename-proof code shape for "copied every argument". The
|
|
413
|
+
// guard is the per-verb RED vector that mutates each argument after the verb returns its promise
|
|
414
|
+
// and asserts the artifact carries the entry value.
|
|
415
|
+
function fixArguments(ErrorClass, code, args) {
|
|
416
|
+
var copies = [];
|
|
417
|
+
var values = [];
|
|
418
|
+
// Through guard.secret, which owns what clearing a secret means -- required inline because
|
|
419
|
+
// guard-secret requires this module; at call time it is fully loaded.
|
|
420
|
+
function release() {
|
|
421
|
+
require("./guard-secret").zeroizeAll(copies, ErrorClass, code, // allow:inline-require -- circular load: guard-secret requires this module
|
|
422
|
+
"a copy of a caller-supplied argument");
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
for (var i = 0; i < args.length; i++) {
|
|
426
|
+
values.push(snapshotDeep(args[i][0], ErrorClass, code, args[i][1], { collect: copies }));
|
|
427
|
+
}
|
|
428
|
+
} catch (e) {
|
|
429
|
+
// The copying itself can fail partway -- a detached leaf, a structure past the depth cap, a
|
|
430
|
+
// getter that throws -- and the arguments already copied by then are just as much ours as if
|
|
431
|
+
// the call had gone on to succeed. Returning nothing would leave the caller with no handle to
|
|
432
|
+
// release them, so a signer key or a password copied before the fault would stay readable for
|
|
433
|
+
// as long as the heap held it. Clear what was made, then let the fault out unchanged.
|
|
434
|
+
release();
|
|
435
|
+
throw e;
|
|
436
|
+
}
|
|
437
|
+
return { values: values, release: release };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
// fixedCall(ErrorClass, code, args, body) -> Promise
|
|
441
|
+
//
|
|
442
|
+
// The form every Promise-returning producing verb uses, because the halves have to be arranged in
|
|
443
|
+
// exactly one way and this is it. The copy runs INSIDE guard-async's boundary, so a fault in the
|
|
444
|
+
// copy itself -- a detached view, a structure nested past the cap, a getter that throws -- leaves
|
|
445
|
+
// as a REJECTION like every other fault of a verb documented `-> Promise<...>`, rather than as a
|
|
446
|
+
// synchronous throw past the caller's `.catch`. It still runs at the call, before any turn passes,
|
|
447
|
+
// which is what fixes the arguments. And the release runs whether the body resolved, rejected, or
|
|
448
|
+
// never ran, so a copied secret is cleared on every path out.
|
|
449
|
+
// @enforced-by behavioral -- an arrangement of two calls has no rename-proof shape to detect. The
|
|
450
|
+
// guards are promise-contract.test.js (every documented Promise verb refuses by rejecting, now
|
|
451
|
+
// including a fault raised by the copy itself) and the per-verb entry-value vectors.
|
|
452
|
+
function fixedCall(ErrorClass, code, args, body) {
|
|
453
|
+
var handle = null;
|
|
454
|
+
return async.deferred(function () {
|
|
455
|
+
handle = fixArguments(ErrorClass, code, args);
|
|
456
|
+
return body.apply(null, handle.values);
|
|
457
|
+
}).finally(function () { if (handle) handle.release(); });
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
module.exports = {
|
|
461
|
+
view: view, source: source, snapshot: snapshot, snapshotSource: snapshotSource,
|
|
462
|
+
snapshotDeep: snapshotDeep, fixArguments: fixArguments, fixedCall: fixedCall,
|
|
463
|
+
};
|
package/lib/guard-parsed.js
CHANGED
|
@@ -336,7 +336,8 @@ function recordingParser(kind, parse, ErrorClass, code, label) {
|
|
|
336
336
|
// re-derivation would faithfully reproduce whatever was written. A record the object can reach
|
|
337
337
|
// is not a record of anything. This copy is never handed out and nothing aliases it.
|
|
338
338
|
if (out && typeof out === "object") {
|
|
339
|
-
PROVENANCE.set(out, { kind: kind, source: isText ? snap : Buffer.from(snap)
|
|
339
|
+
PROVENANCE.set(out, { kind: kind, source: isText ? snap : Buffer.from(snap),
|
|
340
|
+
shape: _shapeOf(out) });
|
|
340
341
|
}
|
|
341
342
|
return out;
|
|
342
343
|
};
|
|
@@ -368,6 +369,7 @@ function recordingWalker(kind, walkNode, decodeBytes) {
|
|
|
368
369
|
PROVENANCE.set(out, {
|
|
369
370
|
kind: kind,
|
|
370
371
|
source: Buffer.from(node.bytes),
|
|
372
|
+
shape: _shapeOf(out),
|
|
371
373
|
derive: function (src) { return walkNode(decodeBytes(src)); },
|
|
372
374
|
});
|
|
373
375
|
}
|
|
@@ -383,6 +385,72 @@ function _recordOf(obj, kind) {
|
|
|
383
385
|
return (rec && rec.kind === kind) ? rec : undefined;
|
|
384
386
|
}
|
|
385
387
|
|
|
388
|
+
// isRecorded(obj) -> whether this exact object carries a provenance record, of any kind.
|
|
389
|
+
//
|
|
390
|
+
// The record is keyed on IDENTITY, so a copy of a parsed structure -- however faithful -- is
|
|
391
|
+
// not the parsed structure: it carries no record and every door that decides integrity refuses
|
|
392
|
+
// it. That makes a recorded object a HANDLE rather than data, and anything that walks a caller's
|
|
393
|
+
// spec taking copies has to leave it alone. guard-bytes.snapshotDeep asks this before descending.
|
|
394
|
+
// @enforced-by behavioral -- a predicate has no rename-proof shape to detect, and re-inlining it is
|
|
395
|
+
// not possible outside this module: the registry it reads is a module-private WeakMap. The guard
|
|
396
|
+
// is the RED vector that passes a parsed certificate through a producing verb's spec and asserts
|
|
397
|
+
// the integrity door still accepts it, which fails the moment a copy is taken instead.
|
|
398
|
+
function isRecorded(obj) {
|
|
399
|
+
return !!obj && typeof obj === "object" && PROVENANCE.has(obj);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// isRecordedAsProduced(obj) -> whether this is a recorded structure that still has the field set
|
|
403
|
+
// the parser gave it.
|
|
404
|
+
//
|
|
405
|
+
// A recorded structure is safe to hand on by identity, because the doors that decide integrity
|
|
406
|
+
// re-derive it from the bytes the record names and ignore whatever the object says. That reasoning
|
|
407
|
+
// covers it AS A PARSED VALUE. It does not cover a caller who takes a parse result, adds an option
|
|
408
|
+
// to it, and passes it where a verb reads options by name -- those fields no door re-derives, so
|
|
409
|
+
// they stay the caller's to change. The record therefore also names the shape the parser produced,
|
|
410
|
+
// and a structure that has grown fields since is no longer only a parse result.
|
|
411
|
+
// Only an ADDED field matters. Deleting one changes nothing -- the door re-derives the structure
|
|
412
|
+
// from the bytes the record names, so a missing property is not a missing value -- and a caller
|
|
413
|
+
// pruning a parse result is a case the shipped vectors cover deliberately. What re-derivation does
|
|
414
|
+
// not reach is a name the parser never produced, which is exactly what an option added to a parse
|
|
415
|
+
// result is.
|
|
416
|
+
// @enforced-by behavioral -- a predicate has no rename-proof shape to detect, and the registry it
|
|
417
|
+
// reads is a module-private WeakMap so nothing outside can re-inline it. The guard is the RED
|
|
418
|
+
// vector that adds an option to a parse result, passes it where options are read by name, and
|
|
419
|
+
// asserts the value the verb used is the one that was there at entry.
|
|
420
|
+
function isRecordedAsProduced(obj) {
|
|
421
|
+
if (!isRecorded(obj)) return false;
|
|
422
|
+
var shape = PROVENANCE.get(obj).shape;
|
|
423
|
+
var keys = _allNames(obj);
|
|
424
|
+
for (var i = 0; i < keys.length; i++) {
|
|
425
|
+
if (!shape[keys[i]]) return false;
|
|
426
|
+
}
|
|
427
|
+
return true;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function _shapeOf(obj) {
|
|
431
|
+
var shape = Object.create(null);
|
|
432
|
+
var keys = _allNames(obj);
|
|
433
|
+
for (var i = 0; i < keys.length; i++) shape[keys[i]] = true;
|
|
434
|
+
return shape;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Every name a `obj.field` lookup could resolve -- own and inherited, enumerable or not. A verb
|
|
438
|
+
// reads an option by name and does not care how it was defined, so a comparison that only saw
|
|
439
|
+
// `Object.keys` would miss a field added with defineProperty and call the object unchanged.
|
|
440
|
+
function _allNames(obj) {
|
|
441
|
+
var names = [];
|
|
442
|
+
var seen = Object.create(null);
|
|
443
|
+
for (var o = obj; o && o !== Object.prototype; o = Object.getPrototypeOf(o)) {
|
|
444
|
+
var own = Object.getOwnPropertyNames(o);
|
|
445
|
+
for (var i = 0; i < own.length; i++) {
|
|
446
|
+
if (seen[own[i]]) continue;
|
|
447
|
+
seen[own[i]] = true;
|
|
448
|
+
names.push(own[i]);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
return names;
|
|
452
|
+
}
|
|
453
|
+
|
|
386
454
|
// fromTrustedSource(input, kind, claimFields, parse, E, code, why) -> the parsed
|
|
387
455
|
// structure | throws.
|
|
388
456
|
//
|
|
@@ -478,5 +546,6 @@ module.exports = {
|
|
|
478
546
|
accept: accept, acceptDerived: acceptDerived,
|
|
479
547
|
fromTrustedSource: fromTrustedSource, recordingParser: recordingParser,
|
|
480
548
|
recordingWalker: recordingWalker,
|
|
481
|
-
isCert: certShape, isCrl: crlShape,
|
|
549
|
+
isCert: certShape, isCrl: crlShape, isRecorded: isRecorded,
|
|
550
|
+
isRecordedAsProduced: isRecordedAsProduced,
|
|
482
551
|
};
|
package/lib/ocsp.js
CHANGED
|
@@ -83,8 +83,7 @@ function _responseFromBytes(response) {
|
|
|
83
83
|
}
|
|
84
84
|
|
|
85
85
|
function _toDer(input, what) {
|
|
86
|
-
if (Buffer.isBuffer(input)) return input;
|
|
87
|
-
if (input instanceof Uint8Array) return Buffer.from(input);
|
|
86
|
+
if (Buffer.isBuffer(input) || input instanceof Uint8Array) return guard.bytes.snapshot(input, OcspError, "ocsp/bad-input", what || "input");
|
|
88
87
|
if (typeof input === "string") { try { return ocspSchema.pemDecode(input); } catch (e) { throw _err("ocsp/bad-input", (what || "input") + " PEM could not be decoded", e); } }
|
|
89
88
|
throw _err("ocsp/bad-input", (what || "input") + " must be a DER Buffer, Uint8Array, or PEM string");
|
|
90
89
|
}
|
|
@@ -152,6 +151,15 @@ function _buildCertID(cert, issuer, hashName) {
|
|
|
152
151
|
* var der = await pki.ocsp.buildRequest({ cert: leafDer, issuer: caDer }, { nonce: true });
|
|
153
152
|
*/
|
|
154
153
|
function buildRequest(query, opts) {
|
|
154
|
+
// Both arguments copied at entry and released when the call settles -- see the note on the same
|
|
155
|
+
// call in x509-sign. The request is assembled across several promise turns (each CertID is a
|
|
156
|
+
// hash), and opts carries byte fields -- the nonce and requestorName -- that reach the encoding.
|
|
157
|
+
return guard.bytes.fixedCall(OcspError, "ocsp/bad-input", [
|
|
158
|
+
[query, "the OCSP request query"], [opts, "pki.ocsp.buildRequest options"],
|
|
159
|
+
], _buildRequest);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function _buildRequest(query, opts) {
|
|
155
163
|
opts = opts || {};
|
|
156
164
|
var lightweight = opts.profile === "lightweight";
|
|
157
165
|
var hashName = opts.hashAlgorithm || "sha1";
|
|
@@ -201,7 +209,7 @@ function buildRequest(query, opts) {
|
|
|
201
209
|
}
|
|
202
210
|
function _emitReq(der, opts) { return opts.pem ? ocspSchema.pemEncode(der, "OCSP REQUEST") : der; }
|
|
203
211
|
function _nameDer(name) {
|
|
204
|
-
if (Buffer.isBuffer(name)) return name;
|
|
212
|
+
if (Buffer.isBuffer(name)) return guard.bytes.snapshot(name, OcspError, "ocsp/bad-input", "requestorName");
|
|
205
213
|
if (name && name.bytes) return name.bytes; // a parsed Name
|
|
206
214
|
throw _err("ocsp/bad-input", "requestorName must be a DER Name Buffer or a parsed Name");
|
|
207
215
|
}
|
|
@@ -210,8 +218,7 @@ function _nameDer(name) {
|
|
|
210
218
|
// a parsed certificate does not retain its full DER encoding, and re-encoding it would risk
|
|
211
219
|
// byte drift from the signed original, so a parsed cert is rejected rather than reconstructed.
|
|
212
220
|
function _normCertDer(cert, what) {
|
|
213
|
-
if (Buffer.isBuffer(cert)) return cert;
|
|
214
|
-
if (cert instanceof Uint8Array) return Buffer.from(cert);
|
|
221
|
+
if (Buffer.isBuffer(cert) || cert instanceof Uint8Array) return guard.bytes.snapshot(cert, OcspError, "ocsp/bad-input", what || "a certificate");
|
|
215
222
|
if (typeof cert === "string") { try { return x509.pemDecode(cert); } catch (e) { throw _err("ocsp/bad-input", (what || "a certificate") + " PEM could not be decoded", e); } }
|
|
216
223
|
if (cert && cert.tbsBytes && cert.subjectPublicKeyInfo) {
|
|
217
224
|
throw _err("ocsp/bad-input", (what || "a certificate") + " to embed must be supplied as DER bytes or a PEM string, not a parsed certificate (the parser does not retain the full DER encoding needed to embed it verbatim)");
|
|
@@ -259,7 +266,11 @@ function _normCertDer(cert, what) {
|
|
|
259
266
|
// Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async); the checks stay
|
|
260
267
|
// synchronous because they read the responder's mutable cert and key.
|
|
261
268
|
function sign(responseData, responder, opts) {
|
|
262
|
-
|
|
269
|
+
// Every caller-owned argument copied at entry and released when the call settles -- see the note
|
|
270
|
+
// on the same call in x509-sign.
|
|
271
|
+
return guard.bytes.fixedCall(OcspError, "ocsp/bad-input", [
|
|
272
|
+
[responseData, "the OCSP responseData"], [responder, "the responder"], [opts, "pki.ocsp.sign options"],
|
|
273
|
+
], _sign);
|
|
263
274
|
}
|
|
264
275
|
|
|
265
276
|
function _sign(responseData, responder, opts) {
|
|
@@ -509,7 +520,8 @@ function verify(response, opts) {
|
|
|
509
520
|
// A client that sent a nonce binds it (RFC 9654 / RFC 5019 sec. 4): a missing or mismatched
|
|
510
521
|
// response nonce fails the verdict closed, even if the status/signature were otherwise good.
|
|
511
522
|
var respNonce = _responseNonce(parsed);
|
|
512
|
-
var reqNonce = Buffer.isBuffer(opts.requestNonce)
|
|
523
|
+
var reqNonce = (Buffer.isBuffer(opts.requestNonce) || opts.requestNonce instanceof Uint8Array)
|
|
524
|
+
? guard.bytes.snapshot(opts.requestNonce, OcspError, "ocsp/bad-input", "opts.requestNonce") : null;
|
|
513
525
|
var matched = respNonce != null && reqNonce != null && guard.crypto.constantTimeEqual(respNonce, reqNonce);
|
|
514
526
|
var out = Object.assign({}, verdict, { nonceMatched: matched });
|
|
515
527
|
// The downgrade applies to `good` ONLY. `unknown` is the closed direction for a
|
package/lib/pkcs12-build.js
CHANGED
|
@@ -121,12 +121,14 @@ var _INTEGRITY_OPTS = { mode: 1, signer: 1, signers: 1, certificates: 1, sid: 1,
|
|
|
121
121
|
// which is a worse defect than leaving a copy readable. Every other input is re-encoded into a
|
|
122
122
|
// buffer this module allocated, which it must clear once a derivation has consumed it.
|
|
123
123
|
function _p12PasswordOwned(pw) {
|
|
124
|
-
if (Buffer.isBuffer(pw)) return { bytes: pw, owned: false };
|
|
124
|
+
if (Buffer.isBuffer(pw)) return { bytes: guard.bytes.view(pw, Pkcs12Error, "pkcs12/bad-input", "the password"), owned: false };
|
|
125
125
|
return { bytes: _p12Encode(pw), owned: true };
|
|
126
126
|
}
|
|
127
127
|
function _p12Encode(pw) {
|
|
128
|
-
|
|
129
|
-
|
|
128
|
+
// A view first, so a detached backing store is a reject rather than an empty password; then a COPY,
|
|
129
|
+
// because everything this function returns is reported OWNED and gets zeroized after derivation --
|
|
130
|
+
// handing back a view of a caller's Uint8Array would wipe the caller's own credential.
|
|
131
|
+
if (Buffer.isBuffer(pw) || pw instanceof Uint8Array) return guard.bytes.snapshot(pw, Pkcs12Error, "pkcs12/bad-input", "the password");
|
|
130
132
|
if (typeof pw !== "string") throw _err("pkcs12/bad-input", _MISSING_PASSWORD);
|
|
131
133
|
var out = Buffer.alloc(pw.length * 2 + 2); // + the 2-byte NULL terminator
|
|
132
134
|
for (var i = 0; i < pw.length; i++) {
|
|
@@ -149,8 +151,8 @@ function _p12Encode(pw) {
|
|
|
149
151
|
// is cleared once the derivation has consumed it. Without it the App. B.1 copy taken from the same
|
|
150
152
|
// argument in the same call was wiped while this one was not.
|
|
151
153
|
function _pbePasswordOwned(pw) {
|
|
152
|
-
if (Buffer.isBuffer(pw)) return { bytes: pw, owned: false };
|
|
153
|
-
if (pw instanceof Uint8Array) return { bytes:
|
|
154
|
+
if (Buffer.isBuffer(pw)) return { bytes: guard.bytes.view(pw, Pkcs12Error, "pkcs12/bad-input", "the password"), owned: false };
|
|
155
|
+
if (pw instanceof Uint8Array) return { bytes: guard.bytes.snapshot(pw, Pkcs12Error, "pkcs12/bad-input", "the password"), owned: true };
|
|
154
156
|
if (typeof pw !== "string") throw _err("pkcs12/bad-input", _MISSING_PASSWORD);
|
|
155
157
|
return { bytes: Buffer.from(pw, "utf8"), owned: true };
|
|
156
158
|
}
|
|
@@ -523,7 +525,20 @@ function _normalizeSpec(spec, opts) {
|
|
|
523
525
|
* { type: 'shroudedKey', key: signerKeyPkcs8, encrypt: { password: 'changeit' } } ] }] },
|
|
524
526
|
* { password: 'changeit', mac: { algorithm: 'hmac', hash: 'sha256' } });
|
|
525
527
|
*/
|
|
526
|
-
|
|
528
|
+
function build(spec, opts) {
|
|
529
|
+
// Both arguments copied at entry and released when the call settles -- see the note on the same
|
|
530
|
+
// call in x509-sign. A PKCS#12 file is assembled over many promise turns (a key derivation per
|
|
531
|
+
// bag, then the outer MAC) while the caller still owns the object holding the passwords and the
|
|
532
|
+
// bag contents. Rewriting a password buffer partway through produced a file whose MAC and whose
|
|
533
|
+
// bag encryption were keyed to two different values, so it opened with neither the password
|
|
534
|
+
// passed in nor the one written over it. The release is what keeps the copy of that password
|
|
535
|
+
// from outliving the call.
|
|
536
|
+
return guard.bytes.fixedCall(Pkcs12Error, "pkcs12/bad-input", [
|
|
537
|
+
[spec, "the PKCS#12 spec"], [opts, "pki.pkcs12.build options"],
|
|
538
|
+
], _build);
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
async function _build(spec, opts) {
|
|
527
542
|
opts = opts || {};
|
|
528
543
|
// The integrity mode is checked against the permitted set rather than compared to one literal.
|
|
529
544
|
// Compared, any other spelling reads as "not public-key" and silently selects password integrity,
|
package/lib/pki-build.js
CHANGED
|
@@ -223,15 +223,14 @@ function makeBuilder(ctx) {
|
|
|
223
223
|
return nodeCrypto.createHash("sha1").update(keyBytes).digest();
|
|
224
224
|
}
|
|
225
225
|
function skiKeyId(val, spkiDer) {
|
|
226
|
-
if (Buffer.isBuffer(val)) return val;
|
|
226
|
+
if (Buffer.isBuffer(val)) return guard.bytes.snapshot(val, ErrorClass, ctx.prefix + "/bad-input", "subjectKeyIdentifier");
|
|
227
227
|
if (val === true) return spkiKeyId(spkiDer);
|
|
228
228
|
throw E("bad-input", "subjectKeyIdentifier must be true (auto-derive) or a Buffer key id");
|
|
229
229
|
}
|
|
230
230
|
|
|
231
231
|
// ---- embedded-input validators ----
|
|
232
232
|
function reqDer(v, what) {
|
|
233
|
-
if (Buffer.isBuffer(v)) return v;
|
|
234
|
-
if (v instanceof Uint8Array) return Buffer.from(v);
|
|
233
|
+
if (Buffer.isBuffer(v) || v instanceof Uint8Array) return guard.bytes.snapshot(v, ErrorClass, ctx.prefix + "/bad-input", what);
|
|
235
234
|
throw E("bad-input", what + " must be a DER Buffer");
|
|
236
235
|
}
|
|
237
236
|
// Full validation of an embedded SubjectPublicKeyInfo via the SAME parser the decoder uses.
|