@blamejs/pki 0.8.11 → 0.8.14

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/lib/trust.js CHANGED
@@ -46,11 +46,14 @@ var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
46
46
  var _strSlice = intrinsic.uncurry(String.prototype.slice);
47
47
  var x509 = require("./schema-x509");
48
48
  var pkix = require("./schema-pkix");
49
+ var oid = require("./oid");
50
+ var trustanchorSchema = require("./schema-trustanchor");
49
51
 
50
52
  var TrustError = errors.TrustError;
51
53
  var LIMITS = constants.LIMITS;
52
54
 
53
55
  function E(code, message, cause) { return new TrustError(code, message, cause); }
56
+ var _NS = pkix.makeNS("trust", TrustError, oid);
54
57
 
55
58
  var PURPOSES = ["serverAuth", "emailProtection", "codeSigning"];
56
59
 
@@ -351,14 +354,30 @@ function _mkAnchor(cert, meta) {
351
354
  label: meta.label,
352
355
  mozillaCaPolicy: meta.mozillaCaPolicy,
353
356
  };
354
- _DERIVED_FROM.set(entry, {
357
+ /** @internal The certificate's own basic constraints are associated with the anchor, so its
358
+ * pathLenConstraint bounds the path below it (RFC 5937 sec. 3.2). */
359
+ var certPathLen = pkix.anchorPathLenFromExtensions(cert.extensions, _NS, "the trust anchor certificate");
360
+ if (certPathLen !== null) {
361
+ var cc = intrinsic.create(null);
362
+ cc.pathLenConstraint = certPathLen;
363
+ guard.verdict.set(entry, "constraints", cc);
364
+ }
365
+ var derived = {
355
366
  name: _copyName(cert.subject),
356
367
  publicKey: Buffer.from(spki.bytes),
357
368
  algorithm: spki.algorithm.oid,
358
369
  parameters: spki.algorithm.parameters == null ? spki.algorithm.parameters : Buffer.from(spki.algorithm.parameters),
359
- purposes: _copyPurposes(meta.purposes),
370
+ purposes: _purposesOrNull(meta.purposes),
360
371
  distrustAfter: _copyDistrustAfter(meta.distrustAfter),
361
- });
372
+ };
373
+ /** @internal An independent copy, so editing the entry's own constraints cannot change what
374
+ * `anchor()` enforces; it reads this record where there is one. */
375
+ if (certPathLen !== null) {
376
+ var dc = intrinsic.create(null);
377
+ dc.pathLenConstraint = certPathLen;
378
+ derived.constraints = dc;
379
+ }
380
+ _DERIVED_FROM.set(entry, derived);
362
381
  return entry;
363
382
  }
364
383
  var _DERIVED_FROM = new WeakMap();
@@ -371,27 +390,39 @@ function _copyPurposes(src) {
371
390
  });
372
391
  }
373
392
 
393
+ /** @internal An EXPLICIT null purposes set stays null; an absent one still defaults to all-false,
394
+ * which is the fail-closed reading of a store entry that named no purpose.
395
+ *
396
+ * The two differ because the formats do. A store that lists a root and grants it nothing says
397
+ * something, and all-false is how that is written. A format carrying no purpose bits at all, such
398
+ * as an RFC 5914 trust anchor, says nothing: turning that into all-false would read as "trusted
399
+ * for no purpose" and make `pki.path.validate` demand a purpose to check a constraint the anchor
400
+ * never stated. The `anchor` entry gate directly below reads a null the same way, refusing an
401
+ * entry whose `purposes` is `!= null` and letting a null through as absent. */
402
+ function _purposesOrNull(src) { return src === null ? null : _copyPurposes(src); }
403
+
374
404
  function _copyDistrustAfter(src) {
375
405
  var out = intrinsic.create(null);
376
406
  if (!src || typeof src !== "object") return out;
377
- Object.keys(src).forEach(function (k) {
378
- out[k] = guard.time.isDate(src[k]) ? new Date(guard.time.instantOf(src[k])) : src[k];
379
- });
407
+ var names = guard.identifier.optionNames(src);
408
+ for (var i = 0; i < names.length; i++) {
409
+ out[names[i]] = guard.time.isDate(src[names[i]]) ? new Date(guard.time.instantOf(src[names[i]])) : src[names[i]];
410
+ }
380
411
  return out;
381
412
  }
382
413
 
383
414
  function _copyName(name) {
384
415
  if (!name || !Array.isArray(name.rdns)) return name;
385
- var out = Object.assign({}, name);
416
+ var out = guard.identifier.ownOptions(name);
386
417
  out.rdns = guard.list.copyMap(name.rdns, function (rdn) {
387
- return Array.isArray(rdn) ? guard.list.copyMap(rdn, function (atv) { return Object.assign({}, atv); }) : rdn;
418
+ return Array.isArray(rdn) ? guard.list.copyMap(rdn, function (atv) { return guard.identifier.ownOptions(atv); }) : rdn;
388
419
  });
389
420
  out.bytes = Buffer.isBuffer(name.bytes) ? Buffer.from(name.bytes) : name.bytes;
390
421
  return out;
391
422
  }
392
423
 
