@blamejs/pki 0.1.22 → 0.1.23

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/schema-csr.js CHANGED
@@ -21,11 +21,11 @@
21
21
  * A CSR is self-signed only in the sense that the requester proves possession of
22
22
  * the private key; unlike a certificate or CRL there is no inner signature
23
23
  * algorithm to agree with, the subject MAY be empty, and the attribute set has
24
- * no uniqueness constraint the parser deliberately omits those three
24
+ * no uniqueness constraint -- the parser deliberately omits those three
25
25
  * certificate/CRL guards.
26
26
  *
27
27
  * @card
28
- * Parse DER / PEM PKCS#10 CSRs into structured, validated fields subject DN,
28
+ * Parse DER / PEM PKCS#10 CSRs into structured, validated fields -- subject DN,
29
29
  * public key, requested attributes, signature, fail-closed.
30
30
  */
31
31
 
@@ -45,23 +45,59 @@ var NS = pkix.makeNS("csr", CsrError, oid);
45
45
  var NAME = pkix.name(NS);
46
46
  var SPKI = pkix.spki(NS);
47
47
 
48
- // CertificationRequestInfo version ::= INTEGER { v1(0) } (RFC 2986 §4.1). A BARE
49
- // mandatory INTEGER whose ONLY legal value is 0 the inverse of the cert/CRL
48
+ // CertificationRequestInfo version ::= INTEGER { v1(0) } (RFC 2986 sec. 4.1). A BARE
49
+ // mandatory INTEGER whose ONLY legal value is 0 -- the inverse of the cert/CRL
50
50
  // readers, which reject 0 as the DER-forbidden default. Surface v1 as 1. A
51
51
  // cert-shaped [0] EXPLICIT wrapper at this position makes asn1.read.integer throw
52
52
  // asn1/* (fail-closed).
53
53
  var CSR_VERSION = pkix.versionReader(NS, { "0": 1 });
54
54
 
55
- // Attribute ::= SEQUENCE { type OID, values SET OF ANY } the shared pkix factory
55
+ // Attribute ::= SEQUENCE { type OID, values SET OF ANY } -- the shared pkix factory
56
56
  // under the csr NS (values raw DER, SET SIZE(1..MAX), no uniqueness). The same
57
57
  // factory serves the PKCS#8 private-key attributes.
58
58
  var ATTRIBUTE = pkix.attribute(NS);
59
59
 
60
+ // The two RECOGNIZED PKCS#9 request attributes get their value syntax enforced
61
+ // (RFC 2985 sec. 5.4.1 challengePassword / sec. 5.4.2 extensionRequest): both
62
+ // are SINGLE VALUE TRUE, extensionRequest carries one Extensions value
63
+ // (RFC 5280 sec. 4.1, SIZE(1..MAX)) and challengePassword one DirectoryString
64
+ // of 1..255 characters. A recognized type with a malformed, multi-valued, or
65
+ // wrong-syntax value fails closed (csr/bad-attribute-value) -- a CA pipeline
66
+ // reading requested extensions off csr.attributes must never receive garbage
67
+ // under the extensionRequest OID. An UNRECOGNIZED attribute type stays opaque
68
+ // raw DER and never fails the parse, and duplicate attribute TYPES stay legal
69
+ // (RFC 2986 puts no uniqueness on the attributes SET). Registry rows keyed by
70
+ // the stable dotted OID, not a switch; values are still surfaced raw.
71
+ var EXTENSIONS = pkix.extensions(NS);
72
+ var DIRECTORY_STRING_TAGS = [
73
+ asn1.TAGS.UTF8_STRING, asn1.TAGS.PRINTABLE_STRING, asn1.TAGS.TELETEX_STRING,
74
+ asn1.TAGS.UNIVERSAL_STRING, asn1.TAGS.BMP_STRING,
75
+ ];
76
+ var RECOGNIZED_ATTRIBUTE_VALUE = {};
77
+ RECOGNIZED_ATTRIBUTE_VALUE[oid.byName("extensionRequest")] = function (node, ctx) {
78
+ try { schema.walk(EXTENSIONS, node, ctx); }
79
+ catch (e) {
80
+ throw ctx.E("csr/bad-attribute-value",
81
+ "extensionRequest value must be a well-formed Extensions SEQUENCE (RFC 2985 sec. 5.4.2): " + ((e && e.message) || String(e)), e);
82
+ }
83
+ };
84
+ RECOGNIZED_ATTRIBUTE_VALUE[oid.byName("challengePassword")] = function (node, ctx) {
85
+ if (node.tagClass !== "universal" || DIRECTORY_STRING_TAGS.indexOf(node.tagNumber) === -1) {
86
+ throw ctx.E("csr/bad-attribute-value", "challengePassword must be a DirectoryString (RFC 2985 sec. 5.4.1)");
87
+ }
88
+ var s;
89
+ try { s = asn1.read.string(node); }
90
+ catch (e) { throw ctx.E("csr/bad-attribute-value", "challengePassword must be a well-formed DirectoryString (RFC 2985 sec. 5.4.1)", e); }
91
+ if (s.length < 1 || s.length > 255) {
92
+ throw ctx.E("csr/bad-attribute-value", "challengePassword must be 1..255 characters (RFC 2985 sec. 5.4.1)");
93
+ }
94
+ };
95
+
60
96
  // CertificationRequestInfo ::= SEQUENCE { version, subject Name,
