@blamejs/pki 0.1.15 → 0.1.17

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.
@@ -93,39 +93,49 @@ function _assertArity(schema, kids, ctx) {
93
93
  // the OID / INTEGER / string reads). `decode(fn)` hands the whole node to fn,
94
94
  // which owns its try/catch and code mapping. `any()` yields the node itself.
95
95
 
96
- function oidLeaf() { return { kind: "leaf", read: asn1.read.oid }; }
97
- function integerLeaf() { return { kind: "leaf", read: asn1.read.integer }; }
98
- function boolean() { return { kind: "leaf", read: asn1.read.boolean }; }
99
- function octetString() { return { kind: "leaf", read: asn1.read.octetString }; }
100
- function bitString() { return { kind: "leaf", read: function (n) { var b = asn1.read.bitString(n); return { unusedBits: b.unusedBits, bytes: b.bytes }; } }; }
96
+ // Leaves carry BOTH directions: `read(node) -> value` and `write(value) -> DER TLV`,
97
+ // so a single leaf definition drives decode (walk) and encode (build). A [tag]
98
+ // IMPLICIT leaf's write emits the context-tagged TLV directly, mirroring its read.
99
+ function _implicitIntContent(v) { return asn1.decode(asn1.build.integer(v)).content; }
100
+ function oidLeaf() { return { kind: "leaf", read: asn1.read.oid, write: function (v) { return asn1.build.oid(v); } }; }
101
+ function integerLeaf() { return { kind: "leaf", read: asn1.read.integer, write: function (v) { return asn1.build.integer(v); } }; }
102
+ function boolean() { return { kind: "leaf", read: asn1.read.boolean, write: function (v) { return asn1.build.boolean(v); } }; }
103
+ function octetString() { return { kind: "leaf", read: asn1.read.octetString, write: function (v) { return asn1.build.octetString(v); } }; }
104
+ 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); } }; }
101
105
  // A [tag] IMPLICIT BIT STRING leaf (context-class primitive) — the primitive-leaf
102
106
  // counterpart to implicitSetOf, for e.g. the PKCS#8 publicKey [1] (RFC 5958 §2).
103
- function implicitBitString(tag) { return { kind: "leaf", read: function (n) { var b = asn1.read.bitStringImplicit(n, tag); return { unusedBits: b.unusedBits, bytes: b.bytes }; } }; }
107
+ 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])); } }; }
104
108
  // A [tag] IMPLICIT OCTET STRING leaf (context-class primitive) — the sibling of
105
109
  // implicitBitString, for e.g. the CMS SignerIdentifier subjectKeyIdentifier [0]
106
110
  // (RFC 5652 §5.3). Yields the raw content bytes.
107
- function implicitOctetString(tag) { return { kind: "leaf", read: function (n) { return asn1.read.octetStringImplicit(n, tag); } }; }
111
+ function implicitOctetString(tag) { return { kind: "leaf", read: function (n) { return asn1.read.octetStringImplicit(n, tag); }, write: function (v) { return asn1.build.contextPrimitive(tag, v); } }; }
108
112
  // A [tag] IMPLICIT NULL leaf (context-class primitive, empty content) — the sibling
109
113
  // of implicitBitString/implicitOctetString, for e.g. the OCSP CertStatus good [0] /
110
114
  // unknown [2] arms (RFC 6960 §4.2.1). Yields null; rejects a constructed or
111
115
  // non-empty [tag] node fail-closed.
112
- function implicitNull(tag) { return { kind: "leaf", read: function (n) { return asn1.read.nullImplicit(n, tag); } }; }
116
+ 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)); } }; }
113
117
  // A [tag] IMPLICIT INTEGER leaf (context-class primitive) — the integer sibling of
114
118
  // implicitBitString, for the RFC 3161 Accuracy millis [0] / micros [1] fields
115
119
  // (context-tagged primitive INTEGERs). Yields a BigInt; a constructed or wrong-tag
116
120
  // node fails asn1/* at the codec.
117
- function implicitInteger(tag) { return { kind: "leaf", read: function (n) { return asn1.read.integerImplicit(n, tag); } }; }
121
+ 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)); } }; }
118
122
  function any() { return { kind: "any" }; }
119
- function decode(fn) { return { kind: "decode", fn: fn }; }
123
+ // decode(fn, write?): a custom decode leaf; `write` is its optional paired encoder
124
+ // (value -> DER TLV) so the same leaf drives both directions.
125
+ function decode(fn, write) { return { kind: "decode", fn: fn, write: write }; }
120
126
 
121
127
  // time(ns): a UTCTime / GeneralizedTime value, asserting the tag before the