393
424
  function _datesEqual(x, y) {
394
- var kx = Object.keys(x).sort(), ky = Object.keys(y).sort();
425
+ var kx = guard.identifier.optionNames(x).sort(), ky = guard.identifier.optionNames(y).sort();
395
426
  if (kx.join(",") !== ky.join(",")) return false;
396
427
  return kx.every(function (k) { return guard.time.instantOf(x[k]) === guard.time.instantOf(y[k]); });
397
428
  }
@@ -929,7 +960,7 @@ function anchor(entry, opts) {
929
960
  throw E("trust/purpose-not-trusted", "this root is not a trusted delegator for " + opts.purpose);
930
961
  }
931
962
  }
932
- if (!derived && (entry.purposes != null || (entry.distrustAfter && Object.keys(entry.distrustAfter).length))) {
963
+ if (!derived && (entry.purposes != null || (entry.distrustAfter && guard.identifier.optionNames(entry.distrustAfter).length))) {
933
964
  throw E("trust/bad-input", "this entry carries a trust store's per-purpose metadata but is not the entry the store produced -- it has been rebuilt, and the metadata is a statement about the key the store read, not about whichever key the copy now names. Pass pki.trust.parseCertdata / parseCcadbCsv output unmodified");
934
965
  }
935
966
  var nameConstraints = _copyNameConstraints(opts.nameConstraints);
@@ -940,18 +971,168 @@ function anchor(entry, opts) {
940
971
  parameters: Buffer.isBuffer(meta.parameters) ? Buffer.from(meta.parameters)
941
972
  : (meta.parameters !== undefined ? meta.parameters : null),
942
973
  distrustAfter: _copyDistrustAfter(meta.distrustAfter),
943
- purposes: _copyPurposes(meta.purposes),
974
+ purposes: _purposesOrNull(meta.purposes),
944
975
  };
945
976
  /** @internal Set only when an overlay was supplied, so an anchor without one is the object this
946
977
  * verb returned before the option existed, and defined rather than assigned: an accessor planted
947
978
  * on Object.prototype under this name would otherwise take the assignment and leave the anchor
948
979
  * carrying no restriction at all. */
949
980
  if (nameConstraints) guard.verdict.set(out, "nameConstraints", nameConstraints);
981
+ /** @internal The RFC 5914 anchor's own constraints, kept separate from the `nameConstraints`
982
+ * overlay a caller may also supply: RFC 5937 sec. 3.2 intersects every permitting set rather than
983
+ * letting one replace another, so the two have to reach the validator as two statements.
984
+ *
985
+ * Read from `meta`, which is the derived record when there is one and the caller's entry when
986
+ * there is not, so a copy of an entry keeps the restrictions the original carried. They can only
987
+ * narrow a validation, so an entry that states them is safe to honor whoever built it. */
988
+ if (meta.constraints) guard.verdict.set(out, "constraints", _copyConstraints(meta.constraints));
950
989
  return out;
951
990
  }
952
991
 
