@blamejs/pki 0.5.16 → 0.5.18
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 +57 -1
- package/lib/acme.js +7 -4
- package/lib/asn1-der.js +1 -0
- package/lib/attrcert-sign.js +1 -0
- package/lib/cbor-det.js +1 -0
- package/lib/cmc-verify.js +1 -0
- package/lib/cmp-build.js +16 -1
- package/lib/cms-decrypt.js +58 -29
- package/lib/cms-encrypt.js +200 -38
- package/lib/cms-sign.js +18 -4
- package/lib/cms-verify.js +5 -1
- package/lib/constants.js +1 -0
- package/lib/crl-sign.js +62 -2
- package/lib/crmf-sign.js +1 -0
- package/lib/csr-sign.js +1 -0
- package/lib/ct.js +1 -0
- package/lib/est.js +1 -0
- package/lib/framework-error.js +1 -0
- package/lib/guard-secret.js +45 -1
- package/lib/hpke.js +6 -2
- package/lib/http-transport.js +1 -0
- package/lib/inspect.js +1 -0
- package/lib/jose.js +31 -3
- package/lib/key.js +2 -1
- package/lib/lint.js +1 -0
- package/lib/merkle.js +1 -0
- package/lib/ocsp.js +1 -0
- package/lib/oid.js +1 -0
- package/lib/path-validate.js +1 -0
- package/lib/pbes2.js +16 -8
- package/lib/pkcs12-build.js +203 -12
- package/lib/schema-all.js +1 -0
- package/lib/schema-c509.js +1 -0
- package/lib/schema-csrattrs.js +1 -0
- package/lib/schema-engine.js +1 -0
- package/lib/schema-pkcs8.js +1 -0
- package/lib/schema-smime.js +1 -0
- package/lib/schema-tsp.js +1 -0
- package/lib/schema-x509.js +1 -0
- package/lib/shbs.js +1 -0
- package/lib/sigstore.js +1 -0
- package/lib/smime.js +1 -0
- package/lib/tls-cert-compress.js +1 -0
- package/lib/trust.js +1 -0
- package/lib/tsp-sign.js +65 -0
- package/lib/webauthn.js +1 -0
- package/lib/webcrypto.js +11 -8
- package/lib/x509-sign.js +38 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/pkcs12-build.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @module pki.pkcs12
|
|
6
6
|
* @nav Signing
|
|
7
7
|
* @title PKCS#12
|
|
8
|
+
* @fullname PKCS#12 keystores (.p12 and .pfx): build and open
|
|
8
9
|
* @intro The PKCS#12 (.p12 / .pfx) producing side (RFC 7292, RFC 9579). `pki.pkcs12.build` assembles a
|
|
9
10
|
* password-integrity PFX -- key, certificate, CRL, and secret bags (shrouded keys and cert safes encrypted
|
|
10
11
|
* under RFC 8018 PBES2) wrapped in an AuthenticatedSafe, protected by either a classic Appendix B HMAC or
|
|
@@ -42,6 +43,7 @@ var x509 = require("./schema-x509");
|
|
|
42
43
|
var schemaCrl = require("./schema-crl");
|
|
43
44
|
var key = require("./key");
|
|
44
45
|
var cms = require("./cms-verify"); // pki.cms.sign (build the id-signedData authSafe) + pki.cms.verify (the open integrity gate) + cms.encrypt (the privacy envelope)
|
|
46
|
+
var cmsSign = require("./cms-sign"); // the signer-descriptor field tables, for the integrity signers this verb authors
|
|
45
47
|
var cmsDecrypt = require("./cms-decrypt"); // the @internal decryptEnvelopedData seam -- opens a privacy safe off the pkcs12 parser's already-walked (BER-capable) EnvelopedData node
|
|
46
48
|
var schemaPkcs12 = require("./schema-pkcs12");
|
|
47
49
|
var pkix = require("./schema-pkix");
|
|
@@ -115,6 +117,91 @@ var _MISSING_PASSWORD = "a password must be a string, Buffer, or Uint8Array -- a
|
|
|
115
117
|
// Every field opts.integrity reads. Adding one here is the only way to make it accepted, so a
|
|
116
118
|
// capability cannot arrive with its option silently ignored at this boundary.
|
|
117
119
|
var _INTEGRITY_OPTS = { mode: 1, signer: 1, signers: 1, certificates: 1, sid: 1, signingTime: 1 };
|
|
120
|
+
// The two caller-owned objects pki.pkcs12.build reads. The spec keys are what _normalizeSpec
|
|
121
|
+
// consumes -- the explicit `safeContents` form and the `{ key, cert, ca }` shorthand -- so the table
|
|
122
|
+
// is derived from that function rather than from the doc block. opts.integrity already had its own
|
|
123
|
+
// door; the objects containing it did not, so a misspelled `certificates` at the top level was
|
|
124
|
+
// dropped and the store built without them.
|
|
125
|
+
// The spec comes in two mutually exclusive FORMS, so it is checked against the one it selects.
|
|
126
|
+
// `_normalizeSpec` returns `spec.safeContents` the moment it is an array and never looks at the
|
|
127
|
+
// shorthand fields, so a union accepted `{ safeContents: [...], key }` and built a store with the
|
|
128
|
+
// key silently absent -- the same omission the door exists to refuse, reached by mixing the forms
|
|
129
|
+
// rather than by misspelling one.
|
|
130
|
+
var _BUILD_SPEC_FULL_KEYS = { safeContents: 1 };
|
|
131
|
+
var _BUILD_SPEC_SHORTHAND_KEYS = { key: 1, cert: 1, ca: 1, friendlyName: 1, localKeyId: 1 };
|
|
132
|
+
// `recipientCerts` selects the privacy recipients and is read by _normalizeSpec, which _build hands
|
|
133
|
+
// opts to -- it belongs here even though _build never names it. The three verbs this module exports
|
|
134
|
+
// have disjoint option sets (verifyMac reads maxIterations / recipientKey / allowUnauthenticated,
|
|
135
|
+
// open reads signerCerts / keys / importAlgorithm), so build gets its own table rather than a
|
|
136
|
+
// shared one that would admit theirs.
|
|
137
|
+
// Per spec FORM, because `recipientCerts` is read only by the shorthand branch of _normalizeSpec.
|
|
138
|
+
// Under the full form it is read by nothing: a caller who put a plaintext key bag in safeContents
|
|
139
|
+
// and supplied recipientCerts expecting public-key privacy got an id-data safe holding the private
|
|
140
|
+
// key in cleartext. The full form builds its own safes, so privacy there is `recipients` on the
|
|
141
|
+
// entry.
|
|
142
|
+
var _BUILD_OPTS_FULL = { integrity: 1, mac: 1, password: 1, pem: 1 };
|
|
143
|
+
var _BUILD_OPTS_SHORTHAND = { integrity: 1, mac: 1, password: 1, pem: 1, recipientCerts: 1 };
|
|
144
|
+
// The fields a safeContents entry carries. This entry is where the PRIVACY directive lives, which
|
|
145
|
+
// makes an unrecognized name here the costliest misspelling in the format: a misspelled `encrypt`
|
|
146
|
+
// is neither present nor falsy, so it passes the present-but-falsy guard in _buildAuthSafeElement,
|
|
147
|
+
// no privacy is selected, and the safe goes out as plaintext id-data -- an unshrouded keyBag in the
|
|
148
|
+
// clear, inside a PFX whose MAC still verifies and which opens without complaint.
|
|
149
|
+
// Per privacy branch, because the branches read different fields:
|
|
150
|
+
// `contentEncryptionAlgorithm` is the public-key branch's content cipher and is read by NOTHING on
|
|
151
|
+
// a password safe, whose cipher comes from `encrypt.cipher`. A union accepted an explicit cipher
|
|
152
|
+
// request on a password safe and encrypted under the default anyway.
|
|
153
|
+
var _SAFE_RECIPIENTS_KEYS = { bags: 1, recipients: 1, contentEncryptionAlgorithm: 1 };
|
|
154
|
+
var _SAFE_PASSWORD_KEYS = { bags: 1, encrypt: 1 };
|
|
155
|
+
var _SAFE_PLAINTEXT_KEYS = { bags: 1 };
|
|
156
|
+
// The fields a bag carries, PER TYPE rather than as one union. A union would admit a field that
|
|
157
|
+
// this bag's own arm never reads, which is worse here than a dropped attribute: `encrypt` on a
|
|
158
|
+
// `key` bag is read by nothing, so the private key is emitted as a plaintext keyBag while the
|
|
159
|
+
// caller supplied an explicit encryption directive and the outer MAC gives integrity only. Naming
|
|
160
|
+
// the fields per type makes that combination a refusal instead of a silent downgrade. Written out
|
|
161
|
+
// per type rather than merged at load, so each row reads as the whole contract for that bag.
|
|
162
|
+
var _BAG_KEYS_BY_TYPE = {
|
|
163
|
+
key: { type: 1, friendlyName: 1, localKeyId: 1, key: 1 },
|
|
164
|
+
shroudedKey: { type: 1, friendlyName: 1, localKeyId: 1, key: 1, encrypt: 1 },
|
|
165
|
+
cert: { type: 1, friendlyName: 1, localKeyId: 1, cert: 1 },
|
|
166
|
+
crl: { type: 1, friendlyName: 1, localKeyId: 1, crl: 1 },
|
|
167
|
+
secret: { type: 1, friendlyName: 1, localKeyId: 1, secretTypeId: 1, secretValue: 1 },
|
|
168
|
+
safeContents: { type: 1, friendlyName: 1, localKeyId: 1, nested: 1 },
|
|
169
|
+
};
|
|
170
|
+
// The PBE descriptor both privacy levels share (_pbeOpts is the one place it is read, so one door
|
|
171
|
+
// covers the safe-level and bag-level spellings). A misspelled parameter reverts to the built-in
|
|
172
|
+
// default, so a caller who asked for a stronger KDF or a different cipher silently gets neither.
|
|
173
|
+
var _ENCRYPT_KEYS = { password: 1, cipher: 1, salt: 1, iterations: 1, prf: 1 };
|
|
174
|
+
// The MAC descriptor. Every field has a default, so a misspelled one reverts to the shipped value:
|
|
175
|
+
// a caller who asked for a higher iteration count, a different hash or a longer key gets the
|
|
176
|
+
// default instead, and the store carries no record of which was used.
|
|
177
|
+
// Per MAC algorithm: the classic Appendix B key derivation produces a key at the hash's own output
|
|
178
|
+
// length and has no keyLength to set, so admitting one there took an explicit security parameter and
|
|
179
|
+
// emitted the same MAC as a call that never named it. Only PBMAC1 (RFC 9579) derives to a length.
|
|
180
|
+
var _MAC_KEYS = { algorithm: 1, hash: 1, salt: 1, iterations: 1, keyLength: 1, password: 1 };
|
|
181
|
+
var _MAC_KEYS_HMAC = { algorithm: 1, hash: 1, salt: 1, iterations: 1, password: 1 };
|
|
182
|
+
var _MAC_KEYS_PBMAC1 = { algorithm: 1, hash: 1, salt: 1, iterations: 1, keyLength: 1, password: 1 };
|
|
183
|
+
// The reading verbs take their own option sets, disjoint from build's. `maxIterations` is the one
|
|
184
|
+
// that matters most: it caps the work a hostile store can demand, so a misspelling restores the
|
|
185
|
+
// built-in ceiling and the caller's tighter bound is never applied.
|
|
186
|
+
// The table is what the CALL reads, including through its delegations -- and those cross a module
|
|
187
|
+
// boundary here. recipientKey / recipientCert are read by _openEncryptedSafe; `recipientIndex` is
|
|
188
|
+
// read by cms-decrypt's decryptEnvelopedData, which _openEncryptedSafe hands this whole options
|
|
189
|
+
// object to, so a table built from this file's own `opts.` reads alone would refuse a documented
|
|
190
|
+
// option and break selecting a recipient by index in a multi-recipient store.
|
|
191
|
+
//
|
|
192
|
+
// One table here, not one per branch, and the distinction is who selects the branch. A builder's
|
|
193
|
+
// options are checked against the form the CALLER chose, because a field the chosen form never
|
|
194
|
+
// reads is a request that silently did not happen. Reading a store, the branch is chosen by the
|
|
195
|
+
// BYTES: which integrity mode it carries, and whether a safe is password- or recipient-encrypted,
|
|
196
|
+
// are not known until it is parsed. These options are the policy a caller declares in advance for
|
|
197
|
+
// whatever the store turns out to be, so one that goes unread means the store did not need it --
|
|
198
|
+
// signerCerts on a MAC'd store, recipientKey on one with no enveloped safe. Refusing those would
|
|
199
|
+
// make a correct, store-agnostic call fail on the shape of its input.
|
|
200
|
+
var _OPEN_OPTS = {
|
|
201
|
+
allowUnauthenticated: 1, importAlgorithm: 1, keys: 1, maxIterations: 1, signerCerts: 1,
|
|
202
|
+
recipientKey: 1, recipientCert: 1, recipientIndex: 1,
|
|
203
|
+
};
|
|
204
|
+
var _VERIFY_MAC_OPTS = { maxIterations: 1 };
|
|
118
205
|
|
|
119
206
|
// The same encoding, reporting ownership, mirroring pbes2.passwordBytesOwned. A caller-supplied
|
|
120
207
|
// Buffer is returned as-is and is borrowed: clearing it would destroy the caller's own credential,
|
|
@@ -279,6 +366,14 @@ function _safeBag(bagName, bagValueDer, bag) {
|
|
|
279
366
|
|
|
280
367
|
function _buildBag(bag, opts, depth) {
|
|
281
368
|
if (!bag || typeof bag !== "object") throw _err("pkcs12/bad-input", "each bag must be an object with a type");
|
|
369
|
+
// Checked against THIS type's table. An unrecognized type falls through to the switch below,
|
|
370
|
+
// which is the one place that names the permitted set.
|
|
371
|
+
if (guard.intrinsic.hasOwn(_BAG_KEYS_BY_TYPE, bag.type)) {
|
|
372
|
+
guard.identifier.assertKnownKeys(bag, _BAG_KEYS_BY_TYPE[bag.type], _err, "pkcs12/bad-input", function (k) {
|
|
373
|
+
return "unknown field " + JSON.stringify(k) + " on a " + JSON.stringify(bag.type) +
|
|
374
|
+
" bag; that field is not read for this bag type";
|
|
375
|
+
});
|
|
376
|
+
}
|
|
282
377
|
switch (bag.type) {
|
|
283
378
|
case "key": {
|
|
284
379
|
var keyDer = _coerceDer(bag.key, "keyBag key");
|
|
@@ -289,9 +384,12 @@ function _buildBag(bag, opts, depth) {
|
|
|
289
384
|
var kDer = _coerceDer(bag.key, "shroudedKey key");
|
|
290
385
|
try { pkcs8.parse(kDer); } catch (e2) { throw _err("pkcs12/bad-input", "shroudedKey key is not a well-formed PKCS#8 PrivateKeyInfo", e2); }
|
|
291
386
|
var enc = bag.encrypt || {};
|
|
387
|
+
// The descriptor is checked BEFORE its password is read, so no field of an object carrying an
|
|
388
|
+
// unrecognized name reaches the KDF -- the same order the safe-level path uses below.
|
|
389
|
+
var bagPbe = _pbeOpts(enc);
|
|
292
390
|
var pw = _pbePasswordOwned(enc.password != null ? enc.password : opts.password);
|
|
293
391
|
var r;
|
|
294
|
-
try { r = pbes2.pbes2Encrypt(pw.bytes, kDer,
|
|
392
|
+
try { r = pbes2.pbes2Encrypt(pw.bytes, kDer, bagPbe, _err, "pkcs12"); }
|
|
295
393
|
finally { _wipePw(pw); }
|
|
296
394
|
return _safeBag("pkcs8ShroudedKeyBag", b.sequence([r.algId, b.octetString(r.ct)]), bag); // EncryptedPrivateKeyInfo
|
|
297
395
|
}
|
|
@@ -348,6 +446,10 @@ function _reqDer(input, label) {
|
|
|
348
446
|
}
|
|
349
447
|
|
|
350
448
|
function _pbeOpts(enc) {
|
|
449
|
+
guard.identifier.assertKnownKeys(enc, _ENCRYPT_KEYS, _err, "pkcs12/bad-input", function (k) {
|
|
450
|
+
return "unknown encrypt field " + JSON.stringify(k) +
|
|
451
|
+
"; the PBE descriptor is { password?, cipher?, salt?, iterations?, prf? }";
|
|
452
|
+
});
|
|
351
453
|
var cipher = CIPHER_NAME[enc.cipher || "aes-256-cbc"];
|
|
352
454
|
if (!cipher) throw _err("pkcs12/bad-input", "unsupported PBES2 cipher " + JSON.stringify(enc.cipher) + " (aes-128-cbc / aes-192-cbc / aes-256-cbc)");
|
|
353
455
|
return {
|
|
@@ -378,6 +480,18 @@ async function _buildAuthSafeElement(sc, opts) {
|
|
|
378
480
|
var hasRecipients = sc.recipients !== undefined;
|
|
379
481
|
var hasEncrypt = sc.encrypt !== undefined;
|
|
380
482
|
if (hasEncrypt && hasRecipients) throw _err("pkcs12/bad-input", "a safeContents cannot combine encrypt (password) and recipients (public-key) -- one ContentInfo is one privacy type (RFC 7292 sec. 4.1)");
|
|
483
|
+
// Checked against the branch the directive above selects, and BEFORE that branch reads anything.
|
|
484
|
+
// The branches read different fields: `contentEncryptionAlgorithm` is the public-key branch's
|
|
485
|
+
// cipher and is read by nothing on a password safe, whose cipher comes from `encrypt.cipher` --
|
|
486
|
+
// so a union accepted an explicit cipher request on a password safe and encrypted under the
|
|
487
|
+
// default anyway.
|
|
488
|
+
guard.identifier.assertKnownKeys(sc, hasRecipients ? _SAFE_RECIPIENTS_KEYS : (hasEncrypt ? _SAFE_PASSWORD_KEYS : _SAFE_PLAINTEXT_KEYS),
|
|
489
|
+
_err, "pkcs12/bad-input", function (k) {
|
|
490
|
+
return "unknown safeContents field " + JSON.stringify(k) + " for a " +
|
|
491
|
+
(hasRecipients ? "public-key safe; it takes { bags, recipients, contentEncryptionAlgorithm? }"
|
|
492
|
+
: hasEncrypt ? "password safe; it takes { bags, encrypt } and its cipher comes from encrypt.cipher"
|
|
493
|
+
: "plaintext safe; it takes { bags }, and a privacy directive must be encrypt or recipients");
|
|
494
|
+
});
|
|
381
495
|
var safeContentsDer = _buildSafeContents(sc.bags || [], opts, 0);
|
|
382
496
|
if (hasRecipients) {
|
|
383
497
|
if (!Array.isArray(sc.recipients) || !sc.recipients.length) throw _err("pkcs12/bad-input", "safeContents.recipients must be a non-empty array of recipient descriptors (RFC 5652 sec. 6.1)");
|
|
@@ -400,9 +514,12 @@ async function _buildAuthSafeElement(sc, opts) {
|
|
|
400
514
|
return b.sequence([b.oid(O("data")), b.explicit(0, b.octetString(safeContentsDer))]);
|
|
401
515
|
}
|
|
402
516
|
if (!sc.encrypt || typeof sc.encrypt !== "object") throw _err("pkcs12/bad-input", "safeContents.encrypt must be an object { password? } (RFC 7292 sec. 5.1) -- omit it entirely for a plaintext safe");
|
|
517
|
+
// Checked before the password is read, so no field of a descriptor carrying an unrecognized name
|
|
518
|
+
// reaches the KDF.
|
|
519
|
+
var safePbe = _pbeOpts(sc.encrypt);
|
|
403
520
|
var pw = _pbePasswordOwned(sc.encrypt.password != null ? sc.encrypt.password : opts.password);
|
|
404
521
|
var r;
|
|
405
|
-
try { r = pbes2.pbes2Encrypt(pw.bytes, safeContentsDer,
|
|
522
|
+
try { r = pbes2.pbes2Encrypt(pw.bytes, safeContentsDer, safePbe, _err, "pkcs12"); }
|
|
406
523
|
finally { _wipePw(pw); }
|
|
407
524
|
var eci = b.sequence([b.oid(O("data")), r.algId, b.contextPrimitive(0, r.ct)]); // EncryptedContentInfo, [0] IMPLICIT ct
|
|
408
525
|
var encData = b.sequence([b.integer(0n), eci]); // EncryptedData { version 0, eci }
|
|
@@ -463,11 +580,26 @@ async function _buildMacData(macOpts, sharedPassword, authSafeDer) {
|
|
|
463
580
|
// Accept the OpenSSL-style convenience form ({ key, cert, ca, friendlyName, localKeyId }) or the full
|
|
464
581
|
// inverse-of-parse form ({ safeContents: SafeContentsSpec[] }). The convenience form -> one privacy safe of
|
|
465
582
|
// PBES2 cert bags + one shrouded-key safe.
|
|
466
|
-
|
|
467
|
-
|
|
583
|
+
// Which of the two forms a spec is gets decided ONCE, and that one decision is what both the door
|
|
584
|
+
// and this normalizer act on. Decided separately they can disagree: two spellings of "is this the
|
|
585
|
+
// full form" drift apart on their own, and a caller getter returning an array to one and something
|
|
586
|
+
// else to the other makes them disagree on demand -- the full form checked while the shorthand form
|
|
587
|
+
// is the one assembled.
|
|
588
|
+
function _specForm(spec) {
|
|
589
|
+
var isObject = !!spec && typeof spec === "object" && !Buffer.isBuffer(spec);
|
|
590
|
+
var safeContents = isObject ? spec.safeContents : null;
|
|
591
|
+
var full = guard.intrinsic.isArray(safeContents);
|
|
592
|
+
return { isObject: isObject, full: full, safeContents: full ? safeContents : null };
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
function _normalizeSpec(spec, opts, form) {
|
|
596
|
+
if (form.full) return form.safeContents;
|
|
468
597
|
if (spec && (spec.key != null || spec.cert != null)) {
|
|
469
|
-
|
|
470
|
-
|
|
598
|
+
// One read of the caller's array, so the emptiness check and the recipients built from it cannot
|
|
599
|
+
// be handed different values.
|
|
600
|
+
var recipientCerts = opts.recipientCerts;
|
|
601
|
+
var enveloped = recipientCerts != null;
|
|
602
|
+
if (enveloped && (!guard.intrinsic.isArray(recipientCerts) || !recipientCerts.length)) throw _err("pkcs12/bad-input", "opts.recipientCerts must be a non-empty array of recipient certificates");
|
|
471
603
|
var certBags = [];
|
|
472
604
|
if (spec.cert != null) certBags.push({ type: "cert", cert: spec.cert, friendlyName: spec.friendlyName, localKeyId: spec.localKeyId });
|
|
473
605
|
if (spec.ca != null && !Array.isArray(spec.ca)) throw _err("pkcs12/bad-input", "spec.ca must be an array of certificates");
|
|
@@ -479,7 +611,7 @@ function _normalizeSpec(spec, opts) {
|
|
|
479
611
|
// caller password, if any, remains the MAC (integrity) password; privacy and integrity are independent.
|
|
480
612
|
var bags = certBags.slice();
|
|
481
613
|
if (spec.key != null) bags.push({ type: "key", key: spec.key, friendlyName: spec.friendlyName, localKeyId: spec.localKeyId });
|
|
482
|
-
sc.push({ recipients:
|
|
614
|
+
sc.push({ recipients: recipientCerts.map(function (c) { return { cert: c }; }), bags: bags });
|
|
483
615
|
return sc;
|
|
484
616
|
}
|
|
485
617
|
if (certBags.length) sc.push({ encrypt: { password: opts.password }, bags: certBags });
|
|
@@ -511,7 +643,7 @@ function _normalizeSpec(spec, opts) {
|
|
|
511
643
|
*
|
|
512
644
|
* @opts
|
|
513
645
|
* - `password` -- the shared privacy + integrity password (string / Buffer / Uint8Array).
|
|
514
|
-
* - `mac` -- `false` for a MAC-less store, or `{ algorithm: 'hmac'(default)|'pbmac1', hash: 'sha256'(default)|'sha1'|'sha384'|'sha512', salt?, iterations
|
|
646
|
+
* - `mac` -- `false` for a MAC-less store, or `{ algorithm: 'hmac'(default)|'pbmac1', hash: 'sha256'(default)|'sha1'|'sha384'|'sha512', salt?, iterations? }`. `keyLength` is a `pbmac1` field: the classic Appendix B derivation produces a key at the hash's own output length, and naming one under `hmac` is refused.
|
|
515
647
|
* - `integrity` -- `{ mode: 'public-key', signer: { cert, key, digestAlgorithm?, pss? } | signers: [ ... ], sid?, signingTime?, certificates? }` for public-key integrity (a CMS SignedData authSafe over any `pki.cms.sign` signer algorithm, no MacData). Combining it with a truthy `mac` is rejected.
|
|
516
648
|
* - `recipientCerts` -- (public-key privacy convenience) `[certDer|PEM, ...]`; the convenience-form cert + key are placed in one recipient-enveloped safe (a plaintext keyBag). For the full form, set per-`safeContents` `recipients: [{ cert }, ...]` -- CERTIFICATE recipients only (RSA-OAEP / ECDH / X25519 / X448 / ML-KEM, dispatched off the cert key); a password or KEK recipient is not public-key privacy and is rejected -- with an optional `contentEncryptionAlgorithm` (`aes-128|192|256-cbc`, default 256; GCM/AEAD rejected). `encrypt` (password) and `recipients` (public-key) on the same safe is rejected; privacy is independent of integrity.
|
|
517
649
|
* - `pem` (boolean) -- return a PEM `PKCS12` string instead of DER.
|
|
@@ -540,6 +672,26 @@ function build(spec, opts) {
|
|
|
540
672
|
|
|
541
673
|
async function _build(spec, opts) {
|
|
542
674
|
opts = opts || {};
|
|
675
|
+
// Before the spec is normalized, so a misspelled bag field is a refusal rather than a store built
|
|
676
|
+
// without it. A PKCS#12 file that silently omits the certificate or the key a caller asked for
|
|
677
|
+
// looks well-formed and opens cleanly; the omission surfaces wherever it is later imported.
|
|
678
|
+
var form = _specForm(spec);
|
|
679
|
+
if (form.isObject) {
|
|
680
|
+
guard.identifier.assertKnownKeys(spec, form.full ? _BUILD_SPEC_FULL_KEYS : _BUILD_SPEC_SHORTHAND_KEYS,
|
|
681
|
+
_err, "pkcs12/bad-input", function (k) {
|
|
682
|
+
return "unknown PKCS#12 spec field " + JSON.stringify(k) + " for the " +
|
|
683
|
+
(form.full ? "safeContents form; the shorthand fields belong to { key, cert, ca } and are not read here"
|
|
684
|
+
: "shorthand form; pass safeContents instead to build the safes yourself");
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
// The options answer to the same form: recipientCerts is read only by the shorthand branch.
|
|
688
|
+
guard.identifier.assertKnownKeys(opts, form.full ? _BUILD_OPTS_FULL : _BUILD_OPTS_SHORTHAND, _err, "pkcs12/bad-input",
|
|
689
|
+
function (k) {
|
|
690
|
+
return "pki.pkcs12.build has an unknown option " + JSON.stringify(k) +
|
|
691
|
+
(form.full && k === "recipientCerts"
|
|
692
|
+
? " for the safeContents form; the full form carries privacy per entry, as safeContents[i].recipients"
|
|
693
|
+
: "");
|
|
694
|
+
});
|
|
543
695
|
// The integrity mode is checked against the permitted set rather than compared to one literal.
|
|
544
696
|
// Compared, any other spelling reads as "not public-key" and silently selects password integrity,
|
|
545
697
|
// dropping the signer with it: a caller who wrote "publicKey" gets a MAC where they asked for a
|
|
@@ -555,7 +707,7 @@ async function _build(spec, opts) {
|
|
|
555
707
|
// RFC 7292 sec. 4: public-key integrity OMITS MacData entirely -- a caller combining opts.mac with it is a
|
|
556
708
|
// config-time reject (the self-check re-parse would otherwise fail the coherence rule anyway).
|
|
557
709
|
if (pubKey && opts.mac != null && opts.mac !== false) throw _err("pkcs12/bad-integrity-mode", "public-key integrity has no MacData -- do not combine opts.mac with opts.integrity.mode 'public-key' (RFC 7292 sec. 4)");
|
|
558
|
-
var safeContentsSpecs = _normalizeSpec(spec, opts);
|
|
710
|
+
var safeContentsSpecs = _normalizeSpec(spec, opts, form);
|
|
559
711
|
if (!Array.isArray(safeContentsSpecs) || !safeContentsSpecs.length) throw _err("pkcs12/bad-input", "the store has no safe contents");
|
|
560
712
|
var elements = [];
|
|
561
713
|
for (var i = 0; i < safeContentsSpecs.length; i++) elements.push(await _buildAuthSafeElement(safeContentsSpecs[i], opts));
|
|
@@ -568,13 +720,48 @@ async function _build(spec, opts) {
|
|
|
568
720
|
// password) provides the integrity, and there is NO MacData. Compose pki.cms.sign -- so every cms.sign
|
|
569
721
|
// signer algorithm (RSA / ECDSA / EdDSA / ML-DSA / SLH-DSA / composite) ships for free.
|
|
570
722
|
var ig = opts.integrity;
|
|
723
|
+
// `signer` is the one-signer spelling of `signers`, and the list wins outright, so supplying
|
|
724
|
+
// both signed the store under the list alone and dropped the single signer without a word.
|
|
725
|
+
if (ig.signers != null && ig.signer != null) throw _err("pkcs12/bad-input", "supply exactly one of opts.integrity.signer or opts.integrity.signers, not both");
|
|
571
726
|
var signers = ig.signers != null ? ig.signers : (ig.signer != null ? [ig.signer] : null);
|
|
572
727
|
if (!Array.isArray(signers) || !signers.length) throw _err("pkcs12/bad-input", "public-key integrity requires opts.integrity.signer or opts.integrity.signers (a cms.sign signer descriptor)");
|
|
728
|
+
// The signer descriptors are authoring input for this store, so they answer to the same rule as
|
|
729
|
+
// every other field the caller writes here, against the form each one selects. Every field
|
|
730
|
+
// beyond the identity has a default: without the door a misspelled `pss` or `digestAlgorithm`
|
|
731
|
+
// signed the store under PKCS#1 with SHA-256 while the caller had asked for something else, and
|
|
732
|
+
// the store records only what was used, never what was requested.
|
|
733
|
+
for (var sx = 0; sx < signers.length; sx++) {
|
|
734
|
+
var sd = signers[sx];
|
|
735
|
+
if (sd == null || typeof sd !== "object" || Buffer.isBuffer(sd)) throw _err("pkcs12/bad-input", "each opts.integrity signer must be a descriptor object");
|
|
736
|
+
var keyOnly = sd.cert == null && sd.spki != null;
|
|
737
|
+
guard.identifier.assertKnownKeys(sd, keyOnly ? cmsSign.KNOWN_SIGNER_KEY_ONLY_KEYS : cmsSign.KNOWN_SIGNER_CERT_KEYS,
|
|
738
|
+
_err, "pkcs12/bad-input", function (k) {
|
|
739
|
+
return "unknown opts.integrity signer field " + JSON.stringify(k) + " for the " + (keyOnly
|
|
740
|
+
? "key-only form; it takes { key, spki, keyIdentifier } plus the signature parameters"
|
|
741
|
+
: "certificate form; it takes { key, cert } plus the signature parameters");
|
|
742
|
+
});
|
|
743
|
+
}
|
|
573
744
|
var authSafeCi = await cms.sign(authSafeDer, signers, { eContentType: "data", detached: false, certificates: ig.certificates !== false, sid: ig.sid, signingTime: ig.signingTime });
|
|
574
745
|
pfx = b.sequence([b.integer(3n), b.raw(authSafeCi)]); // PFX ::= SEQUENCE { version v3, authSafe (id-signedData) } -- NO MacData
|
|
575
746
|
} else {
|
|
576
747
|
var pfxChildren = [b.integer(3n), b.sequence([b.oid(O("data")), b.explicit(0, b.octetString(authSafeDer))])];
|
|
577
|
-
|
|
748
|
+
// An array passes `typeof x === "object"`, and its own names are indices no table lists, so an
|
|
749
|
+
// array reached _buildMacData as an empty descriptor and produced the DEFAULT MAC. Excluded by
|
|
750
|
+
// the same test opts.integrity already applies, read through the load-time capture so a
|
|
751
|
+
// replaced Array.isArray cannot reopen it.
|
|
752
|
+
if (opts.mac != null && opts.mac !== false && (typeof opts.mac !== "object" || guard.intrinsic.isArray(opts.mac))) throw _err("pkcs12/bad-input", "opts.mac must be false or a { algorithm, hash, salt, iterations, keyLength } object");
|
|
753
|
+
if (opts.mac != null && opts.mac !== false) {
|
|
754
|
+
// The algorithm selects the derivation, so the table that runs is the one belonging to it.
|
|
755
|
+
// An unrecognized spelling keeps the union; _buildMacData refuses it by name a moment later.
|
|
756
|
+
var macAlg = opts.mac.algorithm == null ? "hmac" : opts.mac.algorithm;
|
|
757
|
+
var macKeys = macAlg === "hmac" ? _MAC_KEYS_HMAC : macAlg === "pbmac1" ? _MAC_KEYS_PBMAC1 : _MAC_KEYS;
|
|
758
|
+
guard.identifier.assertKnownKeys(opts.mac, macKeys, _err, "pkcs12/bad-input", function (k) {
|
|
759
|
+
return "opts.mac has an unknown option " + JSON.stringify(k) +
|
|
760
|
+
(macAlg === "hmac" && k === "keyLength"
|
|
761
|
+
? " for the classic HMAC algorithm; its key is derived at the hash's own output length, and only pbmac1 takes a keyLength"
|
|
762
|
+
: "");
|
|
763
|
+
});
|
|
764
|
+
}
|
|
578
765
|
if (opts.mac !== false) pfxChildren.push(await _buildMacData(opts.mac || {}, opts.password, authSafeDer));
|
|
579
766
|
pfx = b.sequence(pfxChildren);
|
|
580
767
|
}
|
|
@@ -611,6 +798,9 @@ async function _build(spec, opts) {
|
|
|
611
798
|
* var ok = await pki.pkcs12.verifyMac(p12, 'changeit');
|
|
612
799
|
*/
|
|
613
800
|
async function verifyMac(pfx, password, opts) {
|
|
801
|
+
if (opts != null && typeof opts === "object" && !Buffer.isBuffer(opts)) {
|
|
802
|
+
guard.identifier.assertKnownKeys(opts, _VERIFY_MAC_OPTS, _err, "pkcs12/bad-input", "pki.pkcs12.verifyMac has an unknown option ");
|
|
803
|
+
}
|
|
614
804
|
return _verifyMacOfStore(_storeFromBytes(pfx), password, opts);
|
|
615
805
|
}
|
|
616
806
|
|
|
@@ -714,7 +904,8 @@ function _capWork(iterations, salt, opts, keyLength, hardCap) {
|
|
|
714
904
|
*/
|
|
715
905
|
async function open(pfx, password, opts) {
|
|
716
906
|
opts = opts || {};
|
|
717
|
-
|
|
907
|
+
guard.identifier.assertKnownKeys(opts, _OPEN_OPTS, _err, "pkcs12/bad-input", "pki.pkcs12.open has an unknown option ");
|
|
908
|
+
if (opts.maxIterations != null &&(typeof opts.maxIterations !== "number" || !isFinite(opts.maxIterations) || opts.maxIterations < 1 || Math.floor(opts.maxIterations) !== opts.maxIterations)) {
|
|
718
909
|
throw _err("pkcs12/bad-input", "maxIterations must be a positive integer");
|
|
719
910
|
}
|
|
720
911
|
var m = _storeFromBytes(pfx);
|
|
@@ -848,7 +1039,7 @@ function _decryptLegacyPbe(ea, ct, password, opts, budget) {
|
|
|
848
1039
|
try {
|
|
849
1040
|
if (scheme.rc2) return rc2.cbcDecrypt(keyM, scheme.rc2, iv, ct, _err, "pkcs12/decrypt-failed");
|
|
850
1041
|
var d = nodeCrypto.createDecipheriv(scheme.cipher, keyM, iv);
|
|
851
|
-
return
|
|
1042
|
+
return guard.secret.cipherFinish(d, ct, Pkcs12Error, "pkcs12/bad-input", "the recovered safe contents");
|
|
852
1043
|
} catch (_e) { throw _err("pkcs12/decrypt-failed", "decryption failed"); }
|
|
853
1044
|
finally { guard.secret.zeroize(keyM, Pkcs12Error, "pkcs12/bad-input", "the password-derived decryption key"); }
|
|
854
1045
|
}
|
package/lib/schema-all.js
CHANGED
package/lib/schema-c509.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @module pki.schema.c509
|
|
6
6
|
* @nav Schema
|
|
7
7
|
* @title C509
|
|
8
|
+
* @fullname C509 compact certificates (CBOR-encoded X.509)
|
|
8
9
|
* @intro C509 CBOR-encoded certificates (draft-ietf-cose-cbor-encoded-cert). A compact CBOR
|
|
9
10
|
* re-encoding of an X.509 v3 certificate: a deterministic-CBOR array of exactly 11 elements
|
|
10
11
|
* (10 TBS fields + the issuer signature). Two modes: c509CertificateType 2 is a natively-signed
|
package/lib/schema-csrattrs.js
CHANGED
package/lib/schema-engine.js
CHANGED
package/lib/schema-pkcs8.js
CHANGED
package/lib/schema-smime.js
CHANGED
package/lib/schema-tsp.js
CHANGED
package/lib/schema-x509.js
CHANGED
package/lib/shbs.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @module pki.shbs
|
|
6
6
|
* @nav Signatures
|
|
7
7
|
* @title Stateful hash-based
|
|
8
|
+
* @fullname Stateful hash-based signatures: LMS and XMSS
|
|
8
9
|
* @intro Stateful hash-based signature VERIFICATION -- HSS/LMS (RFC 8554),
|
|
9
10
|
* carried in X.509 by RFC 9802 and in CMS by RFC 9708, profiled by NIST
|
|
10
11
|
* SP 800-208. Verify only, by deliberate design: stateful hash-based signing
|
package/lib/sigstore.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @module pki.sigstore
|
|
6
6
|
* @nav Supply chain
|
|
7
7
|
* @title Sigstore
|
|
8
|
+
* @fullname Sigstore bundles: keyless signing and provenance
|
|
8
9
|
* @intro Zero-dependency verifier for a Sigstore bundle, the exact artifact
|
|
9
10
|
* `npm publish --provenance` produces and the npm registry serves at its
|
|
10
11
|
* attestations API. A bundle is a keyless (Fulcio) signature over a DSSE-wrapped
|
package/lib/smime.js
CHANGED
package/lib/tls-cert-compress.js
CHANGED
package/lib/trust.js
CHANGED
package/lib/tsp-sign.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @module pki.tsp
|
|
6
6
|
* @nav Signing
|
|
7
7
|
* @title Timestamps
|
|
8
|
+
* @fullname RFC 3161 trusted timestamps: request, sign and verify
|
|
8
9
|
* @intro Create an RFC 3161 timestamp token. A TimeStampToken IS a CMS SignedData whose
|
|
9
10
|
* encapsulated content is a `TSTInfo` (the timestamped message imprint + trusted time), so
|
|
10
11
|
* `sign(messageImprint, tsa, opts)` builds the `TSTInfo`, attaches the RFC 3161 sec. 2.4.2
|
|
@@ -141,9 +142,38 @@ function sign(messageImprint, tsa, opts) {
|
|
|
141
142
|
], _sign);
|
|
142
143
|
}
|
|
143
144
|
|
|
145
|
+
// The three caller-owned objects pki.tsp.sign reads. A dropped option here is a token that quietly
|
|
146
|
+
// lacks what was asked for -- `odering` for `ordering` emitted a token with the ordering flag unset,
|
|
147
|
+
// which is a claim about the timestamp's orderability that the caller believes they made.
|
|
148
|
+
// The TSA is gated too, and the reason it needs to be is not the one it looks like. Both fields it
|
|
149
|
+
// carries are REQUIRED, so a MISSPELLING of either is already refused for what it leaves missing --
|
|
150
|
+
// but that is only half the class. A name belonging on the OPTIONS, written on the TSA instead, is
|
|
151
|
+
// read by nothing here: `ordering` placed on the TSA emits a token the same size as one that never
|
|
152
|
+
// asked for it, with the flag unset, while the caller believes they set it. Requiring the argument
|
|
153
|
+
// to carry only what it reads is what closes that, so all three caller-owned arguments answer the
|
|
154
|
+
// same question -- is this a name I read? -- rather than two of them answering it.
|
|
155
|
+
var _TSA_KEYS = { cert: 1, key: 1 };
|
|
156
|
+
var _IMPRINT_KEYS = { hashAlgorithm: 1, hashedMessage: 1 };
|
|
157
|
+
var _SIGN_OPTS = {
|
|
158
|
+
policy: 1, serialNumber: 1, genTime: 1, nonce: 1, accuracy: 1, ordering: 1, sid: 1,
|
|
159
|
+
digestAlgorithm: 1, certHashAlgorithm: 1, additionalSignedAttributes: 1, pss: 1, pem: 1,
|
|
160
|
+
};
|
|
161
|
+
|
|
144
162
|
function _sign(messageImprint, tsa, opts) {
|
|
145
163
|
opts = opts || {};
|
|
164
|
+
if (messageImprint && typeof messageImprint === "object" && !Buffer.isBuffer(messageImprint)) {
|
|
165
|
+
guard.identifier.assertKnownKeys(messageImprint, _IMPRINT_KEYS, _err, "tsp/bad-input", function (k) {
|
|
166
|
+
return "unknown messageImprint field " + JSON.stringify(k) + " (the imprint is { hashAlgorithm, hashedMessage })";
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
guard.identifier.assertKnownKeys(opts, _SIGN_OPTS, _err, "tsp/bad-input", "pki.tsp.sign has an unknown option ");
|
|
146
170
|
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("tsp/bad-input", "pki.tsp.sign options must be an object");
|
|
171
|
+
if (tsa && typeof tsa === "object" && !Buffer.isBuffer(tsa)) {
|
|
172
|
+
guard.identifier.assertKnownKeys(tsa, _TSA_KEYS, _err, "tsp/bad-input", function (k) {
|
|
173
|
+
return "unknown TSA field " + JSON.stringify(k) +
|
|
174
|
+
" (the TSA is { cert, key }; a signing option belongs in the third argument)";
|
|
175
|
+
});
|
|
176
|
+
}
|
|
147
177
|
var mi = messageImprint || {};
|
|
148
178
|
if (!mi.hashAlgorithm || !NODE_DIGEST[mi.hashAlgorithm]) throw _err("tsp/unsupported-algorithm", "messageImprint.hashAlgorithm must be a supported hash name");
|
|
149
179
|
if (!Buffer.isBuffer(mi.hashedMessage) && !(mi.hashedMessage instanceof Uint8Array)) throw _err("tsp/bad-input", "messageImprint.hashedMessage must be a Buffer");
|
|
@@ -186,7 +216,17 @@ function _sign(messageImprint, tsa, opts) {
|
|
|
186
216
|
// Accuracy ::= SEQUENCE { seconds INTEGER OPTIONAL, millis [0] INTEGER (1..999) OPTIONAL,
|
|
187
217
|
// micros [1] INTEGER (1..999) OPTIONAL } (RFC 3161 sec. 2.4.2). seconds is a non-negative
|
|
188
218
|
// integer; millis and micros MUST each be in 1..999 -- enforced before encoding.
|
|
219
|
+
// Accuracy ::= SEQUENCE { seconds INTEGER OPTIONAL, millis [0] OPTIONAL, micros [1] OPTIONAL }
|
|
220
|
+
// (RFC 3161 sec. 2.4.2). All three are OPTIONAL and encoded only when present, so a misspelling is
|
|
221
|
+
// missed by nothing: `milis` emitted an EMPTY Accuracy that parses back as zero seconds, millis and
|
|
222
|
+
// micros, and the token then understates its own precision -- a claim about the timestamp that the
|
|
223
|
+
// caller believed they had made.
|
|
224
|
+
var _ACCURACY_KEYS = { seconds: 1, millis: 1, micros: 1 };
|
|
225
|
+
|
|
189
226
|
function _accuracy(a) {
|
|
227
|
+
guard.identifier.assertKnownKeys(a, _ACCURACY_KEYS, _err, "tsp/bad-input", function (k) {
|
|
228
|
+
return "unknown accuracy field " + JSON.stringify(k) + "; Accuracy is { seconds?, millis?, micros? }";
|
|
229
|
+
});
|
|
190
230
|
var f = [];
|
|
191
231
|
if (a.seconds != null) {
|
|
192
232
|
var s = Number(a.seconds);
|
|
@@ -263,10 +303,29 @@ function _assertImprint(mi) {
|
|
|
263
303
|
* var sha256Digest = Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello")));
|
|
264
304
|
* var req = pki.tsp.request({ hashAlgorithm: "sha256", hashedMessage: sha256Digest }, { nonce: 0x0102030405060708n, certReq: true });
|
|
265
305
|
*/
|
|
306
|
+
// The options pki.tsp.request encodes into the TimeStampReq. Every one is OPTIONAL in the
|
|
307
|
+
// structure, so a misspelled name is missed by nothing and the request goes out claiming less than
|
|
308
|
+
// was asked for. `nonce` is the one that costs most: misspelled, the request carries no nonce at
|
|
309
|
+
// all, and the caller matches a reply against a replay defense that was never asked for.
|
|
310
|
+
var _REQUEST_OPTS = { reqPolicy: 1, nonce: 1, certReq: 1, extensions: 1, pem: 1 };
|
|
311
|
+
|
|
266
312
|
function request(messageImprint, opts) {
|
|
267
313
|
opts = opts || {};
|
|
268
314
|
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("tsp/bad-input", "pki.tsp.request options must be an object");
|
|
315
|
+
guard.identifier.assertKnownKeys(opts, _REQUEST_OPTS, _err, "tsp/bad-input", "pki.tsp.request has an unknown option ");
|
|
269
316
|
var mi = messageImprint || {};
|
|
317
|
+
// The same table pki.tsp.sign holds its imprint to. The request's own options live in the second
|
|
318
|
+
// argument, so `nonce` written on the imprint reaches nothing and the request goes out without
|
|
319
|
+
// the replay defense the caller believes they asked for.
|
|
320
|
+
// The same table pki.tsp.sign holds its imprint to. The request's own options live in the second
|
|
321
|
+
// argument, so `nonce` written on the imprint reaches nothing and the request goes out without
|
|
322
|
+
// the replay defense the caller believes they asked for.
|
|
323
|
+
if (mi && typeof mi === "object" && !Buffer.isBuffer(mi)) {
|
|
324
|
+
guard.identifier.assertKnownKeys(mi, _IMPRINT_KEYS, _err, "tsp/bad-input", function (k) {
|
|
325
|
+
return "unknown messageImprint field " + JSON.stringify(k) +
|
|
326
|
+
" (the imprint is { hashAlgorithm, hashedMessage }; a request option belongs in the second argument)";
|
|
327
|
+
});
|
|
328
|
+
}
|
|
270
329
|
_assertImprint(mi);
|
|
271
330
|
var imprint = b.sequence([_hashAlgId(mi.hashAlgorithm), b.octetString(Buffer.from(mi.hashedMessage))]);
|
|
272
331
|
// TimeStampReq: version(1), messageImprint, then OPTIONAL reqPolicy / nonce / certReq /
|
|
@@ -348,9 +407,15 @@ function parseRequest(input) { return schemaTsp.parseRequest(input); }
|
|
|
348
407
|
* var resp = pki.tsp.response(token, {}); // granted
|
|
349
408
|
* var rej = pki.tsp.response(null, { status: 2, failInfo: ["badAlg"] }); // rejection
|
|
350
409
|
*/
|
|
410
|
+
// The options pki.tsp.response encodes into the TimeStampResp. `status` carries the verdict and
|
|
411
|
+
// defaults to granted(0), so a misspelled name does not lose a detail -- it INVERTS the answer: a
|
|
412
|
+
// TSA refusing a request emits a granted response instead, over a token it did mean to withhold.
|
|
413
|
+
var _RESPONSE_OPTS = { status: 1, statusString: 1, failInfo: 1, pem: 1 };
|
|
414
|
+
|
|
351
415
|
function response(token, opts) {
|
|
352
416
|
opts = opts || {};
|
|
353
417
|
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("tsp/bad-input", "pki.tsp.response options must be an object");
|
|
418
|
+
guard.identifier.assertKnownKeys(opts, _RESPONSE_OPTS, _err, "tsp/bad-input", "pki.tsp.response has an unknown option ");
|
|
354
419
|
var status = opts.status == null ? 0 : Number(opts.status);
|
|
355
420
|
if (!Number.isInteger(status) || status < 0 || status > 5) throw _err("tsp/bad-input", "PKIStatus must be an integer in 0..5 (RFC 3161 sec. 2.4.2)");
|
|
356
421
|
var granted = status === 0 || status === 1;
|
package/lib/webauthn.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @module pki.webauthn
|
|
6
6
|
* @nav Validation
|
|
7
7
|
* @title WebAuthn
|
|
8
|
+
* @fullname WebAuthn: verify registration attestation and assertions
|
|
8
9
|
* @intro Trust evaluation of a W3C WebAuthn (Level 3) / passkey attestation: parse
|
|
9
10
|
* the attestation object + authenticatorData, decode the COSE credential public
|
|
10
11
|
* key, and verify each defined attestation-statement format (packed, tpm,
|