61
97
  // subjectPKInfo SubjectPublicKeyInfo, attributes [0] IMPLICIT SET OF Attribute }.
62
98
  // attributes is a REQUIRED field (a CRI omitting [0] is a missing-required-field
63
99
  // fault, not silently accepted) modeled as an IMPLICIT [0] SET OF with min:0 (an
64
- // empty attributes SET is legal). subject MAY be empty no non-empty-DN guard.
100
+ // empty attributes SET is legal). subject MAY be empty -- no non-empty-DN guard.
65
101
  var CERTIFICATION_REQUEST_INFO = schema.seq([
66
102
  schema.field("version", CSR_VERSION),
67
103
  schema.field("subject", NAME),
@@ -69,21 +105,34 @@ var CERTIFICATION_REQUEST_INFO = schema.seq([
69
105
  schema.field("attributes", schema.implicitSetOf(0, ATTRIBUTE, { min: 0, code: "csr/bad-attributes", what: "attributes" })),
70
106
  ], {
71
107
  assert: "sequence", code: "csr/bad-cri", what: "certificationRequestInfo",
72
- build: function (m) {
108
+ build: function (m, ctx) {
109
+ var attributes = m.fields.attributes.value.items.map(function (it) {
110
+ var a = it.value.result;
111
+ var checkValue = RECOGNIZED_ATTRIBUTE_VALUE[a.type];
112
+ if (checkValue) {
113
+ var valueItems = it.value.fields.values.value.items;
114
+ // Both recognized PKCS#9 types are SINGLE VALUE TRUE (RFC 2985 sec. 5.4).
115
+ if (valueItems.length !== 1) {
116
+ throw ctx.E("csr/bad-attribute-value", (a.name || a.type) + " is a SINGLE VALUE attribute (RFC 2985 sec. 5.4)");
117
+ }
118
+ checkValue(valueItems[0].node, ctx);
119
+ }
120
+ return a;
121
+ });
73
122
  return {
74
123
  version: m.fields.version.value,
75
- subject: m.fields.subject.value.result, // Name is a seqOf field.value is the match; .result is the {rdns, dn} build
124
+ subject: m.fields.subject.value.result, // Name is a seqOf -> field.value is the match; .result is the {rdns, dn} build
76
125
  subjectPublicKeyInfo: m.fields.subjectPKInfo.value.result,
77
- attributes: m.fields.attributes.value.items.map(function (it) { return it.value.result; }),
126
+ attributes: attributes,
78
127
  };
79
128
  },
80
129
  });
81
130
 
82
131
  // CertificationRequest ::= SEQUENCE { certificationRequestInfo, signatureAlgorithm,
83
- // signature BIT STRING } the shared SIGNED envelope. DIVERGENCE from the
132
+ // signature BIT STRING } -- the shared SIGNED envelope. DIVERGENCE from the
84
133
  // cert/CRL builders: OMIT the outer-vs-inner signatureAlgorithm agreement check
85
134
  // (the CRI has no inner signature AlgorithmIdentifier) and the non-empty-subject
86
- // guard (a CSR subject MAY be empty). The omission is structural this build
135
+ // guard (a CSR subject MAY be empty). The omission is structural -- this build
87
136
  // simply never references the agreement bytes.
88
137
  var CERTIFICATION_REQUEST = pkix.signedEnvelope(NS, CERTIFICATION_REQUEST_INFO, {
89
138
  code: "csr/not-a-certification-request", what: "CertificationRequest",
@@ -120,8 +169,8 @@ var CERTIFICATION_REQUEST = pkix.signedEnvelope(NS, CERTIFICATION_REQUEST_INFO,
120
169
  *
121
170
  * @example
122
171
  * var csr = pki.schema.csr.parse(der);
123
- * csr.subject.dn; // "CN=req.example"
124
- * csr.attributes[0].type; // "1.2.840.113549.1.9.14"
172
+ * csr.subject.dn; // -> "CN=req.example"
173
+ * csr.attributes[0].type; // -> "1.2.840.113549.1.9.14"
125
174
  */
126
175
  var parse = pkix.makeParser({ pemLabel: "CERTIFICATE REQUEST", PemError: PemError, ErrorClass: CsrError, prefix: "csr", what: "certification request", topSchema: CERTIFICATION_REQUEST, ns: NS });
127
176
 
@@ -158,7 +207,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || "CERTIFIC
158
207
  function pemEncode(der, label) { return pkix.pemEncode(der, label || "CERTIFICATE REQUEST", PemError); }
159
208
 
160
209
  // A CertificationRequest shares the outer SEQUENCE-of-3 shape with a certificate
161
- // and a CRL; a CSR is distinguished by its CertificationRequestInfo a SEQUENCE
210
+ // and a CRL; a CSR is distinguished by its CertificationRequestInfo -- a SEQUENCE
162
211
  // of EXACTLY four children {version INTEGER, subject SEQUENCE, subjectPKInfo
163
212
  // SEQUENCE, attributes [0]}. The mandatory trailing context-[0] attributes
164
213
  // element is what a cert (whose tbs leads with [0] EXPLICIT version or an INTEGER
@@ -8,7 +8,7 @@
8
8
  * @order 60
9
9
  *
10
10
  * @intro
11
- * L2 of the ASN.1 stack a declarative structure-schema engine. A schema is
11
+ * L2 of the ASN.1 stack -- a declarative structure-schema engine. A schema is
12
12
  * plain data (`{kind, ...}` descriptors built by the combinators here) and
13
13
  * `walk(schema, node, ctx)` interprets it against a decoded DER node. The
14
14
  * engine is where every cross-cutting structural rule lives ONCE: the shape
@@ -21,7 +21,7 @@
21
21
  * base the certificate parser (and, later, CRL / CMS) composes.
22
22
  *
23
23
  * @card
24
- * Declarative ASN.1 structure schemas + one walk engine the shared base the
24
+ * Declarative ASN.1 structure schemas + one walk engine -- the shared base the
25
25
  * certificate / CRL / CMS parsers compose instead of hand-writing.
26
26
  */
27
27
 
@@ -85,6 +85,9 @@ function _assertArity(schema, kids, ctx) {
85
85
  if (a.min != null && kids.length < a.min) {
86
86
  _fail(ctx, schema.code, (schema.what || "value") + " must have at least " + a.min + " elements");
87
87
  }
88
+ if (a.max != null && kids.length > a.max) {
89
+ _fail(ctx, schema.code, (schema.what || "value") + " must have at most " + a.max + " elements");
90
+ }
88
91
  }
89
92
 
90
93
  // ---- leaf / value combinators ---------------------------------------
@@ -102,23 +105,26 @@ function integerLeaf() { return { kind: "leaf", read: asn1.read.integer, writ
102
105
  function boolean() { return { kind: "leaf", read: asn1.read.boolean, write: function (v) { return asn1.build.boolean(v); } }; }
103
106
  function octetString() { return { kind: "leaf", read: asn1.read.octetString, write: function (v) { return asn1.build.octetString(v); } }; }
104
107
  function bitString() { return { kind: "leaf", read: function (n) { var b = asn1.read.bitString(n); return { unusedBits: b.unusedBits, bytes: b.bytes }; }, write: function (v) { return asn1.build.bitString(v.bytes, v.unusedBits); } }; }
105
- // A [tag] IMPLICIT BIT STRING leaf (context-class primitive) the primitive-leaf
106
- // counterpart to implicitSetOf, for e.g. the PKCS#8 publicKey [1] (RFC 5958 §2).
108
+ // A [tag] IMPLICIT BIT STRING leaf (context-class primitive) -- the primitive-leaf
109
+ // counterpart to implicitSetOf, for e.g. the PKCS#8 publicKey [1] (RFC 5958 sec. 2).
107
110
  function implicitBitString(tag) { return { kind: "leaf", read: function (n) { var b = asn1.read.bitStringImplicit(n, tag); return { unusedBits: b.unusedBits, bytes: b.bytes }; }, write: function (v) { return asn1.build.contextPrimitive(tag, Buffer.concat([Buffer.from([v.unusedBits]), v.bytes])); } }; }
108
- // A [tag] IMPLICIT OCTET STRING leaf (context-class primitive) the sibling of
111
+ // A [tag] IMPLICIT OCTET STRING leaf (context-class primitive) -- the sibling of
109
112
  // implicitBitString, for e.g. the CMS SignerIdentifier subjectKeyIdentifier [0]
110
- // (RFC 5652 §5.3). Yields the raw content bytes.
113
+ // (RFC 5652 sec. 5.3). Yields the raw content bytes.
111
114
  function implicitOctetString(tag) { return { kind: "leaf", read: function (n) { return asn1.read.octetStringImplicit(n, tag); }, write: function (v) { return asn1.build.contextPrimitive(tag, v); } }; }
112
- // A [tag] IMPLICIT NULL leaf (context-class primitive, empty content) the sibling
115
+ // A [tag] IMPLICIT NULL leaf (context-class primitive, empty content) -- the sibling
113
116
  // of implicitBitString/implicitOctetString, for e.g. the OCSP CertStatus good [0] /
114
- // unknown [2] arms (RFC 6960 §4.2.1). Yields null; rejects a constructed or
117
+ // unknown [2] arms (RFC 6960 sec. 4.2.1). Yields null; rejects a constructed or
115
118
  // non-empty [tag] node fail-closed.
116
119
  function implicitNull(tag) { return { kind: "leaf", read: function (n) { return asn1.read.nullImplicit(n, tag); }, write: function () { return asn1.build.contextPrimitive(tag, Buffer.alloc(0)); } }; }
117
- // A [tag] IMPLICIT INTEGER leaf (context-class primitive) the integer sibling of
120
+ // A [tag] IMPLICIT INTEGER leaf (context-class primitive) -- the integer sibling of
118
121
  // implicitBitString, for the RFC 3161 Accuracy millis [0] / micros [1] fields
119
122
  // (context-tagged primitive INTEGERs). Yields a BigInt; a constructed or wrong-tag
120
123
  // node fails asn1/* at the codec.
121
124
  function implicitInteger(tag) { return { kind: "leaf", read: function (n) { return asn1.read.integerImplicit(n, tag); }, write: function (v) { return asn1.build.contextPrimitive(tag, _implicitIntContent(v)); } }; }
125
+ // implicitBoolean, for the RFC 5280 sec. 5.2.5 IssuingDistributionPoint scope flags
126
+ // (context-tagged primitive BOOLEANs). DER value rules enforced (0x00/0xFF only).
127
+ function implicitBoolean(tag) { return { kind: "leaf", read: function (n) { return asn1.read.booleanImplicit(n, tag); }, write: function (v) { return asn1.build.contextPrimitive(tag, Buffer.from([v ? 0xff : 0x00])); } }; }
122
128
  function any() { return { kind: "any" }; }
123
129
  // decode(fn, write?): a custom decode leaf; `write` is its optional paired encoder
124
130
  // (value -> DER TLV) so the same leaf drives both directions.
@@ -126,7 +132,7 @@ function decode(fn, write) { return { kind: "decode", fn: fn, write: write }; }
126
132
 
127
133
  // time(ns): a UTCTime / GeneralizedTime value, asserting the tag before the
128
134
  // codec reads it (mirrors _parseValidityTime's x509/bad-time guard). Encodes as
129
- // UTCTime for years 1950..2049 and GeneralizedTime otherwise (RFC 5280 §4.1.2.5).
135
+ // UTCTime for years 1950..2049 and GeneralizedTime otherwise (RFC 5280 sec. 4.1.2.5).
130
136
  function time(ns) {
131
137
  return decode(function (n, ctx) {
132
138
  if (n.tagClass !== "universal" || (n.tagNumber !== TAGS.UTC_TIME && n.tagNumber !== TAGS.GENERALIZED_TIME)) {
@@ -146,14 +152,14 @@ function optional(name, schema, opts) {
146
152
  opts = opts || {};
147
153
  // How the optional field is recognized at its position:
148
154
  // - default: a context [tag] (the certificate version [0] shape).
149
- // - tags: a context tag in the set an OPTIONAL field whose type is
155
+ // - tags: a context tag in the set -- an OPTIONAL field whose type is
150
156
  // itself a CHOICE of several context alternatives, e.g. the CRMF CertReqMsg
151
157
  // popo ProofOfPossession ([0]..[3]) sitting before the universal-SEQUENCE
152
- // regInfo (RFC 4211 §3), where ANY of the CHOICE's tags marks it present.
153
- // - whenUniversal: the next element iff its UNIVERSAL tag is in the set
158
+ // regInfo (RFC 4211 sec. 3), where ANY of the CHOICE's tags marks it present.
159
+ // - whenUniversal: the next element iff its UNIVERSAL tag is in the set --
154
160
  // the CRL TBSCertList shape (bare INTEGER version, Time nextUpdate,
155
161
  // SEQUENCE revokedCertificates), disambiguated by tag, not a context [n].
156
- // - whenAny: the next element whatever its tag an OPTIONAL ANY like
162
+ // - whenAny: the next element whatever its tag -- an OPTIONAL ANY like
157
163
  // AlgorithmIdentifier.parameters.
158
164
  // The recognizer lets _walkSeq CONSUME the element so a closed sequence can
159
165
  // reject whatever is left over (without it, a trailing ANY looks unconsumed).
@@ -164,8 +170,14 @@ function optional(name, schema, opts) {
164
170
  : opts.tags
165
171
  ? function (n) { return isContextOneOf(n, opts.tags); }
166
172
  : function (n) { return isContext(n, opts.tag); };
173
+ // default/defaultCode: `default` binds the DEFAULT value when the field is
174
+ // absent. `defaultCode` opts the field into the X.690 sec. 11.5 walk-side reject
175
+ // (DER: a component equal to its DEFAULT shall not be included) -- a present
176
+ // value equal to `default` (strict equality; Buffers by bytes) fails with
177
+ // that code, so each format keeps its own reject-code family.
167
178
  return { fkind: "optional", name: name, schema: schema, tag: opts.tag, match: match,
168
- explicit: !!opts.explicit, emptyCode: opts.emptyCode, hasDefault: ("default" in opts), def: opts.default };
179
+ explicit: !!opts.explicit, emptyCode: opts.emptyCode, hasDefault: ("default" in opts), def: opts.default,
180
+ defaultCode: opts.defaultCode };
169
181
  }
170
182
  // trailing: the [minTag..maxTag] optional context fields, each at most once in
171
183
  // strictly-increasing tag order (the tbs issuerUniqueID[1]/subjectUniqueID[2]/
@@ -212,16 +224,16 @@ function setOf(item, opts) {
212
224
  function setOfUnique(item, keyFn, opts) {
213
225
  return setOf(item, Object.assign({ unique: keyFn }, opts || {}));
214
226
  }
215
- // [tag] IMPLICIT SET OF item the context tag REPLACES the universal SET tag,
227
+ // [tag] IMPLICIT SET OF item -- the context tag REPLACES the universal SET tag,
216
228
  // so the node is a context-class constructed [tag] whose direct children are the
217
- // items (RFC 2986 §4.1 CSR attributes). No inner SET, no EXPLICIT unwrap.
229
+ // items (RFC 2986 sec. 4.1 CSR attributes). No inner SET, no EXPLICIT unwrap.
218
230
  function implicitSetOf(tag, item, opts) {
219
231
  opts = opts || {};
220
232
  return { kind: "repeat", item: item, assert: "implicit", implicitTag: tag, derSetOrder: true, code: opts.code, what: opts.what,
221
233
  min: opts.min, max: opts.max, maxCode: opts.maxCode,
222
234
  unique: opts.unique, dupCode: opts.dupCode, build: opts.build };
223
235
  }
224
- // [tag] IMPLICIT SEQUENCE OF item the context tag REPLACES the universal SEQUENCE
236
+ // [tag] IMPLICIT SEQUENCE OF item -- the context tag REPLACES the universal SEQUENCE
225
237
  // tag, so the node is a context-class constructed [tag] whose direct children are the
226
238
  // items. Order-preserving: the SEQUENCE-OF sibling of implicitSetOf WITHOUT the SET
227
239
  // ascending-DER-order rule (RFC 3161 extensions [1] IMPLICIT Extensions).
@@ -245,15 +257,17 @@ function implicitSeqOf(tag, item, opts) {
245
257
  * Interpret a declarative schema against a decoded DER node, enforcing the
246
258
  * schema's structural rules (shape assertion, arity, optional / context-tagged
247
259
  * fields in increasing tag order, SET-OF uniqueness) and returning the built
248
- * value or the match tree (`{ node, fields | items }`, with the build output
260
+ * value -- or the match tree (`{ node, fields | items }`, with the build output
249
261
  * on `.result`) for a structure with no build fn. `ctx = { E, prefix, oid }`
250
262
  * supplies the typed-error constructor, the error-code family prefix, and the
251
263
  * OID registry a build fn resolves names through.
252
264
  *
253
- * The schema is assembled from the combinators this module exports structural
265
+ * The schema is assembled from the combinators this module exports -- structural
254
266
  * (`seq` / `field` / `optional` / `explicit` / `trailing` / `seqOf` / `setOf` /
255
- * `setOfUnique` / `choice`) and value (`oidLeaf` / `integerLeaf` / `boolean` /
256
- * `octetString` / `bitString` / `any` / `decode` / `time`).
267
+ * `setOfUnique` / `implicitSeqOf` / `implicitSetOf` / `choice`) and value
268
+ * (`oidLeaf` / `integerLeaf` / `boolean` / `octetString` / `bitString` /
269
+ * `implicitBitString` / `implicitOctetString` / `implicitNull` /
270
+ * `implicitInteger` / `any` / `decode` / `time`).
257
271
  *
258
272
  * @example
259
273
  * var S = pki.schema.engine;
@@ -314,7 +328,7 @@ function _walkRepeat(schema, node, ctx) {
314
328
  if (schema.max != null && kids.length > schema.max) {
315
329
  _fail(ctx, schema.maxCode || schema.code, (schema.what || "value") + " exceeds the element cap " + schema.max);
316
330
  }
317
- // DER (X.690 §11.6) the components of a SET OF appear in ascending order, the
331
+ // DER (X.690 sec. 11.6) -- the components of a SET OF appear in ascending order, the
318
332
  // encodings compared as octet strings. SEQUENCE OF is order-preserving and is
319
333
  // exempt (no derSetOrder). Compare full TLV byte strings, the same canonical
320
334
  // order build.set emits, so a decode round-trips a build. Equal-adjacent
@@ -323,7 +337,7 @@ function _walkRepeat(schema, node, ctx) {
323
337
  if (schema.derSetOrder) {
324
338
  for (var s = 1; s < kids.length; s++) {
325
339
  if (Buffer.compare(kids[s - 1].bytes, kids[s].bytes) > 0) {
326
- _fail(ctx, schema.code, (schema.what || "value") + " components must be in ascending DER order (X.690 §11.6)");
340
+ _fail(ctx, schema.code, (schema.what || "value") + " components must be in ascending DER order (X.690 sec. 11.6)");
327
341
  }
328
342
  }
329
343
  }
@@ -345,6 +359,15 @@ function _walkRepeat(schema, node, ctx) {
345
359
  return match;
346
360
  }
347
361
 
362
+ // The walked-value <-> DEFAULT comparison behind the defaultCode reject: strict
363
+ // equality for the leaf value domains (BigInt, number, string, boolean), byte
364
+ // equality for Buffers. A format opting in declares its `default` in the same
365
+ // domain its leaf's read yields.
366
+ function _equalsDefault(value, def) {
367
+ if (Buffer.isBuffer(value) && Buffer.isBuffer(def)) return value.equals(def);
368
+ return value === def;
369
+ }
370
+
348
371
  function _walkSeq(schema, node, ctx) {
349
372
  var kids = _assertShape(schema, node, ctx);
350
373
  _assertArity(schema, kids, ctx);
@@ -365,18 +388,31 @@ function _walkSeq(schema, node, ctx) {
365
388
  if (fld.explicit) {
366
389
  inner = _explicitInner(next, fld.tag, ctx, fld.emptyCode || schema.code);
367
390
  }
368
- fields[fld.name] = { node: next, present: true, value: walk(fld.schema, inner, ctx) };
391
+ var val = walk(fld.schema, inner, ctx);
392
+ // X.690 sec. 11.5 -- DER: a component equal to its DEFAULT value shall not
393
+ // be included in the encoding. Opt-in via defaultCode so the reject
394
+ // stays in the format's own error-code family.
395
+ if (fld.defaultCode != null && fld.hasDefault && _equalsDefault(val, fld.def)) {
396
+ _fail(ctx, fld.defaultCode, "field " + JSON.stringify(fld.name) + " explicitly encodes its DEFAULT value (X.690 sec. 11.5)");
397
+ }
398
+ fields[fld.name] = { node: next, present: true, value: val };
369
399
  } else {
370
400
  fields[fld.name] = { present: false, value: fld.hasDefault ? fld.def : undefined };
371
401
  }
372
402
  } else if (fld.fkind === "trailing") {
373
403
  _consumeTrailing(fld, kids, idx, fields, ctx);
374
404
  idx = kids.length;
405
+ } else {
406
+ // A malformed field descriptor is the same authoring fault as an
407
+ // unknown schema.kind (walk's default case): fail loud instead of
408
+ // silently skipping the descriptor -- a skipped field never binds and
409
+ // under-specifies the sequence.
410
+ _fail(ctx, (ctx.prefix || "schema") + "/bad-schema", "unknown field kind " + JSON.stringify(fld && fld.fkind));
375
411
  }
376
412
  }
377
413
 
378
414
  // Every child must be consumed by a field. A leftover element (no trailing
379
- // field ran to absorb it) is malformed dropping it silently would let a
415
+ // field ran to absorb it) is malformed -- dropping it silently would let a
380
416
  // closed sequence of optional fields accept a duplicate/extra element.
381
417
  if (idx < kids.length) {
382
418
  _fail(ctx, schema.code, (schema.what || "value") + " has an unexpected element after its last field");
@@ -393,7 +429,7 @@ function _consumeTrailing(fld, kids, start, fields, ctx) {
393
429
  for (var m = 0; m < fld.members.length; m++) byTag[fld.members[m].tag] = fld.members[m];
394
430
  // The monotonic-order sentinel must start below the lowest accepted tag.
395
431
  // Context tag numbers are non-negative, so -1 is below any member tag when
396
- // minTag is absent otherwise a trailing block whose first member is [0]
432
+ // minTag is absent -- otherwise a trailing block whose first member is [0]
397
433
  // would reject that field (0 <= last==0) as repeated/out-of-order.
398
434
  var last = fld.minTag != null ? fld.minTag - 1 : -1;
399
435
  for (var i = start; i < kids.length; i++) {
@@ -419,7 +455,7 @@ function _consumeTrailing(fld, kids, start, fields, ctx) {
419
455
  // encode(schema, value) is the constructor direction of the SAME schema walk
420
456
  // decodes: it interprets the schema against a plain structural value and emits
421
457
  // canonical DER. A single schema definition therefore drives both directions, so
422
- // context-tag (EXPLICIT/IMPLICIT) handling cannot diverge between them the class
458
+ // context-tag (EXPLICIT/IMPLICIT) handling cannot diverge between them -- the class
423
459
  // of bug where a decoder reads a [tag] IMPLICIT field but an ad-hoc encoder emits
424
460
  // the wrong tag is structurally retired. Encode errors are authoring faults (a bad
425
461
  // value / an un-encodable schema), thrown as plain Errors, not parse verdicts.
@@ -435,7 +471,7 @@ function _encFail(message) { throw new Error("schema.encode: " + message); }
435
471
  * @related pki.schema.engine.walk
436
472
  *
437
473
  * Encode a structural value to canonical DER by interpreting the SAME schema
438
- * `walk` decodes the constructor direction. `value` mirrors the schema: a `seq`
474
+ * `walk` decodes -- the constructor direction. `value` mirrors the schema: a `seq`
439
475
  * takes `{ fieldName: value }`, a leaf its natural JS value (an OID string, a
440
476
  * BigInt, a `{ unusedBits, bytes }` BIT STRING, a `Date`), a `repeat` an array, a
441
477
  * `choice` `{ arm, value }`. EXPLICIT wrappers and IMPLICIT `[tag]` retagging are
@@ -484,7 +520,13 @@ function _encodeSeq(schema, value, ctx) {
484
520
  } else if (fld.fkind === "optional") {
485
521
  if (_present(value[fld.name])) {
486
522
  var enc = encode(fld.schema, value[fld.name], ctx);
487
- parts.push(fld.explicit ? asn1.build.explicit(fld.tag, enc) : enc);
523
+ // X.690 sec. 11.5 -- DER: a component equal to its DEFAULT value shall not
524
+ // be included in the encoding. Compare canonical field encodings, so
525
+ // encode never emits the non-canonical explicit-default form (which a
526
+ // walk-side defaultCode reject would refuse to round-trip).
527
+ var isDefault = fld.hasDefault && fld.def !== undefined &&
528
+ enc.equals(encode(fld.schema, fld.def, ctx));
529
+ if (!isDefault) parts.push(fld.explicit ? asn1.build.explicit(fld.tag, enc) : enc);
488
530
  }
489
531
  } else if (fld.fkind === "trailing") {
490
532
  // Emit present members in strictly-increasing tag order (the walk's rule).
@@ -496,6 +538,10 @@ function _encodeSeq(schema, value, ctx) {
496
538
  parts.push(mm.explicit ? asn1.build.explicit(mm.tag, menc) : menc);
497
539
  }
498
540
  }
541
+ } else {
542
+ // The mirror of _walkSeq's unknown-field-kind reject: a malformed
543
+ // descriptor is an authoring fault on the encode direction too.
544
+ _encFail("unknown field kind " + JSON.stringify(fld && fld.fkind));
499
545
  }
500
546
  }
501
547
  return _wrapConstructed(schema, parts);
@@ -506,7 +552,7 @@ function _encodeRepeat(schema, items, ctx) {
506
552
  // Enforce the SAME constraints walk enforces, so encode never emits DER its own
507
553
  // decoder rejects (the walk(decode(encode)) invariant): the SIZE minimum, and
508
554
  // semantic uniqueness (the key computed exactly as walk does, off the round-tripped
509
- // item a duplicate is an authoring fault, not a parse verdict).
555
+ // item -- a duplicate is an authoring fault, not a parse verdict).
510
556
  if (schema.min != null && items.length < schema.min) {
511
557
  _encFail("this repeat requires at least " + schema.min + " element(s) but got " + items.length);
512
558
  }
@@ -530,7 +576,10 @@ function _encodeRepeat(schema, items, ctx) {
530
576
  }
531
577
 
532
578
  function _encodeChoice(schema, value, ctx) {
533
- if (!value || typeof value.arm !== "number" || value.arm < 0 || value.arm >= schema.alts.length) {
579
+ // Number.isInteger: a NaN / fractional arm passes a `< 0 || >= length` range
580
+ // check (both compares are false for NaN) into an undefined alts lookup --
581
+ // the same authoring fault as an out-of-range arm, failed the same way.
582
+ if (!value || !Number.isInteger(value.arm) || value.arm < 0 || value.arm >= schema.alts.length) {
534
583
  _encFail("a choice value must be { arm: <index>, value: <arm value> }");
535
584
  }
536
585
  return encode(schema.alts[value.arm].schema, value.value, ctx);
@@ -546,8 +595,8 @@ function _encodeChoice(schema, value, ctx) {
546
595
  * @related pki.schema.engine.walk, pki.asn1.decode
547
596
  *
548
597
  * Decode a fresh DER (or, with `ber: true`, BER) blob carried inside an
549
- * already-decoded value an OCTET STRING whose content is itself an encoded
550
- * structure and walk it against a schema. A codec failure is wrapped in the
598
+ * already-decoded value -- an OCTET STRING whose content is itself an encoded
599
+ * structure -- and walk it against a schema. A codec failure is wrapped in the
551
600
  * caller's typed `code`; a schema rejection keeps its own code. This is the
552
601
  * one named form of the re-decode idiom, so the caps that a fresh
553
602
  * `pki.asn1.decode` would restart from zero can be carried across re-decode
@@ -558,7 +607,7 @@ function _encodeChoice(schema, value, ctx) {
558
607
  * @opts
559
608
  * code: string, // typed code wrapping a codec failure (required)
560
609
  * what: string, // human label for the wrapped message
561
- * ber: boolean, // default false BER content region (RFC 7292 §4.1)
610
+ * ber: boolean, // default false -- BER content region (RFC 7292 sec. 4.1)
562
611
  * budget: object, // { remaining: n } shared across a parse's re-decodes
563
612
  * budgetCode: string, // typed code when the budget is exhausted
564
613
  *
@@ -587,15 +636,15 @@ function embeddedDer(schema, bytes, ctx, opts) {
587
636
  return walk(schema, node, ctx);
588
637
  }
589
638
 
590
- // X.690 §11.2.2 a NamedBitList BIT STRING (KeyUsage, PKIFailureInfo, and the
639
+ // X.690 sec. 11.2.2 -- a NamedBitList BIT STRING (KeyUsage, PKIFailureInfo, and the
591
640
  // like) MUST drop every trailing zero bit under DER, giving one canonical
592
641
  // encoding per value: an empty value carries 0 unused bits; a non-empty value's
593
642
  // last content octet is non-zero AND its lowest USED bit (at position
594
643
  // `unusedBits`) is set. `fail(message)` throws the caller's typed domain error,
595
- // so the reject code stays per-format. Centralized so no format re-derives and
596
- // silently diverges on the minimal-encoding rule (a DER-canonicalization
644
+ // so the reject code stays per-format. Centralized so no format re-derives -- and
645
+ // silently diverges on -- the minimal-encoding rule (a DER-canonicalization
597
646
  // bypass class); see the named-bitlist-minimal-encoding-inlined gate.
598
- // ASN.1 node tag predicates the per-node `tagClass`/`tagNumber` test every
647
+ // ASN.1 node tag predicates -- the per-node `tagClass`/`tagNumber` test every
599
648
  // format's matches() detector (and the engine's own optional/choice walkers)
600
649
  // would otherwise re-inline. Centralized so a detector composes one predicate
601
650
  // instead of hand-rolling the comparison (and drifting); see the
@@ -618,12 +667,12 @@ function isContextInRange(node, min, max) {
618
667
 
619
668
  function assertMinimalNamedBits(unusedBits, bytes, fail) {
620
669
  if (bytes.length === 0) {
621
- if (unusedBits !== 0) fail("an empty NamedBitList must encode with 0 unused bits (X.690 §11.2.2)");
670
+ if (unusedBits !== 0) fail("an empty NamedBitList must encode with 0 unused bits (X.690 sec. 11.2.2)");
622
671
  return;
623
672
  }
624
673
  var last = bytes[bytes.length - 1];
625
- if (last === 0) fail("a NamedBitList must not carry a trailing all-zero octet (X.690 §11.2.2)");
626
- if (((last >> unusedBits) & 1) !== 1) fail("a NamedBitList must drop all trailing zero bits (X.690 §11.2.2)");
674
+ if (last === 0) fail("a NamedBitList must not carry a trailing all-zero octet (X.690 sec. 11.2.2)");
675
+ if (((last >> unusedBits) & 1) !== 1) fail("a NamedBitList must drop all trailing zero bits (X.690 sec. 11.2.2)");
627
676
  }
628
677
 
629
678
  module.exports = {
@@ -633,7 +682,7 @@ module.exports = {
633
682
  // leaves
634
683
  oidLeaf: oidLeaf, integerLeaf: integerLeaf, boolean: boolean, octetString: octetString,
635
684
  bitString: bitString, implicitBitString: implicitBitString, implicitOctetString: implicitOctetString,
636
- implicitNull: implicitNull, implicitInteger: implicitInteger,
685
+ implicitNull: implicitNull, implicitInteger: implicitInteger, implicitBoolean: implicitBoolean,
637
686
  any: any, decode: decode, time: time,
638
687
  // engine
639
688
  walk: walk, encode: encode, embeddedDer: embeddedDer, assertMinimalNamedBits: assertMinimalNamedBits,