992
+ /** @internal Copied entry by entry, for the same reason every other anchor field is: what the
993
+ * validator enforces must be what this verb read, not what a caller's object answers later. */
994
+ function _copyConstraints(c) {
995
+ var out = intrinsic.create(null);
996
+ if (c.nameConstraints) {
997
+ out.nameConstraints = {
998
+ permitted: _copySubtrees(c.nameConstraints.permitted, "permitted"),
999
+ excluded: _copySubtrees(c.nameConstraints.excluded, "excluded"),
1000
+ };
1001
+ }
1002
+ if (c.policySet) out.policySet = intrinsic.map(c.policySet, function (p) { return p; });
1003
+ if (c.policyFlags) {
1004
+ var f = intrinsic.create(null);
1005
+ intrinsic.forEach(trustanchorSchema.policyFlagBits, function (k) { f[k] = c.policyFlags[k] === true; });
1006
+ out.policyFlags = f;
1007
+ }
1008
+ if (c.pathLenConstraint !== undefined) out.pathLenConstraint = c.pathLenConstraint;
1009
+ return out;
1010
+ }
1011
+
1012
+ /** @internal An RFC 5914 anchor's constraints, in the shape `pki.path.validate` reads them. Each
1013
+ * field is absent rather than null when the anchor states nothing, so an anchor that constrains
1014
+ * nothing is the object this module produced before the format existed. */
1015
+ function _anchorConstraintsOf(taInfo) {
1016
+ var cp = taInfo.certPath;
1017
+ /** @internal `exts` is where RFC 5914 sec. 2.6 says an anchor associates any extension outside
1018
+ * the four `certPath` already carries, and basic constraints is one of them, so a path length
1019
+ * stated there binds as much as one stated in `certPath` (RFC 5937 sec. 3.2). Where both state
1020
+ * one, the stricter wins: an anchor may only narrow. */
1021
+ var extsPathLen = pkix.anchorPathLenFromExtensions(taInfo.exts, _NS, "the TrustAnchorInfo exts");
1022
+ if (cp === null) {
1023
+ if (extsPathLen === null) return null;
1024
+ var only = intrinsic.create(null);
1025
+ only.pathLenConstraint = extsPathLen;
1026
+ return only;
1027
+ }
1028
+ var out = intrinsic.create(null);
1029
+ var any = false;
1030
+ if (cp.nameConstr !== null) {
1031
+ out.nameConstraints = {
1032
+ permitted: intrinsic.map(cp.nameConstr.permittedSubtrees || [], _subtreeOf),
1033
+ excluded: intrinsic.map(cp.nameConstr.excludedSubtrees || [], _subtreeOf),
1034
+ };
1035
+ any = true;
1036
+ }
1037
+ if (cp.policySet !== null) {
1038
+ out.policySet = intrinsic.map(cp.policySet, function (p) { return p.policyIdentifier; });
1039
+ any = true;
1040
+ }
1041
+ if (cp.policyFlags !== null) { out.policyFlags = cp.policyFlags; any = true; }
1042
+ var pathLen = pkix.lowerPathLen(cp.pathLenConstraint, extsPathLen);
1043
+ if (pathLen !== null) { out.pathLenConstraint = pathLen; any = true; }
1044
+ return any ? out : null;
1045
+ }
1046
+ function _subtreeOf(st) { return { tag: st.base.tagNumber, base: st.base.value }; }
1047
+
1048
+ /**
1049
+ * @primitive pki.trust.parseTrustAnchorList
1050
+ * @signature pki.trust.parseTrustAnchorList(input, opts?) -> { anchors }
1051
+ * @since 0.8.13
1052
+ * @status stable
1053
+ * @spec RFC 5914, RFC 5937 sec. 3.2
1054
+ * @related pki.trust.anchor, pki.schema.trustanchor.parse, pki.path.validate
1055
+ *
1056
+ * Read an RFC 5914 `TrustAnchorList` into the trust-store entries `pki.trust.anchor` takes, the
1057
+ * same shape `parseCertdata` and `parseCcadbCsv` produce. Each entry carries the anchor's name,
1058
+ * public key and algorithm, so a list published by a root program drives `pki.path.validate`
1059
+ * without the operator mapping it by hand.
1060
+ *
1061
+ * A `taInfo` anchor's constraints ride on the entry and are applied as RFC 5937 section 3.2 states,
1062
+ * which is that the stricter of the anchor's value and the caller's always wins: its name
1063
+ * constraints intersect the caller's permitted subtrees and union its excluded ones, its
1064
+ * `policySet` intersects `opts.userInitialPolicySet`, each of its `policyFlags` may turn the
1065
+ * matching `initial*` option on and never off, and its `pathLenConstraint` bounds the path.
1066
+ *
1067
+ * The `certificate` and `tbsCert` arms carry no constraints of their own, so an entry from either
1068
+ * is the anchor that certificate's subject and key describe.
1069
+ *
1070
+ * @opts
1071
+ * - `maxBytes` / `maxDepth` / `maxItems` (integer) -- decode caps for the parse.
1072
+ * @example
1073
+ * var pair = await pki.key.generate("Ed25519");
1074
+ * var der = pki.trustanchor.build({ anchors: [{ taInfo: {
1075
+ * pubKey: await pki.key.export(pair.publicKey), keyId: Buffer.alloc(20, 1),
1076
+ * certPath: { taName: "CN=Example Root" } } }] });
1077
+ * pki.trust.parseTrustAnchorList(der).anchors.length; // -> 1
1078
+ */
1079
+ function parseTrustAnchorList(input, opts) {
1080
+ var list = trustanchorSchema.parse(input, opts);
1081
+ var out = [];
1082
+ intrinsic.forEach(list.anchors, function (a, i) {
1083
+ guard.list.append(out, _entryFromAnchor(a, i));
1084
+ });
1085
+ return { anchors: out };
1086
+ }
1087
+
1088
+ function _entryFromAnchor(a, i) {
1089
+ if (a.kind === "certificate" || a.kind === "tbsCert") {
1090
+ var cert = a.kind === "certificate" ? a.certificate : a.tbsCert;
1091
+ return _mkAnchor(cert, { distrustAfter: {}, purposes: null, label: null, mozillaCaPolicy: false });
1092
+ }
1093
+ var info = a.taInfo;
1094
+ if (info.certPath === null) {
1095
+ throw E("trust/bad-input", "anchors[" + i + "] is a TrustAnchorInfo carrying no certPath, so it names no " +
1096
+ "distinguished name to chain to; a trust anchor needs one (RFC 5914 sec. 2)");
1097
+ }
1098
+ var spki = info.pubKey;
1099
+ var constraints = _anchorConstraintsOf(info);
1100
+ var entry = {
1101
+ name: info.certPath.taName,
1102
+ publicKey: spki.bytes,
1103
+ algorithm: spki.algorithm.oid,
1104
+ parameters: spki.algorithm.parameters,
1105
+ subjectDer: info.certPath.taName.bytes,
1106
+ distrustAfter: {},
1107
+ purposes: null,
1108
+ label: info.taTitle,
1109
+ mozillaCaPolicy: false,
1110
+ };
1111
+ /** @internal On the ENTRY, not only in the derived record behind it. The entry carries a name, a
1112
+ * public key and an algorithm, which is exactly the anchor tuple `pki.path.validate` accepts, so
1113
+ * an operator passing `parseTrustAnchorList(der).anchors` straight to `trustAnchors` is doing the
1114
+ * obvious thing. Keeping the constraints out of reach of that call would hand them an
1115
+ * UNCONSTRAINED anchor and silently drop every restriction the list stated. */
1116
+ if (constraints) guard.verdict.set(entry, "constraints", constraints);
1117
+ _DERIVED_FROM.set(entry, {
1118
+ name: _copyName(info.certPath.taName),
1119
+ publicKey: intrinsic.bufferFrom(spki.bytes),
1120
+ algorithm: spki.algorithm.oid,
1121
+ parameters: spki.algorithm.parameters == null ? spki.algorithm.parameters : intrinsic.bufferFrom(spki.algorithm.parameters),
1122
+ purposes: null,
1123
+ distrustAfter: _copyDistrustAfter({}),
1124
+ /** @internal Its OWN copy, not the object the entry carries. The derived record is what
1125
+ * `anchor` trusts in preference to the entry, precisely because the entry is reachable: a
1126
+ * caller who edits `entry.constraints` would otherwise be editing what the anchor enforces,
1127
+ * which is the one thing this record exists to prevent for the name and the key. */
1128
+ constraints: constraints === null ? null : _copyConstraints(constraints),
1129
+ });
1130
+ return entry;
1131
+ }
1132
+
953
1133
  module.exports = {
954
1134
  parseCertdata: parseCertdata,
955
1135
  parseCcadbCsv: parseCcadbCsv,
1136
+ parseTrustAnchorList: parseTrustAnchorList,
956
1137
  anchor: anchor,
957
1138
  };
