@blamejs/pki 0.1.16 → 0.1.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 +39 -0
- package/README.md +9 -7
- package/lib/asn1-der.js +122 -22
- package/lib/constants.js +16 -0
- package/lib/framework-error.js +13 -0
- package/lib/oid.js +42 -3
- package/lib/path-validate.js +10 -2
- package/lib/schema-all.js +41 -7
- package/lib/schema-cms.js +22 -3
- package/lib/schema-crl.js +2 -2
- package/lib/schema-crmf.js +487 -0
- package/lib/schema-csr.js +3 -3
- package/lib/schema-engine.js +216 -18
- package/lib/schema-ocsp.js +5 -7
- package/lib/schema-pkcs12.js +615 -0
- package/lib/schema-pkcs8.js +14 -4
- package/lib/schema-pkix.js +71 -29
- package/lib/schema-tsp.js +1 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/schema-engine.js
CHANGED
|
@@ -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
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
function
|
|
100
|
-
function
|
|
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
|
-
|
|
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
|
-
:
|
|
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
|
}
|
|
@@ -184,12 +200,14 @@ function choice(alts, opts) {
|
|
|
184
200
|
function seqOf(item, opts) {
|
|
185
201
|
opts = opts || {};
|
|
186
202
|
return { kind: "repeat", item: item, assert: opts.assert || "sequence", code: opts.code, what: opts.what,
|
|
187
|
-
min: opts.min,
|
|
203
|
+
min: opts.min, max: opts.max, maxCode: opts.maxCode,
|
|
204
|
+
unique: opts.unique, dupCode: opts.dupCode, build: opts.build };
|
|
188
205
|
}
|
|
189
206
|
function setOf(item, opts) {
|
|
190
207
|
opts = opts || {};
|
|
191
208
|
return { kind: "repeat", item: item, assert: opts.assert || "set", derSetOrder: true, code: opts.code, what: opts.what,
|
|
192
|
-
min: opts.min,
|
|
209
|
+
min: opts.min, max: opts.max, maxCode: opts.maxCode,
|
|
210
|
+
unique: opts.unique, dupCode: opts.dupCode, build: opts.build };
|
|
193
211
|
}
|
|
194
212
|
function setOfUnique(item, keyFn, opts) {
|
|
195
213
|
return setOf(item, Object.assign({ unique: keyFn }, opts || {}));
|
|
@@ -200,7 +218,8 @@ function setOfUnique(item, keyFn, opts) {
|
|
|
200
218
|
function implicitSetOf(tag, item, opts) {
|
|
201
219
|
opts = opts || {};
|
|
202
220
|
return { kind: "repeat", item: item, assert: "implicit", implicitTag: tag, derSetOrder: true, code: opts.code, what: opts.what,
|
|
203
|
-
min: opts.min,
|
|
221
|
+
min: opts.min, max: opts.max, maxCode: opts.maxCode,
|
|
222
|
+
unique: opts.unique, dupCode: opts.dupCode, build: opts.build };
|
|
204
223
|
}
|
|
205
224
|
// [tag] IMPLICIT SEQUENCE OF item — the context tag REPLACES the universal SEQUENCE
|
|
206
225
|
// tag, so the node is a context-class constructed [tag] whose direct children are the
|
|
@@ -209,7 +228,8 @@ function implicitSetOf(tag, item, opts) {
|
|
|
209
228
|
function implicitSeqOf(tag, item, opts) {
|
|
210
229
|
opts = opts || {};
|
|
211
230
|
return { kind: "repeat", item: item, assert: "implicit", implicitTag: tag, code: opts.code, what: opts.what,
|
|
212
|
-
min: opts.min,
|
|
231
|
+
min: opts.min, max: opts.max, maxCode: opts.maxCode,
|
|
232
|
+
unique: opts.unique, dupCode: opts.dupCode, build: opts.build };
|
|
213
233
|
}
|
|
214
234
|
|
|
215
235
|
// ---- the walk engine -------------------------------------------------
|
|
@@ -218,7 +238,7 @@ function implicitSeqOf(tag, item, opts) {
|
|
|
218
238
|
* @primitive pki.schema.engine.walk
|
|
219
239
|
* @signature pki.schema.engine.walk(schema, node, ctx) -> value
|
|
220
240
|
* @since 0.1.7
|
|
221
|
-
* @status
|
|
241
|
+
* @status stable
|
|
222
242
|
* @spec X.690, X.680
|
|
223
243
|
* @related pki.asn1.decode, pki.schema.x509.parse
|
|
224
244
|
*
|
|
@@ -288,6 +308,12 @@ function _walkRepeat(schema, node, ctx) {
|
|
|
288
308
|
if (schema.min != null && kids.length < schema.min) {
|
|
289
309
|
_fail(ctx, schema.code, (schema.what || "value") + " must contain at least " + schema.min + " element(s)");
|
|
290
310
|
}
|
|
311
|
+
// The element-count ceiling: a container of a great many tiny elements
|
|
312
|
+
// amplifies memory through per-element walk products, so a schema that
|
|
313
|
+
// parses attacker-sized lists declares `max` and fails typed instead.
|
|
314
|
+
if (schema.max != null && kids.length > schema.max) {
|
|
315
|
+
_fail(ctx, schema.maxCode || schema.code, (schema.what || "value") + " exceeds the element cap " + schema.max);
|
|
316
|
+
}
|
|
291
317
|
// DER (X.690 §11.6) — the components of a SET OF appear in ascending order, the
|
|
292
318
|
// encodings compared as octet strings. SEQUENCE OF is order-preserving and is
|
|
293
319
|
// exempt (no derSetOrder). Compare full TLV byte strings, the same canonical
|
|
@@ -389,6 +415,178 @@ function _consumeTrailing(fld, kids, start, fields, ctx) {
|
|
|
389
415
|
}
|
|
390
416
|
}
|
|
391
417
|
|
|
418
|
+
// ---- the encode engine (the inverse of walk) -------------------------
|
|
419
|
+
// encode(schema, value) is the constructor direction of the SAME schema walk
|
|
420
|
+
// decodes: it interprets the schema against a plain structural value and emits
|
|
421
|
+
// canonical DER. A single schema definition therefore drives both directions, so
|
|
422
|
+
// context-tag (EXPLICIT/IMPLICIT) handling cannot diverge between them — the class
|
|
423
|
+
// of bug where a decoder reads a [tag] IMPLICIT field but an ad-hoc encoder emits
|
|
424
|
+
// the wrong tag is structurally retired. Encode errors are authoring faults (a bad
|
|
425
|
+
// value / an un-encodable schema), thrown as plain Errors, not parse verdicts.
|
|
426
|
+
|
|
427
|
+
function _encFail(message) { throw new Error("schema.encode: " + message); }
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* @primitive pki.schema.engine.encode
|
|
431
|
+
* @signature pki.schema.engine.encode(schema, value, ctx) -> Buffer
|
|
432
|
+
* @since 0.1.17
|
|
433
|
+
* @status experimental
|
|
434
|
+
* @spec X.690, X.680
|
|
435
|
+
* @related pki.schema.engine.walk
|
|
436
|
+
*
|
|
437
|
+
* Encode a structural value to canonical DER by interpreting the SAME schema
|
|
438
|
+
* `walk` decodes — the constructor direction. `value` mirrors the schema: a `seq`
|
|
439
|
+
* takes `{ fieldName: value }`, a leaf its natural JS value (an OID string, a
|
|
440
|
+
* BigInt, a `{ unusedBits, bytes }` BIT STRING, a `Date`), a `repeat` an array, a
|
|
441
|
+
* `choice` `{ arm, value }`. EXPLICIT wrappers and IMPLICIT `[tag]` retagging are
|
|
442
|
+
* applied by the engine, so `walk(schema, decode(encode(schema, v)))` round-trips.
|
|
443
|
+
*
|
|
444
|
+
* @example
|
|
445
|
+
* var S = pki.schema.engine;
|
|
446
|
+
* var der = S.encode(S.seq([S.field("n", S.integerLeaf())]), { n: 42n });
|
|
447
|
+
*/
|
|
448
|
+
function encode(schema, value, ctx) {
|
|
449
|
+
switch (schema.kind) {
|
|
450
|
+
case "leaf": return schema.write(value);
|
|
451
|
+
case "any": return Buffer.isBuffer(value) ? value : value.bytes;
|
|
452
|
+
case "decode":
|
|
453
|
+
if (typeof schema.write !== "function") _encFail("this decode leaf has no paired encoder (decode(fn, write))");
|
|
454
|
+
return schema.write(value, ctx);
|
|
455
|
+
case "seq": return _encodeSeq(schema, value, ctx);
|
|
456
|
+
case "explicit": return asn1.build.explicit(schema.tag, encode(schema.schema, value, ctx));
|
|
457
|
+
case "repeat": return _encodeRepeat(schema, value, ctx);
|
|
458
|
+
case "choice": return _encodeChoice(schema, value, ctx);
|
|
459
|
+
default: _encFail("cannot encode schema kind " + JSON.stringify(schema.kind));
|
|
460
|
+
}
|
|
461
|
+
return undefined;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function _wrapConstructed(schema, parts) {
|
|
465
|
+
if (schema.implicitTag != null) return asn1.build.contextConstructed(schema.implicitTag, Buffer.concat(parts));
|
|
466
|
+
var mode = schema.assert || "sequence";
|
|
467
|
+
if (mode === "set") return asn1.build.set(parts);
|
|
468
|
+
// "constructed" is a DECODE-time leniency (accept any constructed shape); the
|
|
469
|
+
// canonical encoding is a universal SEQUENCE, same as "sequence".
|
|
470
|
+
if (mode === "sequence" || mode === "constructed") return asn1.build.sequence(parts);
|
|
471
|
+
_encFail("cannot encode a seq/repeat with assert " + JSON.stringify(mode));
|
|
472
|
+
return undefined;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
function _present(v) { return v !== undefined && v !== null; }
|
|
476
|
+
|
|
477
|
+
function _encodeSeq(schema, value, ctx) {
|
|
478
|
+
value = value || {};
|
|
479
|
+
var parts = [];
|
|
480
|
+
for (var f = 0; f < schema.fields.length; f++) {
|
|
481
|
+
var fld = schema.fields[f];
|
|
482
|
+
if (fld.fkind === "required") {
|
|
483
|
+
parts.push(encode(fld.schema, value[fld.name], ctx));
|
|
484
|
+
} else if (fld.fkind === "optional") {
|
|
485
|
+
if (_present(value[fld.name])) {
|
|
486
|
+
var enc = encode(fld.schema, value[fld.name], ctx);
|
|
487
|
+
parts.push(fld.explicit ? asn1.build.explicit(fld.tag, enc) : enc);
|
|
488
|
+
}
|
|
489
|
+
} else if (fld.fkind === "trailing") {
|
|
490
|
+
// Emit present members in strictly-increasing tag order (the walk's rule).
|
|
491
|
+
var members = fld.members.slice().sort(function (a, b) { return a.tag - b.tag; });
|
|
492
|
+
for (var i = 0; i < members.length; i++) {
|
|
493
|
+
var mm = members[i];
|
|
494
|
+
if (_present(value[mm.name])) {
|
|
495
|
+
var menc = encode(mm.schema, value[mm.name], ctx);
|
|
496
|
+
parts.push(mm.explicit ? asn1.build.explicit(mm.tag, menc) : menc);
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return _wrapConstructed(schema, parts);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function _encodeRepeat(schema, items, ctx) {
|
|
505
|
+
if (!Array.isArray(items)) _encFail("a repeat value must be an array");
|
|
506
|
+
// Enforce the SAME constraints walk enforces, so encode never emits DER its own
|
|
507
|
+
// decoder rejects (the walk(decode(encode)) invariant): the SIZE minimum, and
|
|
508
|
+
// 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).
|
|
510
|
+
if (schema.min != null && items.length < schema.min) {
|
|
511
|
+
_encFail("this repeat requires at least " + schema.min + " element(s) but got " + items.length);
|
|
512
|
+
}
|
|
513
|
+
if (schema.max != null && items.length > schema.max) {
|
|
514
|
+
_encFail("this repeat caps at " + schema.max + " element(s) but got " + items.length);
|
|
515
|
+
}
|
|
516
|
+
var parts = items.map(function (it) { return encode(schema.item, it, ctx); });
|
|
517
|
+
if (schema.unique) {
|
|
518
|
+
var seen = new Set();
|
|
519
|
+
parts.forEach(function (tlv) {
|
|
520
|
+
var item = { node: asn1.decode(tlv) };
|
|
521
|
+
item.value = walk(schema.item, item.node, ctx);
|
|
522
|
+
var key = schema.unique(item);
|
|
523
|
+
if (seen.has(key)) _encFail("this repeat requires unique elements but got a duplicate key: " + key);
|
|
524
|
+
seen.add(key);
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
if (schema.derSetOrder) parts = parts.slice().sort(Buffer.compare);
|
|
528
|
+
if (schema.implicitTag != null) return asn1.build.contextConstructed(schema.implicitTag, Buffer.concat(parts));
|
|
529
|
+
return schema.assert === "set" ? asn1.build.set(parts) : asn1.build.sequence(parts);
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
function _encodeChoice(schema, value, ctx) {
|
|
533
|
+
if (!value || typeof value.arm !== "number" || value.arm < 0 || value.arm >= schema.alts.length) {
|
|
534
|
+
_encFail("a choice value must be { arm: <index>, value: <arm value> }");
|
|
535
|
+
}
|
|
536
|
+
return encode(schema.alts[value.arm].schema, value.value, ctx);
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
/**
|
|
540
|
+
* @primitive pki.schema.engine.embeddedDer
|
|
541
|
+
* @signature pki.schema.engine.embeddedDer(schema, bytes, ctx, opts?) -> value
|
|
542
|
+
* @since 0.1.18
|
|
543
|
+
* @status experimental
|
|
544
|
+
* @spec X.690
|
|
545
|
+
* @defends ASN.1-parser-DoS (CWE-400)
|
|
546
|
+
* @related pki.schema.engine.walk, pki.asn1.decode
|
|
547
|
+
*
|
|
548
|
+
* 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
|
|
551
|
+
* caller's typed `code`; a schema rejection keeps its own code. This is the
|
|
552
|
+
* one named form of the re-decode idiom, so the caps that a fresh
|
|
553
|
+
* `pki.asn1.decode` would restart from zero can be carried across re-decode
|
|
554
|
+
* boundaries: a shared `budget` (`{ remaining: n }`) decrements on every call
|
|
555
|
+
* and fails with `budgetCode` at zero, bounding how many nested blobs one
|
|
556
|
+
* parse may unwrap however deeply a container chains them.
|
|
557
|
+
*
|
|
558
|
+
* @opts
|
|
559
|
+
* code: string, // typed code wrapping a codec failure (required)
|
|
560
|
+
* what: string, // human label for the wrapped message
|
|
561
|
+
* ber: boolean, // default false — BER content region (RFC 7292 §4.1)
|
|
562
|
+
* budget: object, // { remaining: n } shared across a parse's re-decodes
|
|
563
|
+
* budgetCode: string, // typed code when the budget is exhausted
|
|
564
|
+
*
|
|
565
|
+
* @example
|
|
566
|
+
* var S = pki.schema.engine;
|
|
567
|
+
* var INNER = S.seq([S.field("version", S.integerLeaf())], { code: "app/bad-inner" });
|
|
568
|
+
* var ns = { prefix: "app", E: MyError, oid: pki.oid };
|
|
569
|
+
* S.embeddedDer(INNER, pki.asn1.build.sequence([pki.asn1.build.integer(3n)]), ns,
|
|
570
|
+
* { code: "app/bad-der", what: "the embedded structure" });
|
|
571
|
+
*/
|
|
572
|
+
function embeddedDer(schema, bytes, ctx, opts) {
|
|
573
|
+
opts = opts || {};
|
|
574
|
+
if (opts.budget) {
|
|
575
|
+
if (!(opts.budget.remaining > 0)) {
|
|
576
|
+
throw ctx.E(opts.budgetCode || opts.code, (opts.what || "embedded DER") +
|
|
577
|
+
": the cross-decode budget is exhausted (nesting chained across too many re-decode boundaries)");
|
|
578
|
+
}
|
|
579
|
+
opts.budget.remaining -= 1;
|
|
580
|
+
}
|
|
581
|
+
var node;
|
|
582
|
+
try {
|
|
583
|
+
node = asn1.decode(bytes, opts.ber ? { ber: true } : undefined);
|
|
584
|
+
} catch (e) {
|
|
585
|
+
throw ctx.E(opts.code, (opts.what || "embedded DER") + " did not decode: " + e.message, e);
|
|
586
|
+
}
|
|
587
|
+
return walk(schema, node, ctx);
|
|
588
|
+
}
|
|
589
|
+
|
|
392
590
|
module.exports = {
|
|
393
591
|
// structural
|
|
394
592
|
seq: seq, field: field, optional: optional, explicit: explicit, trailing: trailing,
|
|
@@ -399,5 +597,5 @@ module.exports = {
|
|
|
399
597
|
implicitNull: implicitNull, implicitInteger: implicitInteger,
|
|
400
598
|
any: any, decode: decode, time: time,
|
|
401
599
|
// engine
|
|
402
|
-
walk: walk,
|
|
600
|
+
walk: walk, encode: encode, embeddedDer: embeddedDer,
|
|
403
601
|
};
|
package/lib/schema-ocsp.js
CHANGED
|
@@ -294,14 +294,12 @@ var RESPONSE_BYTES = schema.seq([
|
|
|
294
294
|
if (responseType !== OID_OCSP_BASIC) {
|
|
295
295
|
throw NS.E("ocsp/unsupported-response-type", (ctx.oid.name(responseType) || responseType) + " is not id-pkix-ocsp-basic (RFC 6960 §4.2.1)");
|
|
296
296
|
}
|
|
297
|
-
var inner;
|
|
298
|
-
try { inner = asn1.decode(raw); }
|
|
299
|
-
catch (e) { throw NS.E("ocsp/bad-der", "BasicOCSPResponse DER did not decode: " + ((e && e.message) || String(e)), e); }
|
|
300
297
|
return {
|
|
301
298
|
responseType: responseType,
|
|
302
299
|
responseTypeName: ctx.oid.name(responseType) || null,
|
|
303
300
|
response: raw,
|
|
304
|
-
basicResponse: schema.
|
|
301
|
+
basicResponse: schema.embeddedDer(BASIC_OCSP_RESPONSE, raw, ctx,
|
|
302
|
+
{ code: "ocsp/bad-der", what: "BasicOCSPResponse" }).result,
|
|
305
303
|
};
|
|
306
304
|
},
|
|
307
305
|
});
|
|
@@ -425,7 +423,7 @@ var OCSP_REQUEST = schema.seq([
|
|
|
425
423
|
* @primitive pki.schema.ocsp.parseRequest
|
|
426
424
|
* @signature pki.schema.ocsp.parseRequest(input) -> ocspRequest
|
|
427
425
|
* @since 0.1.11
|
|
428
|
-
* @status
|
|
426
|
+
* @status stable
|
|
429
427
|
* @spec RFC 6960
|
|
430
428
|
* @related pki.schema.ocsp.parseResponse, pki.schema.parse
|
|
431
429
|
*
|
|
@@ -447,7 +445,7 @@ var parseRequest = pkix.makeParser({ pemLabel: "OCSP REQUEST", PemError: PemErro
|
|
|
447
445
|
* @primitive pki.schema.ocsp.parseResponse
|
|
448
446
|
* @signature pki.schema.ocsp.parseResponse(input) -> ocspResponse
|
|
449
447
|
* @since 0.1.11
|
|
450
|
-
* @status
|
|
448
|
+
* @status stable
|
|
451
449
|
* @spec RFC 6960
|
|
452
450
|
* @related pki.schema.ocsp.parseRequest, pki.schema.parse
|
|
453
451
|
*
|
|
@@ -471,7 +469,7 @@ var parseResponse = pkix.makeParser({ pemLabel: "OCSP RESPONSE", PemError: PemEr
|
|
|
471
469
|
* @primitive pki.schema.ocsp.pemDecode
|
|
472
470
|
* @signature pki.schema.ocsp.pemDecode(text, label?) -> Buffer
|
|
473
471
|
* @since 0.1.11
|
|
474
|
-
* @status
|
|
472
|
+
* @status stable
|
|
475
473
|
* @spec RFC 7468, RFC 6960
|
|
476
474
|
* @related pki.schema.ocsp.parseResponse
|
|
477
475
|
*
|