@blamejs/pki 0.8.6 → 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.
Files changed (48) hide show
  1. package/CHANGELOG.md +143 -76
  2. package/README.md +4 -2
  3. package/index.js +11 -0
  4. package/lib/acme.js +15 -78
  5. package/lib/asn1-der.js +58 -6
  6. package/lib/cbor-det.js +11 -1
  7. package/lib/cmc-build.js +1 -1
  8. package/lib/cmc-verify.js +3 -3
  9. package/lib/cmp-session.js +1 -1
  10. package/lib/cms-sign.js +3 -3
  11. package/lib/cms-verify.js +5 -5
  12. package/lib/constants.js +11 -0
  13. package/lib/est.js +1 -1
  14. package/lib/framework-error.js +8 -0
  15. package/lib/guard-async.js +53 -2
  16. package/lib/guard-encoding.js +105 -1
  17. package/lib/guard-identifier.js +74 -1
  18. package/lib/guard-intrinsic.js +2 -0
  19. package/lib/guard-json.js +1 -6
  20. package/lib/guard-limits.js +40 -1
  21. package/lib/guard-list.js +25 -0
  22. package/lib/guard-name.js +134 -22
  23. package/lib/guard-text.js +33 -1
  24. package/lib/http-transport.js +39 -7
  25. package/lib/identity-match.js +561 -0
  26. package/lib/ip-utils.js +52 -1
  27. package/lib/key.js +250 -5
  28. package/lib/ocsp.js +125 -22
  29. package/lib/oid.js +1 -1
  30. package/lib/path-validate.js +199 -61
  31. package/lib/pkcs11-uri.js +646 -0
  32. package/lib/pkcs12-build.js +6 -1
  33. package/lib/revocation-fetch.js +772 -0
  34. package/lib/schema-all.js +124 -0
  35. package/lib/schema-attrcert.js +4 -2
  36. package/lib/schema-crmf.js +4 -2
  37. package/lib/schema-pkcs1.js +335 -0
  38. package/lib/schema-pkcs8.js +15 -0
  39. package/lib/schema-pkix.js +322 -40
  40. package/lib/schema-sec1.js +244 -0
  41. package/lib/schema-tsp.js +4 -2
  42. package/lib/schema-x509.js +4 -2
  43. package/lib/trust.js +2 -2
  44. package/lib/tsp-sign.js +1 -1
  45. package/lib/webauthn.js +4 -4
  46. package/lib/webcrypto.js +17 -5
  47. package/package.json +1 -1
  48. package/sbom.cdx.json +6 -6