@@ -0,0 +1,278 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ // Copyright (c) blamejs contributors
3
+ "use strict";
4
+ /**
5
+ * @module pki.trustanchor
6
+ * @nav Trust
7
+ * @title Trust anchor lists
8
+ * @order 178
9
+ * @slug trustanchor-build
10
+ *
11
+ * @intro
12
+ * Write the RFC 5914 `TrustAnchorList` a root program publishes: each anchor
13
+ * as a certificate, as a bare `TBSCertificate`, or as a `TrustAnchorInfo`
14
+ * carrying the name it is known by and the constraints a relying party
15
+ * applies to paths beneath it. Every anchor is held to the same rules
16
+ * `pki.schema.trustanchor.parse` holds one to, and the result round-trips
17
+ * through that parser before it is returned.
18
+ *
19
+ * @card
20
+ * Build RFC 5914 trust anchor lists: the anchor carries its own policies,
21
+ * name constraints and path length.
22
+ */
23
+
24
+ var asn1 = require("./asn1-der");
25
+ var schema = require("./schema-engine");
26
+ var pkix = require("./schema-pkix");
27
+ var trustanchor = require("./schema-trustanchor");
28
+ var x509 = require("./schema-x509");
29
+ var pkiBuild = require("./pki-build");
30
+ var oid = require("./oid");
31
+ var frameworkError = require("./framework-error");
32
+ var intrinsic = require("./guard-intrinsic");
33
+ var guard = require("./guard-all");
34
+
35
+ var TrustAnchorError = frameworkError.TrustAnchorError;
36
+ var b = asn1.build;
37
+
38
+ function _err(code, message, cause) { return new TrustAnchorError(code, message, cause); }
39
+ function O(n) { return oid.byName(n); }
40
+
41
+ var NS = pkix.makeNS("trustanchor", TrustAnchorError, oid);
42
+ var NAME_SCHEMA = pkix.name(NS);
43
+ var SPKI_SCHEMA = pkix.spki(NS);
44
+ var _b = pkiBuild.makeBuilder({ ErrorClass: TrustAnchorError, prefix: "trustanchor", O: O, NS: NS,
45
+ NAME_SCHEMA: NAME_SCHEMA, SPKI_SCHEMA: SPKI_SCHEMA, EXT_DECODERS: {} });
46
+
47
+ var CERT_EXT_DECODERS = pkix.certExtensionDecoders(NS).byOid;
48
+ var OID_NAME_CONSTRAINTS = oid.byName("nameConstraints");
49
+
50
+ var _ANCHOR_KEYS = intrinsic.assign(intrinsic.create(null), { certificate: 1, tbsCert: 1, taInfo: 1 });
51
+ var _INFO_KEYS = intrinsic.assign(intrinsic.create(null), {
52
+ pubKey: 1, keyId: 1, taTitle: 1, certPath: 1, exts: 1, taTitleLangTag: 1 });
53
+ var _CERT_PATH_KEYS = intrinsic.assign(intrinsic.create(null), {
54
+ taName: 1, certificate: 1, policySet: 1, policyFlags: 1, nameConstr: 1, pathLenConstraint: 1 });
55
+ var _FLAG_KEYS = intrinsic.assign(intrinsic.create(null), {
56
+ inhibitPolicyMapping: 1, requireExplicitPolicy: 1, inhibitAnyPolicy: 1 });
57
+ var _BUILD_KEYS = intrinsic.assign(intrinsic.create(null), { anchors: 1, pem: 1 });
58
+
59
+ /** @internal The shape check comes first, so every nested record reports a typed refusal naming
60
+ * the field instead of a native TypeError from the first property read below it. */
61
+ function _known(obj, allowed, what) {
62
+ guard.identifier.assertPlainRecord(obj, _err, "trustanchor/bad-input", what);
63
+ guard.identifier.assertKnownKeys(obj, allowed, _err, "trustanchor/bad-input", "unknown " + what + " field ");
64
+ }
65
+
66
+ function _policySetDer(list) {
67
+ if (!intrinsic.isArray(list) || list.length === 0) {
68
+ throw _err("trustanchor/bad-input", "certPath.policySet must be a non-empty array of policy identifiers");
69
+ }
70
+ return b.sequence(intrinsic.map(list, function (p) {
71
+ if (typeof p !== "string" || !oid.isDottedDecimal(p)) {
72
+ throw _err("trustanchor/bad-input", "certPath.policySet entries must be dotted-decimal policy identifiers");
73
+ }
74
+ /** @internal RFC 5914 sec. 2 forbids the qualifiers, so there is no place to put one and a
75
+ * caller cannot supply one: the element is the identifier alone. */
76
+ return b.sequence([b.oid(p)]);
77
+ }));
78
+ }
79
+
80
+ var FLAG_BITS = trustanchor.policyFlagBits;
81
+ function _policyFlagsDer(flags) {
82
+ _known(flags, _FLAG_KEYS, "certPath.policyFlags");
83
+ var bits = [];
84
+ for (var i = 0; i < FLAG_BITS.length; i++) {
85
+ var v = flags[FLAG_BITS[i]];
86
+ if (v === undefined || v === false) continue;
87
+ if (v !== true) throw _err("trustanchor/bad-input", "certPath.policyFlags." + FLAG_BITS[i] + " must be a boolean");
88
+ guard.list.append(bits, i);
89
+ }
90
+ if (bits.length === 0) return null;
91
+ return b.implicit(2, b.namedBitString(bits));
92
+ }
93
+
94
+ function _certPathDer(spec) {
95
+ _known(spec, _CERT_PATH_KEYS, "certPath");
96
+ var nameDer = _b.encodeName(spec.taName);
97
+ if (_b.isEmptyName(nameDer)) {
98
+ throw _err("trustanchor/bad-input", "certPath.taName must be a non-empty distinguished name (RFC 5914 sec. 2)");
99
+ }
100
+ var kids = [b.raw(nameDer)];
101
+ /** @internal The RFC 5914 module is IMPLICIT TAGS, so a context tag REPLACES the member's own
102
+ * universal tag rather than wrapping it. `b.implicit` re-tags the value it is given; only the
103
+ * three members the module writes EXPLICIT beside get a wrapper. */
104
+ if (spec.certificate !== undefined) {
105
+ guard.list.append(kids, b.implicit(0, _b.reqDer(spec.certificate, "certPath.certificate")));
106
+ }
107
+ var hasPolicySet = spec.policySet !== undefined;
108
+ if (hasPolicySet) guard.list.append(kids, b.implicit(1, _policySetDer(spec.policySet)));
109
+ if (spec.policyFlags !== undefined) {
110
+ if (!spec.policyFlags || typeof spec.policyFlags !== "object") {
111
+ throw _err("trustanchor/bad-input", "certPath.policyFlags must be an object of boolean flags");
112
+ }
113
+ /** @internal RFC 5914 sec. 2: "This bit MUST be set to FALSE if policySet is absent." Refused
114
+ * here as well as at the parse, so the builder cannot emit what the parser would reject. */
115
+ if (spec.policyFlags.requireExplicitPolicy === true && !hasPolicySet) {
116
+ throw _err("trustanchor/bad-input", "certPath.policyFlags.requireExplicitPolicy requires a policySet (RFC 5914 sec. 2)");
117
+ }
118
+ var flagsDer = _policyFlagsDer(spec.policyFlags);
119
+ if (flagsDer !== null) guard.list.append(kids,flagsDer);
120
+ }
121
+ if (spec.nameConstr !== undefined) {
122
+ var ncDer = _b.reqDer(spec.nameConstr, "certPath.nameConstr");
123
+ try { CERT_EXT_DECODERS[OID_NAME_CONSTRAINTS](ncDer); }
124
+ catch (e) { throw _err("trustanchor/bad-input", "certPath.nameConstr is not a well-formed NameConstraints (RFC 5280 sec. 4.2.1.10)", e); }
125
+ guard.list.append(kids, b.implicit(3, ncDer));
126
+ }
127
+ if (spec.pathLenConstraint !== undefined) {
128
+ var n = spec.pathLenConstraint;
129
+ if (!intrinsic.isInteger(n) || n < 0) {
130
+ throw _err("trustanchor/bad-input", "certPath.pathLenConstraint must be a non-negative integer (INTEGER (0..MAX), RFC 5914 sec. 2)");
131
+ }
132
+ guard.list.append(kids,b.implicit(4, b.integer(intrinsic.BigInt(n))));
133
+ }
134
+ return b.sequence(kids);
135
+ }
136
+
137
+ function _taInfoDer(spec) {
138
+ _known(spec, _INFO_KEYS, "taInfo");
139
+ /** @internal The version is the DEFAULT v1 and DER omits it, so it is not a field a caller sets
140
+ * and there is exactly one encoding of a v1 anchor. */
141
+ var spki = _b.reqDer(spec.pubKey, "taInfo.pubKey");
142
+ _b.assertValidSpki(spki, "taInfo.pubKey");
143
+ var keyId = guard.bytes.snapshotSource(spec.keyId, TrustAnchorError, "trustanchor/bad-input", "taInfo.keyId");
144
+ if (keyId.length === 0) throw _err("trustanchor/bad-input", "taInfo.keyId must not be empty");
145
+ var kids = [b.raw(spki), b.octetString(keyId)];
146
+ if (spec.taTitle !== undefined) {
147
+ /** @internal Counted the way the reader counts, in characters rather than UTF-16 code units,
148
+ * so the writer admits exactly the titles the reader does. */
149
+ if (typeof spec.taTitle !== "string") {
150
+ throw _err("trustanchor/bad-input", "taInfo.taTitle must be a string");
151
+ }
152
+ var titleLen = trustanchor.titleChars(spec.taTitle);
153
+ if (titleLen < 1 || titleLen > 64) {
154
+ throw _err("trustanchor/bad-input", "taInfo.taTitle must be 1 to 64 characters (SIZE (1..64), RFC 5914 sec. 2), got " + titleLen);
155
+ }
156
+ guard.list.append(kids,b.utf8(spec.taTitle));
157
+ }
158
+ if (spec.certPath !== undefined) guard.list.append(kids,_certPathDer(spec.certPath));
159
+ if (spec.exts !== undefined) {
160
+ if (!intrinsic.isArray(spec.exts) || spec.exts.length === 0) {
161
+ throw _err("trustanchor/bad-input", "taInfo.exts must be a non-empty array of pre-encoded Extension DER");
162
+ }
163
+ var extDers = intrinsic.map(spec.exts, function (e, i) {
164
+ var der = _b.reqDer(e, "taInfo.exts[" + i + "]");
165
+ var extnId;
166
+ try { extnId = asn1.read.oid(asn1.decode(der).children[0]); }
167
+ catch (err) { throw _err("trustanchor/bad-input", "taInfo.exts[" + i + "] is not a well-formed Extension", err); }
168
+ /** @internal RFC 5914 sec. 3 names four extension types that MUST NOT appear here. The parser
169
+ * ignores one that does, as the RFC says to; the builder refuses to write one, so this
170
+ * toolkit never produces an anchor carrying a constraint nothing will read. */
171
+ if (trustanchor.excludedExtensionOids[extnId] === true) {
172
+ throw _err("trustanchor/bad-input", "taInfo.exts must not carry " + (oid.name(extnId) || extnId) +
173
+ ", which RFC 5914 sec. 3 excludes and a reader ignores; state it in certPath instead");
174
+ }
175
+ return b.raw(der);
176
+ });
177
+ guard.list.append(kids,b.explicit(1, b.sequence(extDers)));
178
+ }
179
+ if (spec.taTitleLangTag !== undefined) {
180
+ /** @internal The type carries no SIZE constraint, so the reader accepts an empty language tag
181
+ * and this refuses to write one: an empty tag names no language, and the field exists to say
182
+ * which language the title is in. The docstring states it, since the module does not. */
183
+ if (typeof spec.taTitleLangTag !== "string" || spec.taTitleLangTag.length === 0) {
184
+ throw _err("trustanchor/bad-input", "taInfo.taTitleLangTag must be a non-empty string; an empty one names no language");
185
+ }
186
+ /** @internal `[2] UTF8String` with no EXPLICIT beside it in the module, so the context tag
187
+ * replaces the UTF8String tag rather than wrapping it. */
188
+ guard.list.append(kids, b.implicit(2, b.utf8(spec.taTitleLangTag)));
189
+ }
190
+ return b.sequence(kids);
191
+ }
192
+
193
+ function _anchorDer(spec, i) {
194
+ if (!spec || typeof spec !== "object" || intrinsic.isArray(spec)) {
195
+ throw _err("trustanchor/bad-input", "anchors[" + i + "] must be an object naming exactly one of certificate, tbsCert or taInfo");
196
+ }
197
+ _known(spec, _ANCHOR_KEYS, "anchors[" + i + "]");
198
+ var named = [];
199
+ intrinsic.forEach(["certificate", "tbsCert", "taInfo"], function (k) {
200
+ if (spec[k] !== undefined) guard.list.append(named, k);
201
+ });
202
+ if (named.length !== 1) {
203
+ throw _err("trustanchor/bad-input", "anchors[" + i + "] must name exactly one of certificate, tbsCert or taInfo, got " +
204
+ (named.length === 0 ? "none" : intrinsic.join(named, " and ")));
205
+ }
206
+ if (named[0] === "certificate") return b.raw(_b.reqDer(spec.certificate, "anchors[" + i + "].certificate"));
207
+ if (named[0] === "tbsCert") return b.explicit(1, b.raw(_b.reqDer(spec.tbsCert, "anchors[" + i + "].tbsCert")));
208
+ return b.explicit(2, _taInfoDer(spec.taInfo));
209
+ }
210
+
211
+ /**
212
+ * @primitive pki.trustanchor.build
213
+ * @signature pki.trustanchor.build(spec, opts?) -> Buffer | string
214
+ * @since 0.8.13
215
+ * @status stable
216
+ * @spec RFC 5914
217
+ * @related pki.schema.trustanchor.parse, pki.trust.parseTrustAnchorList
218
+ *
219
+ * Build an RFC 5914 `TrustAnchorList`, the structure a root program publishes. `spec.anchors` is a
220
+ * non-empty array, each entry naming exactly one of `certificate` (pre-encoded Certificate DER),
221
+ * `tbsCert` (pre-encoded TBSCertificate DER) or `taInfo`.
222
+ *
223
+ * A `taInfo` entry is `{ pubKey, keyId, taTitle?, certPath?, exts?, taTitleLangTag? }`, where
224
+ * `pubKey` is SubjectPublicKeyInfo DER and `keyId` the key identifier bytes. `certPath` carries the
225
+ * constraints: `taName` (a distinguished name string or an array of relative names), `certificate`,
226
+ * `policySet` (an array of policy identifiers), `policyFlags`
227
+ * (`{ inhibitPolicyMapping, requireExplicitPolicy, inhibitAnyPolicy }`), `nameConstr` (pre-encoded
228
+ * NameConstraints DER) and `pathLenConstraint`.
229
+ *
230
+ * Every rule the parser enforces is enforced here, so this toolkit does not emit an anchor it would
231
+ * refuse to read: an empty `taName`, a `requireExplicitPolicy` with no `policySet`, a negative
232
+ * `pathLenConstraint`, and an `exts` entry naming one of the four extension types RFC 5914
233
+ * section 3 excludes. The result round-trips through `pki.schema.trustanchor.parse` before it is
234
+ * returned.
235
+ *
236
+ * The version is not a field a caller sets: v1 is the ASN.1 DEFAULT and DER omits it, so every v1
237
+ * anchor has one encoding.
238
+ *
239
+ * Two refusals are stricter than the module, on the principle that what this
240
+ * writes is what an operator publishes: `taTitleLangTag` may not be the empty string, which
241
+ * names no language, and `exts` may not carry one of the four extension types RFC 5914 section 3
242
+ * excludes, which a reader is told to ignore and which `certPath` states properly.
243
+ *
244
+ * @opts
245
+ * - `pem` (boolean) -- return a PEM `TRUST ANCHOR LIST` string instead of DER.
246
+ * @example
247
+ * var pair = await pki.key.generate("Ed25519");
248
+ * var der = pki.trustanchor.build({ anchors: [{ taInfo: {
249
+ * pubKey: await pki.key.export(pair.publicKey),
250
+ * keyId: Buffer.alloc(20, 1),
251
+ * taTitle: "Example Root CA",
252
+ * certPath: { taName: "CN=Example Root", pathLenConstraint: 2 } } }] });
253
+ * pki.schema.trustanchor.parse(der).anchors[0].taInfo.certPath.pathLenConstraint; // -> 2
254
+ */
255
+ function build(spec, opts) {
256
+ if (!spec || typeof spec !== "object" || intrinsic.isArray(spec)) {
257
+ throw _err("trustanchor/bad-input", "build expects a { anchors } specification object");
258
+ }
259
+ _known(spec, _BUILD_KEYS, "build");
260
+ var checked = guard.identifier.optionsObject(opts, _err, "trustanchor/bad-input", "pki.trustanchor.build options");
261
+ guard.identifier.assertKnownKeys(checked, intrinsic.assign(intrinsic.create(null), { pem: 1 }),
262
+ _err, "trustanchor/bad-input", "unknown pki.trustanchor.build option ");
263
+ if (!intrinsic.isArray(spec.anchors) || spec.anchors.length === 0) {
264
+ throw _err("trustanchor/bad-input", "build: anchors must be a non-empty array (TrustAnchorList is SIZE (1..MAX))");
265
+ }
266
+ var der = b.sequence(intrinsic.map(spec.anchors, function (a, i) { return _anchorDer(a, i); }));
267
+ /** @internal The reader reads the writer's output back before it is returned, so a shape the two
268
+ * disagree about fails the build instead of reaching an operator. */
269
+ try { trustanchor.parse(der); }
270
+ catch (e) { throw _err("trustanchor/bad-input", "the built trust anchor list does not parse: " + guard.text.describeThrown(e), e); }
271
+ if (checked.pem === true) return trustanchor.pemEncode(der);
272
+ return der;
273
+ }
274
+
275
+ void schema;
276
+ void x509;
277
+
278
+ module.exports = { build: build };
@@ -122,7 +122,7 @@ function normalizeObjectAttributePolicy(policy, E, code) {
122
122
  var explicit = policy.objectAttributes;
123
123
  if (explicit !== undefined) {
124
124
  if (!explicit || typeof explicit !== "object" || Array.isArray(explicit)) throw new E(code, "opts.tpmPolicy.objectAttributes must be an object");
125
- Object.keys(explicit).forEach(function (n) {
125
+ guard.identifier.optionNames(explicit).forEach(function (n) {
126
126
  if (TPMA_OBJECT_BITS[n] === undefined) throw new E(code, "opts.tpmPolicy.objectAttributes has an unknown attribute " + JSON.stringify(n));
127
127
  if (typeof explicit[n] !== "boolean") throw new E(code, "opts.tpmPolicy.objectAttributes." + n + " must be a boolean");
128
128
  want[n] = explicit[n];
@@ -440,7 +440,7 @@ function metadataAnchors(entry, opts) {
440
440
  opts = opts || {};
441
441
  if (typeof opts !== "object" || Array.isArray(opts)) throw _err("webauthn/bad-input", "metadataAnchors opts must be an object");
442
442
  guard.identifier.assertKnownKeys(opts, _ANCHOR_OPTS, _err, "webauthn/bad-input", "metadataAnchors opts has an unknown key ");
443
- opts = Object.assign({}, opts);
443
+ opts = guard.identifier.ownOptions(opts);
444
444
  if (opts.time !== undefined) guard.time.assertValid(opts.time, _err, "webauthn/bad-input", "opts.time");
445
445
  if (opts.metadata !== undefined) {
446
446
  if (!isVerifiedResult(opts.metadata)) {
package/lib/webauthn.js CHANGED
@@ -906,7 +906,7 @@ function verify(attestationObject, clientDataHash, opts) {
906
906
  try {
907
907
  if (!_isPlainObject(opts)) throw _err("webauthn/bad-input", "opts must be an object");
908
908
  guard.identifier.assertKnownKeys(opts, _VERIFY_OPTS, _err, "webauthn/bad-input", "opts has an unknown key ");
909
- opts = Object.assign({}, opts);
909
+ opts = guard.identifier.ownOptions(opts);
910
910
  if (opts.time !== undefined) guard.time.assertValid(opts.time, _err, "webauthn/bad-input", "opts.time");
911
911
  _FORMAT_SCOPED_BOOLEAN_OPTS.forEach(function (k) {
912
912
  if (opts[k] !== undefined && typeof opts[k] !== "boolean") throw _err("webauthn/bad-input", "opts." + k + " must be a boolean");
@@ -957,10 +957,9 @@ function verify(attestationObject, clientDataHash, opts) {
957
957
  var bindingChecked, vopts;
958
958
  try {
959
959
  bindingChecked = _applyBindings(att.authData, att.authData.credentialPublicKey, opts);
960
- vopts = Object.assign({}, opts, {
961
- rootCertificates: _snapshotRoots(opts.rootCertificates),
962
- safetyNetRoots: _snapshotRoots(opts.safetyNetRoots),
963
- });
960
+ vopts = guard.identifier.ownOptions(opts);
961
+ vopts.rootCertificates = _snapshotRoots(opts.rootCertificates);
962
+ vopts.safetyNetRoots = _snapshotRoots(opts.safetyNetRoots);
964
963
  } catch (e) { return Promise.reject(e); }
965
964
  return Promise.resolve().then(function () { return verifier(att, clientDataHash, vopts); })
966
965
  .then(function (res) {
@@ -1478,8 +1477,7 @@ var _ASSERT_OPTS = Object.assign(Object.create(null), {
1478
1477
  function _snapshotAssertion(input) {
1479
1478
  if (!_isPlainObject(input)) throw _err("webauthn/bad-input", "pki.webauthn.verifyAssertion takes an options object");
1480
1479
  guard.identifier.assertKnownKeys(input, _ASSERT_OPTS, _err, "webauthn/bad-input", "verifyAssertion input has an unknown key ");
1481
- var out = intrinsic.create(null), k;
1482
- for (k in input) { if (_hasOwn(input, k)) out[k] = input[k]; }
1480
+ var out = guard.identifier.ownOptions(input);
1483
1481
  ["authenticatorData", "clientDataJSON", "clientDataHash", "signature", "expectedChallenge"].forEach(function (f) {
1484
1482
  if (_isBufferSource(out[f])) out[f] = guard.bytes.snapshotSource(out[f], WebauthnError, "webauthn/bad-input", f);
1485
1483
  });
@@ -1487,9 +1485,10 @@ function _snapshotAssertion(input) {
1487
1485
  out.credentialPublicKey = guard.bytes.snapshotSource(out.credentialPublicKey, WebauthnError,
1488
1486
  "webauthn/bad-input", "credentialPublicKey");
1489
1487
  } else if (_isPlainObject(out.credentialPublicKey)) {
1490
- var key = intrinsic.create(null), kk;
1491
- for (kk in out.credentialPublicKey) {
1492
- if (!_hasOwn(out.credentialPublicKey, kk)) continue;
1488
+ var key = intrinsic.create(null);
1489
+ var coseNames = guard.identifier.optionNames(out.credentialPublicKey);
1490
+ for (var ci = 0; ci < coseNames.length; ci++) {
1491
+ var kk = coseNames[ci];
1493
1492
  var v;
1494
1493
  try { v = out.credentialPublicKey[kk]; }
1495
1494
  catch (e) { throw _err("webauthn/bad-cose-key", "credentialPublicKey." + kk + " could not be read", e); }
package/lib/webcrypto.js CHANGED
@@ -116,8 +116,7 @@ function _normalizeAlg(algorithm, who) {
116
116
  if (!alg || typeof alg.name !== "string") {
117
117
  throw new WebCryptoError("webcrypto/syntax", (who || "operation") + ": algorithm must be a string or an object with a name");
118
118
  }
119
- var out = intrinsic.create(null);
120
- for (var k in alg) { if (_hasOwn(alg, k)) out[k] = alg[k]; }
119
+ var out = guard.identifier.ownOptions(alg);
121
120
  out.name = _toUpperCase(alg.name);
122
121
  return out;
123
122
  }
@@ -1030,7 +1029,8 @@ function _algFromImport(name, alg, keyObject) {
1030
1029
  function _curveFromKey(ko) {
1031
1030
  try {
1032
1031
  var jwk = ko.export({ format: "jwk" });
1033
- for (var k in CURVE_NODE) { if (jwk.crv === k) return k; }
1032
+ var curves = guard.identifier.optionNames(CURVE_NODE);
1033
+ for (var k = 0; k < curves.length; k++) { if (jwk.crv === curves[k]) return curves[k]; }
1034
1034
  }
1035
1035
  catch (_e) { }
1036
1036
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.8.11",
3
+ "version": "0.8.14",
4
4
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",