122
- // codec reads it (mirrors _parseValidityTime's x509/bad-time guard).
128
+ // 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).
123
130
  function time(ns) {
124
131
  return decode(function (n, ctx) {
125
132
  if (n.tagClass !== "universal" || (n.tagNumber !== TAGS.UTC_TIME && n.tagNumber !== TAGS.GENERALIZED_TIME)) {
126
133
  _fail(ctx, ns.prefix + "/bad-time", "time must be UTCTime or GeneralizedTime");
127
134
  }
128
135
  return asn1.read.time(n);
136
+ }, function (date) {
137
+ var y = date.getUTCFullYear();
138
+ return (y >= 1950 && y < 2050) ? asn1.build.utcTime(date) : asn1.build.generalizedTime(date);
129
139
  });
130
140
  }
131
141
 
@@ -136,6 +146,10 @@ function optional(name, schema, opts) {
136
146
  opts = opts || {};
137
147
  // How the optional field is recognized at its position:
138
148
  // - default: a context [tag] (the certificate version [0] shape).
149
+ // - tags: a context tag in the set — an OPTIONAL field whose type is
150
+ // itself a CHOICE of several context alternatives, e.g. the CRMF CertReqMsg
151
+ // popo ProofOfPossession ([0]..[3]) sitting before the universal-SEQUENCE
152
+ // regInfo (RFC 4211 §3), where ANY of the CHOICE's tags marks it present.
139
153
  // - whenUniversal: the next element iff its UNIVERSAL tag is in the set —
140
154
  // the CRL TBSCertList shape (bare INTEGER version, Time nextUpdate,
141
155
  // SEQUENCE revokedCertificates), disambiguated by tag, not a context [n].
@@ -147,7 +161,9 @@ function optional(name, schema, opts) {
147
161
  ? function () { return true; }
148
162
  : opts.whenUniversal
149
163
  ? function (n) { return n.tagClass === "universal" && opts.whenUniversal.indexOf(n.tagNumber) !== -1; }
150
- : function (n) { return n.tagClass === "context" && n.tagNumber === opts.tag; };
164
+ : opts.tags
165
+ ? function (n) { return n.tagClass === "context" && opts.tags.indexOf(n.tagNumber) !== -1; }
166
+ : function (n) { return n.tagClass === "context" && n.tagNumber === opts.tag; };
151
167
  return { fkind: "optional", name: name, schema: schema, tag: opts.tag, match: match,
152
168
  explicit: !!opts.explicit, emptyCode: opts.emptyCode, hasDefault: ("default" in opts), def: opts.default };
153
169
  }
@@ -218,7 +234,7 @@ function implicitSeqOf(tag, item, opts) {
218
234
  * @primitive pki.schema.engine.walk
219
235
  * @signature pki.schema.engine.walk(schema, node, ctx) -> value
220
236
  * @since 0.1.7
221
- * @status experimental
237
+ * @status stable
222
238
  * @spec X.690, X.680
223
239
  * @related pki.asn1.decode, pki.schema.x509.parse
224
240
  *
@@ -389,6 +405,124 @@ function _consumeTrailing(fld, kids, start, fields, ctx) {
389
405
  }
390
406
  }
391
407
 
408
+ // ---- the encode engine (the inverse of walk) -------------------------
409
+ // encode(schema, value) is the constructor direction of the SAME schema walk
410
+ // decodes: it interprets the schema against a plain structural value and emits
411
+ // canonical DER. A single schema definition therefore drives both directions, so
412
+ // context-tag (EXPLICIT/IMPLICIT) handling cannot diverge between them — the class
413
+ // of bug where a decoder reads a [tag] IMPLICIT field but an ad-hoc encoder emits
414
+ // the wrong tag is structurally retired. Encode errors are authoring faults (a bad
415
+ // value / an un-encodable schema), thrown as plain Errors, not parse verdicts.
416
+
417
+ function _encFail(message) { throw new Error("schema.encode: " + message); }
418
+
419
+ /**
420
+ * @primitive pki.schema.engine.encode
421
+ * @signature pki.schema.engine.encode(schema, value, ctx) -> Buffer
422
+ * @since 0.1.17
423
+ * @status experimental
424
+ * @spec X.690, X.680
425
+ * @related pki.schema.engine.walk
426
+ *
427
+ * Encode a structural value to canonical DER by interpreting the SAME schema
428
+ * `walk` decodes — the constructor direction. `value` mirrors the schema: a `seq`
429
+ * takes `{ fieldName: value }`, a leaf its natural JS value (an OID string, a
430
+ * BigInt, a `{ unusedBits, bytes }` BIT STRING, a `Date`), a `repeat` an array, a
431
+ * `choice` `{ arm, value }`. EXPLICIT wrappers and IMPLICIT `[tag]` retagging are
432
+ * applied by the engine, so `walk(schema, decode(encode(schema, v)))` round-trips.
433
+ *
434
+ * @example
435
+ * var S = pki.schema.engine;
436
+ * var der = S.encode(S.seq([S.field("n", S.integerLeaf())]), { n: 42n });
437
+ */
438
+ function encode(schema, value, ctx) {
439
+ switch (schema.kind) {
440
+ case "leaf": return schema.write(value);
441
+ case "any": return Buffer.isBuffer(value) ? value : value.bytes;
442
+ case "decode":
443
+ if (typeof schema.write !== "function") _encFail("this decode leaf has no paired encoder (decode(fn, write))");
444
+ return schema.write(value, ctx);
445
+ case "seq": return _encodeSeq(schema, value, ctx);
446
+ case "explicit": return asn1.build.explicit(schema.tag, encode(schema.schema, value, ctx));
447
+ case "repeat": return _encodeRepeat(schema, value, ctx);
448
+ case "choice": return _encodeChoice(schema, value, ctx);
449
+ default: _encFail("cannot encode schema kind " + JSON.stringify(schema.kind));
450
+ }
451
+ return undefined;
452
+ }
453
+
454
+ function _wrapConstructed(schema, parts) {
455
+ if (schema.implicitTag != null) return asn1.build.contextConstructed(schema.implicitTag, Buffer.concat(parts));
456
+ var mode = schema.assert || "sequence";
457
+ if (mode === "set") return asn1.build.set(parts);
458
+ // "constructed" is a DECODE-time leniency (accept any constructed shape); the
459
+ // canonical encoding is a universal SEQUENCE, same as "sequence".
460
+ if (mode === "sequence" || mode === "constructed") return asn1.build.sequence(parts);
461
+ _encFail("cannot encode a seq/repeat with assert " + JSON.stringify(mode));
462
+ return undefined;
463
+ }
464
+
465
+ function _present(v) { return v !== undefined && v !== null; }
466
+
467
+ function _encodeSeq(schema, value, ctx) {
468
+ value = value || {};
469
+ var parts = [];
470
+ for (var f = 0; f < schema.fields.length; f++) {
471
+ var fld = schema.fields[f];
472
+ if (fld.fkind === "required") {
473
+ parts.push(encode(fld.schema, value[fld.name], ctx));
474
+ } else if (fld.fkind === "optional") {
475
+ if (_present(value[fld.name])) {
476
+ var enc = encode(fld.schema, value[fld.name], ctx);
477
+ parts.push(fld.explicit ? asn1.build.explicit(fld.tag, enc) : enc);
478
+ }
479
+ } else if (fld.fkind === "trailing") {
480
+ // Emit present members in strictly-increasing tag order (the walk's rule).
481
+ var members = fld.members.slice().sort(function (a, b) { return a.tag - b.tag; });
482
+ for (var i = 0; i < members.length; i++) {
483
+ var mm = members[i];
484
+ if (_present(value[mm.name])) {
485
+ var menc = encode(mm.schema, value[mm.name], ctx);
486
+ parts.push(mm.explicit ? asn1.build.explicit(mm.tag, menc) : menc);
487
+ }
488
+ }
489
+ }
490
+ }
491
+ return _wrapConstructed(schema, parts);
492
+ }
493
+
494
+ function _encodeRepeat(schema, items, ctx) {
495
+ if (!Array.isArray(items)) _encFail("a repeat value must be an array");
496
+ // Enforce the SAME constraints walk enforces, so encode never emits DER its own
497
+ // decoder rejects (the walk(decode(encode)) invariant): the SIZE minimum, and
498
+ // semantic uniqueness (the key computed exactly as walk does, off the round-tripped
499
+ // item — a duplicate is an authoring fault, not a parse verdict).
500
+ if (schema.min != null && items.length < schema.min) {
501
+ _encFail("this repeat requires at least " + schema.min + " element(s) but got " + items.length);
502
+ }
503
+ var parts = items.map(function (it) { return encode(schema.item, it, ctx); });
504
+ if (schema.unique) {
505
+ var seen = new Set();
506
+ parts.forEach(function (tlv) {
507
+ var item = { node: asn1.decode(tlv) };
508
+ item.value = walk(schema.item, item.node, ctx);
509
+ var key = schema.unique(item);
510
+ if (seen.has(key)) _encFail("this repeat requires unique elements but got a duplicate key: " + key);
511
+ seen.add(key);
512
+ });
513
+ }
514
+ if (schema.derSetOrder) parts = parts.slice().sort(Buffer.compare);
515
+ if (schema.implicitTag != null) return asn1.build.contextConstructed(schema.implicitTag, Buffer.concat(parts));
516
+ return schema.assert === "set" ? asn1.build.set(parts) : asn1.build.sequence(parts);
517
+ }
518
+
519
+ function _encodeChoice(schema, value, ctx) {
520
+ if (!value || typeof value.arm !== "number" || value.arm < 0 || value.arm >= schema.alts.length) {
521
+ _encFail("a choice value must be { arm: <index>, value: <arm value> }");
522
+ }
523
+ return encode(schema.alts[value.arm].schema, value.value, ctx);
524
+ }
525
+
392
526
  module.exports = {
393
527
  // structural
394
528
  seq: seq, field: field, optional: optional, explicit: explicit, trailing: trailing,
@@ -399,5 +533,5 @@ module.exports = {
399
533
  implicitNull: implicitNull, implicitInteger: implicitInteger,
400
534
  any: any, decode: decode, time: time,
401
535
  // engine
402
- walk: walk,
536
+ walk: walk, encode: encode,
403
537
  };
@@ -425,7 +425,7 @@ var OCSP_REQUEST = schema.seq([
425
425
  * @primitive pki.schema.ocsp.parseRequest
426
426
  * @signature pki.schema.ocsp.parseRequest(input) -> ocspRequest
427
427
  * @since 0.1.11
428
- * @status experimental
428
+ * @status stable
429
429
  * @spec RFC 6960
430
430
  * @related pki.schema.ocsp.parseResponse, pki.schema.parse
431
431
  *
@@ -447,7 +447,7 @@ var parseRequest = pkix.makeParser({ pemLabel: "OCSP REQUEST", PemError: PemErro
447
447
  * @primitive pki.schema.ocsp.parseResponse
448
448
  * @signature pki.schema.ocsp.parseResponse(input) -> ocspResponse
449
449
  * @since 0.1.11
450
- * @status experimental
450
+ * @status stable
451
451
  * @spec RFC 6960
452
452
  * @related pki.schema.ocsp.parseRequest, pki.schema.parse
453
453
  *
@@ -471,7 +471,7 @@ var parseResponse = pkix.makeParser({ pemLabel: "OCSP RESPONSE", PemError: PemEr
471
471
  * @primitive pki.schema.ocsp.pemDecode
472
472
  * @signature pki.schema.ocsp.pemDecode(text, label?) -> Buffer
473
473
  * @since 0.1.11
474
- * @status experimental
474
+ * @status stable
475
475
  * @spec RFC 7468, RFC 6960
476
476
  * @related pki.schema.ocsp.parseResponse
477
477
  *
@@ -102,7 +102,7 @@ var ENCRYPTED_PRIVATE_KEY_INFO = schema.seq([
102
102
  * @primitive pki.schema.pkcs8.parse
103
103
  * @signature pki.schema.pkcs8.parse(input) -> privateKey
104
104
  * @since 0.1.9
105
- * @status experimental
105
+ * @status stable
106
106
  * @spec RFC 5208, RFC 5958
107
107
  * @related pki.schema.parse, pki.schema.x509.parse
108
108
  *
@@ -124,7 +124,7 @@ var parse = pkix.makeParser({ pemLabel: "PRIVATE KEY", PemError: PemError, Error
124
124
  * @primitive pki.schema.pkcs8.parseEncrypted
125
125
  * @signature pki.schema.pkcs8.parseEncrypted(input) -> encrypted
126
126
  * @since 0.1.9
127
- * @status experimental
127
+ * @status stable
128
128
  * @spec RFC 5958, RFC 5208
129
129
  * @related pki.schema.pkcs8.parse
130
130
  *
@@ -143,7 +143,7 @@ var parseEncrypted = pkix.makeParser({ pemLabel: "ENCRYPTED PRIVATE KEY", PemErr
143
143
  * @primitive pki.schema.pkcs8.pemDecode
144
144
  * @signature pki.schema.pkcs8.pemDecode(text, label?) -> Buffer
145
145
  * @since 0.1.9
146
- * @status experimental
146
+ * @status stable
147
147
  * @spec RFC 7468, RFC 5958
148
148
  * @related pki.schema.pkcs8.parse
149
149
  *
@@ -160,7 +160,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || "PRIVATE
160
160
  * @primitive pki.schema.pkcs8.pemEncode
161
161
  * @signature pki.schema.pkcs8.pemEncode(der, label?) -> string
162
162
  * @since 0.1.9
163
- * @status experimental
163
+ * @status stable
164
164
  * @spec RFC 7468
165
165
  * @related pki.schema.pkcs8.pemDecode
166
166
  *