@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.
- package/CHANGELOG.md +143 -76
- package/README.md +4 -2
- package/index.js +11 -0
- package/lib/acme.js +15 -78
- package/lib/asn1-der.js +58 -6
- package/lib/cbor-det.js +11 -1
- 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 +8 -0
- package/lib/guard-async.js +53 -2
- package/lib/guard-encoding.js +105 -1
- package/lib/guard-identifier.js +74 -1
- package/lib/guard-intrinsic.js +2 -0
- package/lib/guard-json.js +1 -6
- package/lib/guard-limits.js +40 -1
- package/lib/guard-list.js +25 -0
- package/lib/guard-name.js +134 -22
- 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/pkcs11-uri.js +646 -0
- package/lib/pkcs12-build.js +6 -1
- 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
|
@@ -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
|
+
});
|
package/lib/pkcs12-build.js
CHANGED
|
@@ -188,7 +188,12 @@ function _assertMacIter(n, cap) {
|
|
|
188
188
|
|
|
189
189
|
function _bagAttributes(bag) {
|
|
190
190
|
var attrs = [];
|
|
191
|
-
if (bag.friendlyName != null)
|
|
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"))])]));
|