@@ -0,0 +1,646 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.pkcs11
6
+ * @nav Keys
7
+ * @title PKCS#11 URI
8
+ * @fullname PKCS #11 URIs: naming a token and an object on it (RFC 7512)
9
+ * @order 235
10
+ * @slug pkcs11
11
+ *
12
+ * @intro
13
+ * RFC 7512 defines a URI that names a token, a slot, a library and an object
14
+ * on a PKCS #11 device, which is how an operator points a tool at a key in a
15
+ * hardware security module, a smart card or a PIV slot without naming a file.
16
+ * `parseUri` reads one into a record and `formatUri` writes the record back.
17
+ *
18
+ * The path component carries what identifies the object: `token`,
19
+ * `manufacturer`, `serial`, `model`, the three `library-*` attributes, the
20
+ * three `slot-*` attributes, `object`, `type` and `id`. The query component
21
+ * carries what tells a consumer how to reach it: `pin-source`, `pin-value`,
22
+ * `module-name` and `module-path`. A vendor attribute in either component is
23
+ * kept under its own name rather than dropped. A vendor query attribute is
24
+ * carried as a list of its values, which sec. 2.4 permits to repeat and leaves
25
+ * to the consumer to interpret; a vendor path attribute carries one value.
26
+ *
27
+ * Parsing is fail-closed and follows the ABNF of sec. 2.3 and sec. 2.4 rather
28
+ * than a general URI reader: a character a component does not admit unencoded
29
+ * is refused, a truncated or non-hex escape is refused, an empty attribute
30
+ * between two delimiters is refused, a repeated attribute is refused wherever
31
+ * the RFC does not permit one, and the attributes the RFC writes as digits or
32
+ * as literal alternatives are read that way rather than through an escape. `id` is decoded to a `Buffer`, because it
33
+ * names bytes and not text. A URI carrying both `pin-source` and `pin-value`
34
+ * is refused, and so is a `module-path` that is not absolute, both of which
35
+ * sec. 2.4 asks a consumer to treat as invalid.
36
+ *
37
+ * An attribute name and a `type` value are read whichever case they arrive in,
38
+ * RFC 5234 sec. 2.3 making an ABNF quoted literal case-insensitive, so
39
+ * `PIN-SOURCE` is the `pin-source` attribute and is held to the rules
40
+ * `pin-source` is held to rather than passing as vendor data. Writing produces
41
+ * the canonical form, which is what lets two URIs naming one object be compared
42
+ * as strings, and every rule parsing refuses writing refuses too, so the two
43
+ * directions cannot drift. This is a syntax, not a driver: nothing here loads a
44
+ * PKCS #11 module or talks to a token.
45
+ *
46
+ * @card
47
+ * `parseUri` / `formatUri` over the RFC 7512 ABNF, fail-closed, with `id` as
48
+ * bytes and no module loading.
49
+ */
50
+
51
+ var frameworkError = require("./framework-error");
52
+ var guard = require("./guard-all");
53
+ var intrinsic = require("./guard-intrinsic");
54
+
55
+ var _create = intrinsic.create;
56
+ var _assign = intrinsic.assign;
57
+ var _hasOwn = intrinsic.hasOwn;
58
+ var _keys = intrinsic.keys;
59
+ var _forEach = intrinsic.forEach;
60
+ var _push = intrinsic.push;
61
+ var _join = intrinsic.join;
62
+ var _String = intrinsic.String;
63
+ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
64
+ var _strSlice = intrinsic.uncurry(String.prototype.slice);
65
+ var _fromCharCode = String.fromCharCode;
66
+
67
+ var Pkcs11Error = frameworkError.Pkcs11Error;
68
+
69
+ function _err(code, message, cause) { return new Pkcs11Error(code, message, cause); }
70
+
71
+ var SCHEME = "pkcs11:";
72
+
73
+ /** @internal The RFC 7512 sec. 2.3 / sec. 2.4 attribute tables, keyed by the name on the wire.
74
+ * `field` is the name the parsed record carries, `kind` how the value is read. A vendor
75
+ * attribute matches neither table and is kept under its own wire name. */
76
+ var PATH_ATTRS = _assign(_create(null), {
77
+ "token": { field: "token", kind: "text" },
78
+ "manufacturer": { field: "manufacturer", kind: "text" },
79
+ "serial": { field: "serial", kind: "text" },
80
+ "model": { field: "model", kind: "text" },
81
+ "library-manufacturer": { field: "libraryManufacturer", kind: "text" },
82
+ "library-description": { field: "libraryDescription", kind: "text" },
83
+ "library-version": { field: "libraryVersion", kind: "version" },
84
+ "object": { field: "object", kind: "text" },
85
+ "type": { field: "type", kind: "type" },
86
+ "id": { field: "id", kind: "bytes" },
87
+ "slot-manufacturer": { field: "slotManufacturer", kind: "text" },
88
+ "slot-description": { field: "slotDescription", kind: "text" },
89
+ "slot-id": { field: "slotId", kind: "number" },
90
+ });
91
+ var QUERY_ATTRS = _assign(_create(null), {
92
+ "pin-source": { field: "pinSource", kind: "text" },
93
+ "pin-value": { field: "pinValue", kind: "text" },
94
+ "module-name": { field: "moduleName", kind: "text" },
95
+ "module-path": { field: "modulePath", kind: "path" },
96
+ });
97
+ var OBJECT_TYPES = _assign(_create(null), {
98
+ "public": 1, "private": 1, "cert": 1, "secret-key": 1, "data": 1,
99
+ });
100
+
101
+ /** @internal The wire name each record field writes back to, derived from the tables above so the
102
+ * two directions cannot name different attributes. */
103
+ function _byField(table) {
104
+ var out = _create(null);
105
+ _forEach(_keys(table), function (wire) { out[table[wire].field] = _assign(_create(null), { wire: wire, kind: table[wire].kind }); });
106
+ return out;
107
+ }
108
+ var PATH_BY_FIELD = _byField(PATH_ATTRS);
109
+ var QUERY_BY_FIELD = _byField(QUERY_ATTRS);
110
+
111
+ /** @internal `unreserved` of RFC 3986 sec. 2.3: ALPHA / DIGIT / "-" / "." / "_" / "~". */
112
+ function _isUnreserved(c) {
113
+ return guard.encoding.isAlphanumericByte(c) || c === 0x2d || c === 0x2e || c === 0x5f || c === 0x7e;
114
+ }
115
+ /** @internal `pk11-res-avail` of sec. 2.3: the reserved characters both components admit unencoded. */
116
+ function _isResAvail(c) {
117
+ return c === 0x3a || c === 0x5b || c === 0x5d || c === 0x40 || c === 0x21 || c === 0x24 ||
118
+ c === 0x27 || c === 0x28 || c === 0x29 || c === 0x2a || c === 0x2b || c === 0x2c || c === 0x3d;
119
+ }
120
+ /** @internal `pk11-pchar` minus pct-encoded: the path admits "&" on top of the shared set. */
121
+ function _isPathChar(c) { return _isUnreserved(c) || _isResAvail(c) || c === 0x26; }
122
+ /** @internal `pk11-qchar` minus pct-encoded: the query admits "/", "?" and "|" instead. */
123
+ function _isQueryChar(c) { return _isUnreserved(c) || _isResAvail(c) || c === 0x2f || c === 0x3f || c === 0x7c; }
124
+
125
+ var _hexValue = guard.encoding.hexNibble;
126
+
127
+ /** @internal The ASCII fold, not the Unicode one. RFC 5234 sec. 2.3 makes a quoted literal
128
+ * case-insensitive over the US-ASCII the ABNF is written in, and `String.prototype.toLowerCase`
129
+ * folds further than that: U+212A KELVIN SIGN lowercases to "k", so folding before the grammar
130
+ * check would read a name carrying one as a standard attribute it is not. */
131
+ function _asciiLower(text) {
132
+ var out = "";
133
+ for (var i = 0; i < text.length; i++) {
134
+ var c = _charCodeAt(text, i);
135
+ out += _fromCharCode(c >= 0x41 && c <= 0x5a ? c + 32 : c);
136
+ }
137
+ return out;
138
+ }
139
+
140
+ var HEX = "0123456789ABCDEF";
141
+ function _pctEncodeByte(b) {
142
+ return "%" + _fromCharCode(_charCodeAt(HEX, b >> 4)) + _fromCharCode(_charCodeAt(HEX, b & 0x0f));
143
+ }
144
+
145
+ /** @internal The octets a component's value encodes, with every escape decoded and every bare
146
+ * character held to the component's own set. A character outside that set is not a URI this
147
+ * toolkit reads: the ABNF admits it only percent-encoded, so accepting it would accept a string
148
+ * no conforming producer emits and no other consumer reads the same way. */
149
+ function _decodeOctets(text, isChar, what) {
150
+ var out = intrinsic.bufferAlloc(text.length);
151
+ var n = 0;
152
+ for (var i = 0; i < text.length; i++) {
153
+ var c = _charCodeAt(text, i);
154
+ if (c === 0x25) {
155
+ if (i + 2 >= text.length) throw _err("pkcs11/bad-escape", what + " ends in a truncated percent-escape");
156
+ var hi = _hexValue(_charCodeAt(text, i + 1));
157
+ var lo = _hexValue(_charCodeAt(text, i + 2));
158
+ if (hi < 0 || lo < 0) throw _err("pkcs11/bad-escape", what + " carries a percent-escape that is not two hexadecimal digits");
159
+ out[n++] = (hi << 4) | lo;
160
+ i += 2;
161
+ continue;
162
+ }
163
+ if (!isChar(c)) {
164
+ throw _err("pkcs11/bad-uri", what + " carries a character this URI component admits only percent-encoded (RFC 7512 sec. 2.3, sec. 2.4)");
165
+ }
166
+ out[n++] = c;
167
+ }
168
+ return intrinsic.subarray(out, 0, n);
169
+ }
170
+
171
+ /** @internal A value read as text is UTF-8, per sec. 2.3. A byte run that is not valid UTF-8 is
172
+ * refused rather than replaced, because a replacement character would compare equal to a
173
+ * different token's name. */
174
+ function _decodeText(text, isChar, what) {
175
+ var octets = _decodeOctets(text, isChar, what);
176
+ return guard.text.decode(octets, octets.length, Pkcs11Error, {
177
+ charset: "utf-8", fatal: true, tooLarge: "pkcs11/bad-uri",
178
+ badInput: "pkcs11/bad-input", badDecode: "pkcs11/bad-encoding", label: what,
179
+ });
180
+ }
181
+
182
+ function _decimal(text, what) {
183
+ if (text.length === 0) throw _err("pkcs11/bad-uri", what + " must be a decimal number (RFC 7512 sec. 2.3)");
184
+ var value = 0;
185
+ for (var i = 0; i < text.length; i++) {
186
+ var c = _charCodeAt(text, i);
187
+ if (c < 0x30 || c > 0x39) throw _err("pkcs11/bad-uri", what + " must be a decimal number (RFC 7512 sec. 2.3)");
188
+ value = value * 10 + (c - 0x30);
189
+ if (!intrinsic.isSafeInteger(value)) throw _err("pkcs11/bad-uri", what + " is larger than this toolkit reads as a number");
190
+ }
191
+ return value;
192
+ }
193
+
194
+ /** @internal sec. 2.3: "Both numbers are one byte in size; see the 'libraryVersion' member of the
195
+ * CK_INFO structure". A larger number names a version no token can report. */
196
+ var VERSION_MAX = 255;
197
+ function _versionPart(text, what) {
198
+ var value = _decimal(text, "library-version");
199
+ if (value > VERSION_MAX) {
200
+ throw _err("pkcs11/bad-uri", "the library-version " + what + " is one byte, so it is 0 to 255 (RFC 7512 sec. 2.3), got " + text);
201
+ }
202
+ return value;
203
+ }
204
+
205
+ /** @internal `pk11-lib-ver` of sec. 2.3: the major is required, the minor defaults to zero. */
206
+ function _libraryVersion(text) {
207
+ var dot = -1;
208
+ for (var i = 0; i < text.length; i++) { if (_charCodeAt(text, i) === 0x2e) { dot = i; break; } }
209
+ var out = _create(null);
210
+ if (dot === -1) {
211
+ out.major = _versionPart(text, "major");
212
+ out.minor = 0;
213
+ return out;
214
+ }
215
+ out.major = _versionPart(_strSlice(text, 0, dot), "major");
216
+ out.minor = _versionPart(_strSlice(text, dot + 1), "minor");
217
+ return out;
218
+ }
219
+
220
+ /** @internal `pk11-type`, `pk11-slot-id` and `pk11-lib-ver` are written in the ABNF as literal
221
+ * alternatives and digit runs, with no `pct-encoded` among them. Decoding an escape there and
222
+ * then reading the result would make `%70ublic` and `public` two spellings of one value, and
223
+ * sec. 2.6 asks a consumer to be able to compare two URIs as strings. */
224
+ /** @internal sec. 2.4 asks a consumer to refuse a module-path that is relative, because a relative
225
+ * one names a different shared object depending on where the process happens to be running. What
226
+ * counts as absolute is the platform's answer rather than the RFC's, and this toolkit runs on
227
+ * every platform Node does: a leading separator, a drive letter followed by one, and a UNC root
228
+ * are the three forms. */
229
+ function _isAbsolutePath(text) {
230
+ var first = _charCodeAt(text, 0);
231
+ if (first === 0x2f || first === 0x5c) return true;
232
+ var isDriveLetter = (first >= 0x41 && first <= 0x5a) || (first >= 0x61 && first <= 0x7a);
233
+ if (!isDriveLetter || _charCodeAt(text, 1) !== 0x3a) return false;
234
+ var third = _charCodeAt(text, 2);
235
+ return third === 0x2f || third === 0x5c;
236
+ }
237
+
238
+ function _assertNoEscape(raw, wire) {
239
+ for (var i = 0; i < raw.length; i++) {
240
+ if (_charCodeAt(raw, i) === 0x25) {
241
+ throw _err("pkcs11/bad-uri", "the " + wire + " attribute is written out rather than percent-encoded (RFC 7512 sec. 2.3)");
242
+ }
243
+ }
244
+ return raw;
245
+ }
246
+
247
+ function _readValue(spec, raw, isChar, wire) {
248
+ if (spec.kind === "bytes") return _decodeOctets(raw, isChar, "the " + wire + " attribute");
249
+ if (spec.kind === "number") return _decimal(_assertNoEscape(raw, wire), wire);
250
+ if (spec.kind === "version") return _libraryVersion(_assertNoEscape(raw, wire));
251
+ if (spec.kind === "type") {
252
+ /** @internal RFC 5234 sec. 2.3 makes an ABNF quoted literal case-insensitive, and sec. 2.6
253
+ * asks for comparison after case normalization, so the spelling is read either way and
254
+ * carried in one form. */
255
+ var named = _asciiLower(_assertNoEscape(raw, wire));
256
+ if (OBJECT_TYPES[named] !== 1) {
257
+ throw _err("pkcs11/bad-uri", "the type attribute is one of public, private, cert, secret-key or data (RFC 7512 sec. 2.3), got " + intrinsic.stringify(raw));
258
+ }
259
+ return named;
260
+ }
261
+ var text = _decodeText(raw, isChar, "the " + wire + " attribute");
262
+ if (spec.kind === "path" && !_isAbsolutePath(text)) {
263
+ throw _err("pkcs11/relative-module-path", "a module-path with a relative path is refused (RFC 7512 sec. 2.4)");
264
+ }
265
+ return text;
266
+ }
267
+
268
+ /** @internal `pk11-v-attr-nm-char` of sec. 2.3, asked once so reading and writing hold a vendor
269
+ * name to the same rule. A name the component's own table owns is refused as a vendor name, and
270
+ * a name carrying a delimiter is refused outright: written out, it would put a standard attribute
271
+ * into the URI that the caller never named and that neither component's checks ever saw. */
272
+ function _assertVendorName(wire, table, component, code) {
273
+ if (wire.length === 0) throw _err(code, "a " + component + " attribute name must not be empty (RFC 7512 sec. 2.3)");
274
+ for (var i = 0; i < wire.length; i++) {
275
+ var c = _charCodeAt(wire, i);
276
+ if (!(guard.encoding.isAlphanumericByte(c) || c === 0x2d || c === 0x5f)) {
277
+ throw _err(code, "a vendor attribute name is letters, digits, hyphen and underscore (RFC 7512 sec. 2.3), got " + intrinsic.stringify(wire));
278
+ }
279
+ }
280
+ if (_hasOwn(table, _asciiLower(wire))) {
281
+ throw _err(code, "the " + component + " attribute " + wire + " is defined by RFC 7512 sec. 2.3, so it is named as itself rather than as a vendor attribute");
282
+ }
283
+ }
284
+
285
+ /** @internal One `name=value` run. The name is a vendor name when neither table knows it, and a
286
+ * vendor name is held to `pk11-v-attr-nm-char` so a stray delimiter cannot pass as one. */
287
+ function _attribute(run, table, isChar, component, known, vendor, vendorRepeats) {
288
+ if (run.length === 0) {
289
+ throw _err("pkcs11/bad-uri", "a " + component + " component carries no empty attribute between its delimiters (RFC 7512 sec. 2.3)");
290
+ }
291
+ var eq = -1;
292
+ for (var i = 0; i < run.length; i++) { if (_charCodeAt(run, i) === 0x3d) { eq = i; break; } }
293
+ if (eq === -1) throw _err("pkcs11/bad-uri", "every " + component + " attribute is name=value (RFC 7512 sec. 2.3), got " + intrinsic.stringify(run));
294
+ /** @internal RFC 7512 writes each attribute name as an ABNF quoted literal, which RFC 5234
295
+ * sec. 2.3 makes case-insensitive, so the name is matched in one case. Matching it as written
296
+ * would read `PIN-SOURCE` as a vendor attribute and let it past the rules pin-source is held
297
+ * to, which is the opposite of what those rules are for. */
298
+ var wire = _asciiLower(_strSlice(run, 0, eq));
299
+ var raw = _strSlice(run, eq + 1);
300
+ var spec = _hasOwn(table, wire) ? table[wire] : null;
301
+ if (spec) {
302
+ if (_hasOwn(known, spec.field)) {
303
+ throw _err("pkcs11/duplicate-attribute", "the " + wire + " attribute appears more than once (RFC 7512 sec. 2.3)");
304
+ }
305
+ known[spec.field] = _readValue(spec, raw, isChar, wire);
306
+ return;
307
+ }
308
+ _assertVendorName(wire, table, component, "pkcs11/bad-uri");
309
+ var text = _decodeText(raw, isChar, "the " + wire + " attribute");
310
+ /** @internal sec. 2.4: "Aside from the query attributes defined in this document, duplicate
311
+ * (vendor) attributes MAY be present in the URI query component". The path admits none at all
312
+ * (sec. 2.3), which is why the query carries its vendor values as a list and the path does not. */
313
+ if (!vendorRepeats) {
314
+ if (_hasOwn(vendor, wire)) {
315
+ throw _err("pkcs11/duplicate-attribute", "the " + wire + " attribute appears more than once (RFC 7512 sec. 2.3)");
316
+ }
317
+ vendor[wire] = text;
318
+ return;
319
+ }
320
+ if (!_hasOwn(vendor, wire)) vendor[wire] = [];
321
+ _push(vendor[wire], text);
322
+ }
323
+
324
+ function _split(text, delimiter, into) {
325
+ var start = 0;
326
+ for (var i = 0; i < text.length; i++) {
327
+ if (_charCodeAt(text, i) === delimiter) { _push(into, _strSlice(text, start, i)); start = i + 1; }
328
+ }
329
+ _push(into, _strSlice(text, start));
330
+ return into;
331
+ }
332
+
333
+ function _assertPinExclusive(query) {
334
+ if (query.pinSource != null && query.pinValue != null) {
335
+ throw _err("pkcs11/conflicting-pin", "a URI carrying both pin-source and pin-value is refused (RFC 7512 sec. 2.4)");
336
+ }
337
+ }
338
+
339
+ /**
340
+ * @primitive pki.pkcs11.parseUri
341
+ * @signature pki.pkcs11.parseUri(uri) -> parsed
342
+ * @since 0.8.7
343
+ * @status stable
344
+ * @spec RFC 7512
345
+ * @related pki.pkcs11.formatUri
346
+ *
347
+ * Read an RFC 7512 PKCS #11 URI. Returns `{ path, vendorPath, query, vendorQuery }`,
348
+ * each a prototype-less record, so a computed read answers from the URI and never from
349
+ * `Object.prototype`. `path.id` is a `Buffer`, since the attribute names bytes;
350
+ * `path.slotId` and `path.libraryVersion` (`{ major, minor }`) are numbers. A
351
+ * `vendorQuery` attribute carries a list of its values, which sec. 2.4 permits to repeat;
352
+ * a `vendorPath` attribute carries one value.
353
+ *
354
+ * Fail-closed against the ABNF rather than a general URI reader: a character the
355
+ * component admits only percent-encoded, a truncated or non-hex escape, a value that is
356
+ * not valid UTF-8, an empty attribute between two delimiters, a repeated attribute the
357
+ * RFC does not permit to repeat, a percent-escape inside `type`, `slot-id` or
358
+ * `library-version`, a `type` outside the five
359
+ * the RFC enumerates, a `library-version` component above 255, a URI carrying both
360
+ * `pin-source` and `pin-value`, and a `module-path` that is not absolute are each refused
361
+ * with their own `pkcs11/*` code. An attribute name is matched case-insensitively, so a
362
+ * standard attribute cannot arrive spelled differently and pass as vendor data.
363
+ *
364
+ * @example
365
+ * var u = pki.pkcs11.parseUri("pkcs11:token=My%20Token;object=signing-key;type=private?module-name=softhsm2");
366
+ * u.path.token; // "My Token"
367
+ * u.path.type; // "private"
368
+ * u.query.moduleName; // "softhsm2"
369
+ */
370
+ function parseUri(uri) {
371
+ if (typeof uri !== "string") {
372
+ throw _err("pkcs11/bad-input", "a PKCS #11 URI must be a string, got " + guard.text.showValue(uri));
373
+ }
374
+ var head = _strSlice(uri, 0, SCHEME.length);
375
+ if (_asciiLower(head) !== SCHEME) {
376
+ throw _err("pkcs11/bad-uri", "a PKCS #11 URI begins with the pkcs11: scheme (RFC 7512 sec. 2.1)");
377
+ }
378
+ var rest = _strSlice(uri, SCHEME.length);
379
+ var mark = -1;
380
+ for (var i = 0; i < rest.length; i++) { if (_charCodeAt(rest, i) === 0x3f) { mark = i; break; } }
381
+ var pathText = mark === -1 ? rest : _strSlice(rest, 0, mark);
382
+ var queryText = mark === -1 ? "" : _strSlice(rest, mark + 1);
383
+
384
+ var path = _create(null), vendorPath = _create(null);
385
+ var query = _create(null), vendorQuery = _create(null);
386
+ /** @internal `pk11-path` and `pk11-query` are each optional as a whole, and only as a whole:
387
+ * between two delimiters the ABNF requires an attribute, so an empty run there is a refusal
388
+ * rather than something to skip past. */
389
+ if (pathText.length) {
390
+ _forEach(_split(pathText, 0x3b, []), function (run) {
391
+ _attribute(run, PATH_ATTRS, _isPathChar, "path", path, vendorPath);
392
+ });
393
+ }
394
+ if (queryText.length) {
395
+ _forEach(_split(queryText, 0x26, []), function (run) {
396
+ _attribute(run, QUERY_ATTRS, _isQueryChar, "query", query, vendorQuery, true);
397
+ });
398
+ }
399
+ _assertPinExclusive(query);
400
+
401
+ var out = _create(null);
402
+ out.path = path;
403
+ out.vendorPath = vendorPath;
404
+ out.query = query;
405
+ out.vendorQuery = vendorQuery;
406
+ return out;
407
+ }
408
+
409
+ /** @internal A caller's string is encoded as the UTF-8 sec. 2.3 asks for. An unpaired surrogate
410
+ * has no UTF-8 form, and writing the replacement character for it would put a different name in
411
+ * the URI than the caller asked for, so it is refused instead. */
412
+ function _encodeText(text, isChar, what) {
413
+ var octets = intrinsic.bufferFrom(text, "utf8");
414
+ if (intrinsic.bufToString(octets, "utf8") !== text) {
415
+ throw _err("pkcs11/bad-input", what + " carries a character with no UTF-8 form (an unpaired surrogate)");
416
+ }
417
+ var parts = [];
418
+ for (var i = 0; i < octets.length; i++) {
419
+ var b = octets[i];
420
+ _push(parts, isChar(b) ? _fromCharCode(b) : _pctEncodeByte(b));
421
+ }
422
+ return _join(parts, "");
423
+ }
424
+
425
+ /** @internal sec. 2.3 asks for `id` to be percent-encoded whole rather than read as text, since it
426
+ * carries bytes, and for the hexadecimal to be uppercase so two spellings of one identifier
427
+ * compare equal. */
428
+ function _encodeBytes(value) {
429
+ var octets = guard.bytes.snapshotSource(value, Pkcs11Error, "pkcs11/bad-input", "the id attribute");
430
+ var parts = [];
431
+ for (var i = 0; i < octets.length; i++) _push(parts, _pctEncodeByte(octets[i]));
432
+ return _join(parts, "");
433
+ }
434
+
435
+ function _writableDecimal(value) {
436
+ return intrinsic.isSafeInteger(value) && value >= 0;
437
+ }
438
+
439
+ /** @internal A text attribute carries what the URI carries, which is a string, and `parseUri`
440
+ * answers with one. Coercing anything else writes a value the caller never named: an object
441
+ * reaches the URI as its default string form and a list as its members joined with a comma,
442
+ * and both read back as one value. So the value is required to be a string rather than turned
443
+ * into one, which also settles the second question a coercion raises, since a string cannot
444
+ * answer differently between the check and the encoding. */
445
+ function _writableText(value, what) {
446
+ if (typeof value !== "string") {
447
+ throw _err("pkcs11/bad-input", what + " must be a string, got " + guard.text.showValue(value));
448
+ }
449
+ return value;
450
+ }
451
+
452
+ function _writeValue(kind, value, isChar, wire) {
453
+ if (kind === "bytes") return _encodeBytes(value);
454
+ /** @internal The bound is the one the reader applies, so nothing is written that reading it back
455
+ * would refuse: past the safe-integer range a number also stringifies in exponent notation,
456
+ * which `1*DIGIT` does not admit. */
457
+ if (kind === "number") {
458
+ if (!_writableDecimal(value)) {
459
+ throw _err("pkcs11/bad-input", "the " + wire + " attribute must be a non-negative integer this toolkit reads back (RFC 7512 sec. 2.3)");
460
+ }
461
+ return _String(value);
462
+ }
463
+ if (kind === "version") {
464
+ /** @internal Each member read ONCE, into the number the check and the URI both use. Read
465
+ * again per use, a getter could answer inside the range for the check and outside it for
466
+ * the string, which is the same "checked one value, wrote another" the component snapshot
467
+ * above closes one level up. */
468
+ var pair = value != null && typeof value === "object" ? value : {};
469
+ var major = pair.major;
470
+ var minor = pair.minor;
471
+ if (!_writableDecimal(major) || !_writableDecimal(minor) || major > VERSION_MAX || minor > VERSION_MAX) {
472
+ throw _err("pkcs11/bad-input", "library-version is { major, minor }, each one byte and so 0 to 255 (RFC 7512 sec. 2.3)");
473
+ }
474
+ return _String(major) + "." + _String(minor);
475
+ }
476
+ var text = _writableText(value, "the " + wire + " attribute");
477
+ if (kind === "type") {
478
+ var named = _asciiLower(text);
479
+ if (OBJECT_TYPES[named] !== 1) {
480
+ throw _err("pkcs11/bad-input", "the type attribute is one of public, private, cert, secret-key or data (RFC 7512 sec. 2.3), got " + intrinsic.stringify(text));
481
+ }
482
+ return named;
483
+ }
484
+ if (kind === "path" && !_isAbsolutePath(text)) {
485
+ throw _err("pkcs11/relative-module-path", "a module-path with a relative path is refused (RFC 7512 sec. 2.4)");
486
+ }
487
+ return _encodeText(text, isChar, "the " + wire + " attribute");
488
+ }
489
+
490
+ /** @internal One read per known field, into a record of this module's own. */
491
+ function _snapshotComponent(record, byField, component) {
492
+ if (record == null) return _create(null);
493
+ if (typeof record !== "object") {
494
+ throw _err("pkcs11/bad-input", "the " + component + " component must be an object of attributes");
495
+ }
496
+ guard.identifier.assertKnownKeys(record, byField, _err, "pkcs11/bad-input",
497
+ "the " + component + " component has an unknown attribute ");
498
+ return guard.identifier.snapshotOptions(record, byField);
499
+ }
500
+
501
+ /** @internal One read per own name, since a vendor component names its own attributes. */
502
+ function _snapshotVendor(record, component) {
503
+ var out = _create(null);
504
+ if (record == null) return out;
505
+ if (typeof record !== "object") {
506
+ throw _err("pkcs11/bad-input", "the " + component + " vendor attributes must be an object");
507
+ }
508
+ /** @internal A list is copied too, so a value appended to it while another component is being
509
+ * written cannot reach the URI. */
510
+ _forEach(_keys(record), function (name) {
511
+ var value = record[name];
512
+ if (!intrinsic.isArray(value)) { out[name] = value; return; }
513
+ var copy = [];
514
+ for (var i = 0; i < value.length; i++) _push(copy, value[i]);
515
+ out[name] = copy;
516
+ });
517
+ return out;
518
+ }
519
+
520
+ function _writeComponent(record, byField, isChar, component, parts) {
521
+ _forEach(_keys(byField), function (field) {
522
+ if (!_hasOwn(record, field) || record[field] == null) return;
523
+ var spec = byField[field];
524
+ _push(parts, spec.wire + "=" + _writeValue(spec.kind, record[field], isChar, spec.wire));
525
+ });
526
+ }
527
+
528
+ function _writeVendor(record, table, isChar, component, parts, repeats) {
529
+ /** @internal Lowercased and ordered by name, because the canonical form is what lets two URIs
530
+ * naming one object be compared as strings: the reader matches a name in one case, and the
531
+ * order a caller happened to build the record in is not part of what the URI says. Two names
532
+ * that differ only in case are the one attribute twice, which is what the reader would call
533
+ * them. A name's own values keep the order they were given, since sec. 2.4 leaves what a
534
+ * repeated vendor attribute means to the consumer and the order may be part of it. The
535
+ * standard attributes are already in the order the RFC lists them.
536
+ *
537
+ * A name in this record IS an attribute the caller asked for, the standard components being
538
+ * where a field is named in advance and left unset. So a name carrying nothing is refused
539
+ * rather than skipped: writing the URI without it answers a different URI than the one that
540
+ * was asked for, and says so nowhere. */
541
+ var written = _create(null), names = [];
542
+ _forEach(_keys(record), function (given) {
543
+ _assertVendorName(given, table, component, "pkcs11/bad-input");
544
+ var wire = _asciiLower(given);
545
+ if (_hasOwn(written, wire)) {
546
+ throw _err("pkcs11/duplicate-attribute", "the " + wire + " attribute appears more than once (RFC 7512 sec. 2.3)");
547
+ }
548
+ var value = record[given];
549
+ if (value == null) {
550
+ throw _err("pkcs11/bad-input", "the " + wire + " attribute is named with no value; remove the name or give it one");
551
+ }
552
+ if (repeats) {
553
+ if (!intrinsic.isArray(value)) {
554
+ throw _err("pkcs11/bad-input", "a vendor query attribute carries a list of values, since RFC 7512 sec. 2.4 permits it to repeat; got " + guard.text.showValue(value));
555
+ }
556
+ if (value.length === 0) {
557
+ throw _err("pkcs11/bad-input", "the " + wire + " attribute is named with an empty list; remove the name or give it a value");
558
+ }
559
+ written[wire] = _mapValues(value, wire, isChar);
560
+ } else {
561
+ written[wire] = [_encodeText(_writableText(value, "the " + wire + " attribute"), isChar, "the " + wire + " attribute")];
562
+ }
563
+ _push(names, wire);
564
+ });
565
+ _forEach(intrinsic.sort(names), function (wire) {
566
+ _forEach(written[wire], function (encoded) { _push(parts, wire + "=" + encoded); });
567
+ });
568
+ }
569
+
570
+ function _mapValues(list, wire, isChar) {
571
+ var out = [];
572
+ for (var i = 0; i < list.length; i++) {
573
+ if (list[i] == null) {
574
+ throw _err("pkcs11/bad-input", "a vendor query attribute's list carries no missing value; the " + wire + " attribute has one");
575
+ }
576
+ _push(out, _encodeText(_writableText(list[i], "the " + wire + " attribute"), isChar, "the " + wire + " attribute"));
577
+ }
578
+ return out;
579
+ }
580
+
581
+ var KNOWN_RECORD_KEYS = _assign(_create(null), { path: 1, vendorPath: 1, query: 1, vendorQuery: 1 });
582
+
583
+ /**
584
+ * @primitive pki.pkcs11.formatUri
585
+ * @signature pki.pkcs11.formatUri(record) -> string
586
+ * @since 0.8.7
587
+ * @status stable
588
+ * @spec RFC 7512
589
+ * @related pki.pkcs11.parseUri
590
+ *
591
+ * Write an RFC 7512 PKCS #11 URI from `{ path, vendorPath, query, vendorQuery }`, the
592
+ * shape `parseUri` returns. What comes out is the canonical form rather than the input
593
+ * byte for byte: attributes are emitted in the order the RFC lists them, each value is
594
+ * percent-encoded to what its own component admits unencoded, `id` is encoded whole with
595
+ * uppercase hexadecimal, `slot-id` drops leading zeros and `library-version` states its
596
+ * minor. That is what makes two URIs naming one object compare equal as strings, which
597
+ * sec. 2.6 asks of a consumer, and the form is stable: writing it again changes nothing.
598
+ *
599
+ * The rules `parseUri` refuses are refused here too rather than written out: a `type`
600
+ * outside the five the RFC enumerates, both `pin-source` and `pin-value`, a relative
601
+ * `module-path`, and a number this toolkit would not read back. An attribute the RFC does
602
+ * not define is named in the refusal, so a misspelled field is a refusal rather than a
603
+ * silently dropped one; put a vendor attribute under `vendorPath` or `vendorQuery`, where
604
+ * its name is held to the vendor grammar and refused if a standard attribute owns it, so
605
+ * a name carrying a delimiter cannot put an attribute into the URI the caller never named.
606
+ * A `vendorQuery` attribute is given as a list and its values are written in the order the
607
+ * list gives them, which is the shape `parseUri` returns; a `vendorPath` attribute is
608
+ * given one value. A vendor name given no value, or given an empty list, is refused as
609
+ * well, since the name is the attribute the caller asked for.
610
+ *
611
+ * @example
612
+ * pki.pkcs11.formatUri({ path: { token: "My Token", type: "private" }, query: { moduleName: "softhsm2" } });
613
+ * // -> "pkcs11:token=My%20Token;type=private?module-name=softhsm2"
614
+ */
615
+ function formatUri(record) {
616
+ if (record == null || typeof record !== "object" || guard.bytes.isByteSource(record)) {
617
+ throw _err("pkcs11/bad-input", "a PKCS #11 URI is written from { path, vendorPath, query, vendorQuery }");
618
+ }
619
+ guard.identifier.assertKnownKeys(record, KNOWN_RECORD_KEYS, _err, "pkcs11/bad-input",
620
+ "a PKCS #11 URI record has an unknown field ");
621
+
622
+ /** @internal Every component is snapshotted BEFORE anything is written. A getter on one
623
+ * component that ran while another was being written could otherwise add an attribute to a
624
+ * component written after it, past a check that had already passed. What is checked is what
625
+ * is written. */
626
+ var path = _snapshotComponent(record.path, PATH_BY_FIELD, "path");
627
+ var query = _snapshotComponent(record.query, QUERY_BY_FIELD, "query");
628
+ var vendorPath = _snapshotVendor(record.vendorPath, "path");
629
+ var vendorQuery = _snapshotVendor(record.vendorQuery, "query");
630
+ _assertPinExclusive(query);
631
+
632
+ var pathParts = [], queryParts = [];
633
+ _writeComponent(path, PATH_BY_FIELD, _isPathChar, "path", pathParts);
634
+ _writeVendor(vendorPath, PATH_ATTRS, _isPathChar, "path", pathParts);
635
+ _writeComponent(query, QUERY_BY_FIELD, _isQueryChar, "query", queryParts);
636
+ _writeVendor(vendorQuery, QUERY_ATTRS, _isQueryChar, "query", queryParts, true);
637
+
638
+ var out = SCHEME + _join(pathParts, ";");
639
+ if (queryParts.length) out += "?" + _join(queryParts, "&");
640
+ return out;
641
+ }
642
+
643
+ module.exports = intrinsic.freeze({
644
+ parseUri: parseUri,
645
+ formatUri: formatUri,
646
+ });
@@ -188,7 +188,12 @@ function _assertMacIter(n, cap) {
188
188
 
189
189
  function _bagAttributes(bag) {
190
190
  var attrs = [];
191
- if (bag.friendlyName != null) attrs.push(b.sequence([b.oid(O("friendlyName")), b.set([b.bmpString(String(bag.friendlyName))])]));
191
+ if (bag.friendlyName != null) {
192
+ if (typeof bag.friendlyName !== "string") {
193
+ throw _err("pkcs12/bad-input", "a bag's friendlyName must be a string, since it is the text a key store lists the bag under");
194
+ }
195
+ attrs.push(b.sequence([b.oid(O("friendlyName")), b.set([b.bmpString(bag.friendlyName)])]));
196
+ }
192
197
  if (bag.localKeyId != null) {
193
198
  if (bag.localKeyId === "ski") throw _err("pkcs12/bad-input", "localKeyId 'ski' auto-derivation is not yet supported -- supply an explicit Buffer");
194
199
  attrs.push(b.sequence([b.oid(O("localKeyId")), b.set([b.octetString(_bytes(bag.localKeyId, "localKeyId"))])]));