@blamejs/pki 0.1.22 → 0.1.24

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.
@@ -9,8 +9,8 @@
9
9
  * @slug ocsp
10
10
  *
11
11
  * @intro
12
- * OCSP request and response handling per RFC 6960 (§4.1 OCSPRequest, §4.2
13
- * OCSPResponse). Two entry points `parseRequest` and `parseResponse` turn a
12
+ * OCSP request and response handling per RFC 6960 (sec. 4.1 OCSPRequest, sec. 4.2
13
+ * OCSPResponse). Two entry points -- `parseRequest` and `parseResponse` -- turn a
14
14
  * DER or PEM message into a structured object. A response is a two-stage
15
15
  * OID-dispatch: `OCSPResponse` carries an ENUMERATED `responseStatus` and an
16
16
  * OPTIONAL `responseBytes`; when the `responseType` is `id-pkix-ocsp-basic` its
@@ -23,11 +23,13 @@
23
23
  * `issuerNameHash` / `issuerKeyHash` as raw octets, and the responder's `byKey`
24
24
  * hash and the raw `signature` bytes are left for the caller to verify. Embedded
25
25
  * certificates are surfaced as raw DER, so an unknown alternative never fails the
26
- * parse. DER-only, fail-closed.
26
+ * parse. The nonce extension's value syntax is validated (OCTET STRING of 1..128
27
+ * octets, RFC 9654 sec. 2.1) with the decoded bytes surfaced as `nonce` alongside the
28
+ * raw value; other extension values stay opaque. DER-only, fail-closed.
27
29
  *
28
30
  * @card
29
31
  * Parse DER / PEM OCSP requests and responses (RFC 6960) into structured,
30
- * validated fields per-certificate status, responder identity, raw tbs bytes for
32
+ * validated fields -- per-certificate status, responder identity, raw tbs bytes for
31
33
  * external verification, certificates kept raw, fail-closed.
32
34
  */
33
35
 
@@ -56,31 +58,32 @@ var VERSION = pkix.versionReader(NS, {});
56
58
  // other is recognized-and-deferred. Resolved from the registry, never a literal.
57
59
  var OID_OCSP_BASIC = oid.byName("ocspBasic");
58
60
 
59
- // OCSPResponseStatus ::= ENUMERATED value 4 is "not used" and >= 7 is undefined,
60
- // so both are rejected (RFC 6960 §4.2.1).
61
+ // id-pkix-ocsp-nonce -- the request/response binding extension whose value syntax
62
+ // RFC 9654 sec. 2.1 (updating RFC 6960 sec. 4.4.1) fixes to OCTET STRING (SIZE(1..128)).
63
+ var OID_OCSP_NONCE = oid.byName("ocspNonce");
64
+
65
+ // OCSPResponseStatus ::= ENUMERATED -- value 4 is "not used" and >= 7 is undefined,
66
+ // so both are rejected (RFC 6960 sec. 4.2.1).
61
67
  var STATUS_NAMES = { "0": "successful", "1": "malformedRequest", "2": "internalError", "3": "tryLater", "5": "sigRequired", "6": "unauthorized" };
62
68
 
63
- // CRLReason ::= ENUMERATED value 7 is "not used"; the rest are RFC 5280 §5.3.1.
64
- var CRL_REASONS = { "0": "unspecified", "1": "keyCompromise", "2": "cACompromise", "3": "affiliationChanged", "4": "superseded", "5": "cessationOfOperation", "6": "certificateHold", "8": "removeFromCRL", "9": "privilegeWithdrawn", "10": "aACompromise" };
69
+ // CRLReason ::= ENUMERATED -- value 7 is "not used"; the rest are RFC 5280
70
+ // sec. 5.3.1. The legal set + names live once in pkix.CRL_REASON_NAMES (shared
71
+ // with the CRL reasonCode decoder, which surfaces the numeric code).
72
+ var CRL_REASONS = pkix.CRL_REASON_NAMES;
65
73
 
66
74
  // ---- shared leaves ---------------------------------------------------
67
75
 
68
76
  // A GeneralizedTime-only leaf. OCSP times (producedAt / thisUpdate / nextUpdate /
69
77
  // revocationTime) are GeneralizedTime, never UTCTime (RFC 6960); assert the tag
70
78
  // before the codec reads it so a UTCTime is rejected ocsp/bad-time.
71
- var GENERALIZED_TIME = schema.decode(function (n, ctx) {
72
- if (n.tagClass !== "universal" || n.tagNumber !== asn1.TAGS.GENERALIZED_TIME) {
73
- throw ctx.E("ocsp/bad-time", "OCSP time must be a GeneralizedTime (RFC 6960)");
74
- }
75
- return asn1.read.time(n);
76
- });
79
+ var GENERALIZED_TIME = pkix.generalizedTime(NS, { code: "ocsp/bad-time", message: "OCSP time must be a GeneralizedTime (RFC 6960)" });
77
80
 
78
81
  // OCSPResponseStatus decode-and-whitelist leaf. read.enumerated is tag-checked, so
79
82
  // an INTEGER-encoded status is rejected at the leaf (asn1/unexpected-tag).
80
83
  var OCSP_RESPONSE_STATUS = schema.decode(function (n, ctx) {
81
84
  var v = asn1.read.enumerated(n);
82
85
  var key = v.toString();
83
- if (!Object.prototype.hasOwnProperty.call(STATUS_NAMES, key)) throw ctx.E("ocsp/bad-response-status", "undefined OCSPResponseStatus " + key + " (RFC 6960 §4.2.1)");
86
+ if (!Object.prototype.hasOwnProperty.call(STATUS_NAMES, key)) throw ctx.E("ocsp/bad-response-status", "undefined OCSPResponseStatus " + key + " (RFC 6960 sec. 4.2.1)");
84
87
  return { code: Number(v), name: STATUS_NAMES[key] };
85
88
  });
86
89
 
@@ -88,25 +91,20 @@ var OCSP_RESPONSE_STATUS = schema.decode(function (n, ctx) {
88
91
  var CRL_REASON = schema.decode(function (n, ctx) {
89
92
  var v = asn1.read.enumerated(n);
90
93
  var key = v.toString();
91
- if (!Object.prototype.hasOwnProperty.call(CRL_REASONS, key)) throw ctx.E("ocsp/bad-revocation-reason", "undefined CRLReason " + key + " (RFC 5280 §5.3.1)");
94
+ if (!Object.prototype.hasOwnProperty.call(CRL_REASONS, key)) throw ctx.E("ocsp/bad-revocation-reason", "undefined CRLReason " + key + " (RFC 5280 sec. 5.3.1)");
92
95
  return CRL_REASONS[key];
93
96
  });
94
97
 
95
98
  // A certs [0] SEQUENCE OF Certificate element. Unlike the CMS CertificateChoices
96
- // (a tagged CHOICE), an OCSP certs element is a plain Certificate a universal
97
- // SEQUENCE (RFC 5280 §4.1) so assert that shape before surfacing its raw DER, and
99
+ // (a tagged CHOICE), an OCSP certs element is a plain Certificate -- a universal
100
+ // SEQUENCE (RFC 5280 sec. 4.1) -- so assert that shape before surfacing its raw DER, and
98
101
  // reject a non-SEQUENCE element rather than reporting arbitrary bytes as a cert.
99
102
  function certificateBytes() {
100
- return schema.decode(function (n, ctx) {
101
- if (n.tagClass !== "universal" || n.tagNumber !== asn1.TAGS.SEQUENCE || !n.children) {
102
- throw ctx.E("ocsp/bad-certs", "each certs element must be a Certificate (a SEQUENCE) (RFC 6960 §4.1.1/§4.2.1)");
103
- }
104
- return n.bytes;
105
- });
103
+ return pkix.rawNonEmptySequence(NS, { code: "ocsp/bad-certs", message: "each certs element must be a non-empty Certificate SEQUENCE (RFC 6960 sec. 4.1.1/sec. 4.2.1)" });
106
104
  }
107
105
 
108
- // requestorName [1] EXPLICIT GeneralName (RFC 6960 §4.1.1) validated + surfaced raw
109
- // via the shared pkix.generalName primitive (RFC 5280 §4.2.1.6), so a malformed
106
+ // requestorName [1] EXPLICIT GeneralName (RFC 6960 sec. 4.1.1) -- validated + surfaced raw
107
+ // via the shared pkix.generalName primitive (RFC 5280 sec. 4.2.1.6), so a malformed
110
108
  // GeneralName fails closed and the OCSP + TSP parsers cannot drift on this grammar.
111
109
  var GENERAL_NAME_RAW = pkix.generalName(NS, { code: "ocsp/bad-requestor-name" });
112
110
 
@@ -119,15 +117,43 @@ function _rawSignature(field) {
119
117
  return sig.bytes;
120
118
  }
121
119
 
122
- // certs [0] EXPLICIT SEQUENCE OF Certificate each element raw. Shared by the
120
+ // certs [0] EXPLICIT SEQUENCE OF Certificate -- each element raw. Shared by the
123
121
  // request Signature and the BasicOCSPResponse.
124
122
  var CERTS = schema.seqOf(certificateBytes(), { assert: "sequence", code: "ocsp/bad-certs", what: "certs" });
125
123
 
124
+ // Validate the OCSP protocol extension values in a decoded extension list --
125
+ // applied to every extension surface (request / single-request / response /
126
+ // single-response) so a hostile placement cannot dodge the syntax rule. The
127
+ // nonce is the one extension whose value the module names: Nonce ::= OCTET
128
+ // STRING (SIZE(1..128)) (RFC 9654 sec. 2.1, updating RFC 6960 sec. 4.4.1 -- the 128
129
+ // upper bound is the RFC 9654 responder rule; RFC 8954's was 32). The decoded
130
+ // nonce bytes are surfaced on the extension as `nonce` alongside the raw value;
131
+ // other extension values stay opaque (an unknown extension is representable,
132
+ // not a fault).
133
+ function _validateOcspExtensions(list) {
134
+ if (!list) return list;
135
+ for (var i = 0; i < list.length; i++) {
136
+ var ext = list[i];
137
+ if (ext.oid !== OID_OCSP_NONCE) continue;
138
+ var node;
139
+ try { node = asn1.decode(ext.value); }
140
+ catch (e) { throw NS.E("ocsp/bad-nonce", "the nonce extension value did not decode: " + ((e && e.message) || String(e)), e); }
141
+ if (!(node.tagClass === "universal" && node.tagNumber === asn1.TAGS.OCTET_STRING && node.content)) {
142
+ throw NS.E("ocsp/bad-nonce", "the nonce extension value must be an OCTET STRING (RFC 9654 sec. 2.1)");
143
+ }
144
+ if (node.content.length < 1 || node.content.length > 128) {
145
+ throw NS.E("ocsp/bad-nonce", "the nonce must be 1..128 octets (RFC 9654 sec. 2.1)");
146
+ }
147
+ ext.nonce = node.content;
148
+ }
149
+ return list;
150
+ }
151
+
126
152
  // ---- CertID (shared request + response) ------------------------------
127
153
 
128
154
  // CertID ::= SEQUENCE { hashAlgorithm AlgorithmIdentifier, issuerNameHash OCTET
129
155
  // STRING, issuerKeyHash OCTET STRING, serialNumber CertificateSerialNumber }
130
- // (RFC 6960 §4.1.1). The two hashes are surfaced raw for the caller to verify.
156
+ // (RFC 6960 sec. 4.1.1). The two hashes are surfaced raw for the caller to verify.
131
157
  var CERT_ID = schema.seq([
132
158
  schema.field("hashAlgorithm", ALGORITHM_IDENTIFIER),
133
159
  schema.field("issuerNameHash", schema.octetString()),
@@ -149,7 +175,7 @@ var CERT_ID = schema.seq([
149
175
  // ---- response tree ---------------------------------------------------
150
176
 
151
177
  // RevokedInfo ::= SEQUENCE { revocationTime GeneralizedTime, revocationReason [0]
152
- // EXPLICIT CRLReason OPTIONAL } (RFC 6960 §4.2.1). Walked on the [1] IMPLICIT node,
178
+ // EXPLICIT CRLReason OPTIONAL } (RFC 6960 sec. 4.2.1). Walked on the [1] IMPLICIT node,
153
179
  // so assert:"constructed" (the context tag replaced the universal SEQUENCE tag).
154
180
  var REVOKED_INFO = schema.seq([
155
181
  schema.field("revocationTime", GENERALIZED_TIME),
@@ -167,7 +193,7 @@ var REVOKED_INFO = schema.seq([
167
193
  });
168
194
 
169
195
  // CertStatus ::= CHOICE { good [0] IMPLICIT NULL, revoked [1] IMPLICIT RevokedInfo,
170
- // unknown [2] IMPLICIT NULL } (RFC 6960 §4.2.1). good/unknown are primitive empty
196
+ // unknown [2] IMPLICIT NULL } (RFC 6960 sec. 4.2.1). good/unknown are primitive empty
171
197
  // context nodes; revoked is a constructed context [1] whose body is RevokedInfo.
172
198
  var CERT_STATUS = schema.choice([
173
199
  { when: { tagClass: "context", tagNumber: 0 }, schema: schema.implicitNull(0) },
@@ -185,7 +211,7 @@ function _shapeCertStatus(field) {
185
211
 
186
212
  // SingleResponse ::= SEQUENCE { certID, certStatus, thisUpdate GeneralizedTime,
187
213
  // nextUpdate [0] EXPLICIT GeneralizedTime OPTIONAL, singleExtensions [1] EXPLICIT
188
- // Extensions OPTIONAL } (RFC 6960 §4.2.1). certStatus is consumed positionally
214
+ // Extensions OPTIONAL } (RFC 6960 sec. 4.2.1). certStatus is consumed positionally
189
215
  // (before thisUpdate), so its context [0] good arm cannot be confused with the
190
216
  // trailing nextUpdate [0].
191
217
  var SINGLE_RESPONSE = schema.seq([
@@ -204,12 +230,12 @@ var SINGLE_RESPONSE = schema.seq([
204
230
  certStatus: _shapeCertStatus(m.fields.certStatus),
205
231
  thisUpdate: m.fields.thisUpdate.value,
206
232
  nextUpdate: m.fields.nextUpdate.present ? m.fields.nextUpdate.value : null,
207
- singleExtensions: m.fields.singleExtensions.present ? m.fields.singleExtensions.value.result : null,
233
+ singleExtensions: m.fields.singleExtensions.present ? _validateOcspExtensions(m.fields.singleExtensions.value.result) : null,
208
234
  };
209
235
  },
210
236
  });
211
237
 
212
- // ResponderID ::= CHOICE { byName [1] Name, byKey [2] KeyHash } (RFC 6960 §4.2.1)
238
+ // ResponderID ::= CHOICE { byName [1] Name, byKey [2] KeyHash } (RFC 6960 sec. 4.2.1) --
213
239
  // both EXPLICIT (the module is EXPLICIT TAGS). byKey is an EXPLICIT-wrapped universal
214
240
  // OCTET STRING (0xA2 04 ...), NOT an IMPLICIT primitive [2].
215
241
  var RESPONDER_ID = schema.choice([
@@ -219,16 +245,16 @@ var RESPONDER_ID = schema.choice([
219
245
 
220
246
  function _shapeResponderID(field) {
221
247
  if (field.node.tagNumber === 1) return { byName: field.value.result };
222
- // RFC 6960 §4.2.1 KeyHash is the SHA-1 hash of the responder's public key, so a
248
+ // RFC 6960 sec. 4.2.1 -- KeyHash is the SHA-1 hash of the responder's public key, so a
223
249
  // conformant byKey ResponderID is exactly 20 octets; reject any other length.
224
250
  var keyHash = field.value;
225
- if (keyHash.length !== 20) throw NS.E("ocsp/bad-responder-id", "ResponderID byKey (KeyHash) must be a 20-byte SHA-1 hash (RFC 6960 §4.2.1)");
251
+ if (keyHash.length !== 20) throw NS.E("ocsp/bad-responder-id", "ResponderID byKey (KeyHash) must be a 20-byte SHA-1 hash (RFC 6960 sec. 4.2.1)");
226
252
  return { byKey: keyHash };
227
253
  }
228
254
 
229
255
  // ResponseData ::= SEQUENCE { version [0] EXPLICIT DEFAULT v1, responderID,
230
256
  // producedAt GeneralizedTime, responses SEQUENCE OF SingleResponse,
231
- // responseExtensions [1] EXPLICIT Extensions OPTIONAL } (RFC 6960 §4.2.1).
257
+ // responseExtensions [1] EXPLICIT Extensions OPTIONAL } (RFC 6960 sec. 4.2.1).
232
258
  var RESPONSE_DATA = schema.seq([
233
259
  schema.optional("version", VERSION, { tag: 0, explicit: true, default: 1, emptyCode: "ocsp/bad-version" }),
234
260
  schema.field("responderID", RESPONDER_ID),
@@ -245,15 +271,15 @@ var RESPONSE_DATA = schema.seq([
245
271
  responderID: _shapeResponderID(m.fields.responderID),
246
272
  producedAt: m.fields.producedAt.value,
247
273
  responses: m.fields.responses.value.items.map(function (it) { return it.value.result; }),
248
- responseExtensions: m.fields.responseExtensions.present ? m.fields.responseExtensions.value.result : null,
274
+ responseExtensions: m.fields.responseExtensions.present ? _validateOcspExtensions(m.fields.responseExtensions.value.result) : null,
249
275
  };
250
276
  },
251
277
  });
252
278
 
253
279
  // BasicOCSPResponse ::= SEQUENCE { tbsResponseData ResponseData, signatureAlgorithm
254
280
  // AlgorithmIdentifier, signature BIT STRING, certs [0] EXPLICIT SEQUENCE OF
255
- // Certificate OPTIONAL } (RFC 6960 §4.2.1). tbsResponseData.node.bytes is the exact
256
- // signed region (no CMS-style re-tag divergence the clean case).
281
+ // Certificate OPTIONAL } (RFC 6960 sec. 4.2.1). tbsResponseData.node.bytes is the exact
282
+ // signed region (no CMS-style re-tag divergence -- the clean case).
257
283
  var BASIC_OCSP_RESPONSE = schema.seq([
258
284
  schema.field("tbsResponseData", RESPONSE_DATA),
259
285
  schema.field("signatureAlgorithm", ALGORITHM_IDENTIFIER),
@@ -280,7 +306,7 @@ var BASIC_OCSP_RESPONSE = schema.seq([
280
306
  });
281
307
 
282
308
  // ResponseBytes ::= SEQUENCE { responseType OBJECT IDENTIFIER, response OCTET STRING }
283
- // (RFC 6960 §4.2.1). For id-pkix-ocsp-basic the response OCTET STRING content is a
309
+ // (RFC 6960 sec. 4.2.1). For id-pkix-ocsp-basic the response OCTET STRING content is a
284
310
  // fresh DER BasicOCSPResponse (decoded + walked); an unknown responseType is
285
311
  // recognized-and-deferred with a precise ocsp/unsupported-response-type.
286
312
  var RESPONSE_BYTES = schema.seq([
@@ -292,7 +318,7 @@ var RESPONSE_BYTES = schema.seq([
292
318
  var responseType = m.fields.responseType.value;
293
319
  var raw = m.fields.response.value;
294
320
  if (responseType !== OID_OCSP_BASIC) {
295
- throw NS.E("ocsp/unsupported-response-type", (ctx.oid.name(responseType) || responseType) + " is not id-pkix-ocsp-basic (RFC 6960 §4.2.1)");
321
+ throw NS.E("ocsp/unsupported-response-type", (ctx.oid.name(responseType) || responseType) + " is not id-pkix-ocsp-basic (RFC 6960 sec. 4.2.1)");
296
322
  }
297
323
  return {
298
324
  responseType: responseType,
@@ -305,9 +331,11 @@ var RESPONSE_BYTES = schema.seq([
305
331
  });
306
332
 
307
333
  // OCSPResponse ::= SEQUENCE { responseStatus OCSPResponseStatus, responseBytes [0]
308
- // EXPLICIT ResponseBytes OPTIONAL } (RFC 6960 §4.2.1). The status <-> responseBytes
334
+ // EXPLICIT ResponseBytes OPTIONAL } (RFC 6960 sec. 4.2.1). The status <-> responseBytes
309
335
  // biconditional (successful iff responseBytes present) is the OCSP-specific
310
- // cross-field guard.
336
+ // cross-field guard. The top-level reject code predates the
337
+ // `<prefix>/not-a-<structure>` convention (ocsp/not-a-request follows it) and
338
+ // stays frozen on the public error-code surface.
311
339
  var OCSP_RESPONSE = schema.seq([
312
340
  schema.field("responseStatus", OCSP_RESPONSE_STATUS),
313
341
  schema.optional("responseBytes", RESPONSE_BYTES, { tag: 0, explicit: true, emptyCode: "ocsp/bad-ocsp-response" }),
@@ -316,10 +344,10 @@ var OCSP_RESPONSE = schema.seq([
316
344
  build: function (m) {
317
345
  var status = m.fields.responseStatus.value;
318
346
  var present = m.fields.responseBytes.present;
319
- // RFC 6960 §4.2.1 a successful response MUST carry responseBytes; any other
347
+ // RFC 6960 sec. 4.2.1 -- a successful response MUST carry responseBytes; any other
320
348
  // status MUST NOT (there is nothing to convey but the status).
321
- if (status.code === 0 && !present) throw NS.E("ocsp/bad-response-bytes", "a successful OCSPResponse must carry responseBytes (RFC 6960 §4.2.1)");
322
- if (status.code !== 0 && present) throw NS.E("ocsp/bad-response-bytes", "a non-successful OCSPResponse must not carry responseBytes (RFC 6960 §4.2.1)");
349
+ if (status.code === 0 && !present) throw NS.E("ocsp/bad-response-bytes", "a successful OCSPResponse must carry responseBytes (RFC 6960 sec. 4.2.1)");
350
+ if (status.code !== 0 && present) throw NS.E("ocsp/bad-response-bytes", "a non-successful OCSPResponse must not carry responseBytes (RFC 6960 sec. 4.2.1)");
323
351
  var rb = present ? m.fields.responseBytes.value.result : null;
324
352
  return {
325
353
  responseStatus: status,
@@ -332,7 +360,7 @@ var OCSP_RESPONSE = schema.seq([
332
360
  // ---- request tree ----------------------------------------------------
333
361
 
334
362
  // Request ::= SEQUENCE { reqCert CertID, singleRequestExtensions [0] EXPLICIT
335
- // Extensions OPTIONAL } (RFC 6960 §4.1.1).
363
+ // Extensions OPTIONAL } (RFC 6960 sec. 4.1.1).
336
364
  var REQUEST = schema.seq([
337
365
  schema.field("reqCert", CERT_ID),
338
366
  schema.trailing([
@@ -343,13 +371,13 @@ var REQUEST = schema.seq([
343
371
  build: function (m) {
344
372
  return {
345
373
  certID: m.fields.reqCert.value.result,
346
- singleRequestExtensions: m.fields.singleRequestExtensions.present ? m.fields.singleRequestExtensions.value.result : null,
374
+ singleRequestExtensions: m.fields.singleRequestExtensions.present ? _validateOcspExtensions(m.fields.singleRequestExtensions.value.result) : null,
347
375
  };
348
376
  },
349
377
  });
350
378
 
351
379
  // Signature ::= SEQUENCE { signatureAlgorithm AlgorithmIdentifier, signature BIT
352
- // STRING, certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } (RFC 6960 §4.1.1).
380
+ // STRING, certs [0] EXPLICIT SEQUENCE OF Certificate OPTIONAL } (RFC 6960 sec. 4.1.1).
353
381
  var SIGNATURE = schema.seq([
354
382
  schema.field("signatureAlgorithm", ALGORITHM_IDENTIFIER),
355
383
  schema.field("signature", schema.bitString()),
@@ -369,7 +397,7 @@ var SIGNATURE = schema.seq([
369
397
 
370
398
  // TBSRequest ::= SEQUENCE { version [0] EXPLICIT DEFAULT v1, requestorName [1]
371
399
  // EXPLICIT GeneralName OPTIONAL, requestList SEQUENCE OF Request, requestExtensions
372
- // [2] EXPLICIT Extensions OPTIONAL } (RFC 6960 §4.1.1). requestorName is surfaced
400
+ // [2] EXPLICIT Extensions OPTIONAL } (RFC 6960 sec. 4.1.1). requestorName is surfaced
373
401
  // raw (GeneralName is a wide CHOICE with no factory yet).
374
402
  var TBS_REQUEST = schema.seq([
375
403
  schema.optional("version", VERSION, { tag: 0, explicit: true, default: 1, emptyCode: "ocsp/bad-version" }),
@@ -386,13 +414,13 @@ var TBS_REQUEST = schema.seq([
386
414
  version: m.fields.version.value,
387
415
  requestorName: rn.present ? { bytes: rn.value.bytes, tagClass: rn.value.tagClass, tagNumber: rn.value.tagNumber } : null,
388
416
  requestList: m.fields.requestList.value.items.map(function (it) { return it.value.result; }),
389
- requestExtensions: m.fields.requestExtensions.present ? m.fields.requestExtensions.value.result : null,
417
+ requestExtensions: m.fields.requestExtensions.present ? _validateOcspExtensions(m.fields.requestExtensions.value.result) : null,
390
418
  };
391
419
  },
392
420
  });
393
421
 
394
422
  // OCSPRequest ::= SEQUENCE { tbsRequest TBSRequest, optionalSignature [0] EXPLICIT
395
- // Signature OPTIONAL } (RFC 6960 §4.1.1). tbsRequest.node.bytes is the raw signed
423
+ // Signature OPTIONAL } (RFC 6960 sec. 4.1.1). tbsRequest.node.bytes is the raw signed
396
424
  // region for external verification.
397
425
  var OCSP_REQUEST = schema.seq([
398
426
  schema.field("tbsRequest", TBS_REQUEST),
@@ -402,11 +430,11 @@ var OCSP_REQUEST = schema.seq([
402
430
  build: function (m) {
403
431
  var tbs = m.fields.tbsRequest.value.result;
404
432
  var optionalSignature = m.fields.optionalSignature.present ? m.fields.optionalSignature.value.result : null;
405
- // RFC 6960 §4.1.2 if the request is signed, the requestor SHALL specify its
433
+ // RFC 6960 sec. 4.1.2 -- if the request is signed, the requestor SHALL specify its
406
434
  // name in requestorName (the identity the signature binds to). A signed request
407
435
  // without it is internally inconsistent; reject fail-closed.
408
436
  if (optionalSignature && tbs.requestorName === null) {
409
- throw NS.E("ocsp/missing-requestor-name", "a signed OCSPRequest must specify requestorName (RFC 6960 §4.1.2)");
437
+ throw NS.E("ocsp/missing-requestor-name", "a signed OCSPRequest must specify requestorName (RFC 6960 sec. 4.1.2)");
410
438
  }
411
439
  return {
412
440
  tbsRequestBytes: m.fields.tbsRequest.node.bytes,
@@ -482,6 +510,23 @@ var parseResponse = pkix.makeParser({ pemLabel: "OCSP RESPONSE", PemError: PemEr
482
510
  */
483
511
  function pemDecode(text, label) { return pkix.pemDecode(text, label || "OCSP RESPONSE", PemError); }
484
512
 
513
+ /**
514
+ * @primitive pki.schema.ocsp.pemEncode
515
+ * @signature pki.schema.ocsp.pemEncode(der, label?) -> string
516
+ * @since 0.1.23
517
+ * @status stable
518
+ * @spec RFC 7468, RFC 6960
519
+ * @related pki.schema.ocsp.pemDecode
520
+ *
521
+ * Wrap DER bytes in a PEM envelope (default label `OCSP RESPONSE`, the same
522
+ * default `pemDecode` reads; pass `"OCSP REQUEST"` for a request). Throws
523
+ * `PemError` on a malformed label.
524
+ *
525
+ * @example
526
+ * var pem = pki.schema.ocsp.pemEncode(der);
527
+ */
528
+ function pemEncode(der, label) { return pkix.pemEncode(der, label || "OCSP RESPONSE", PemError); }
529
+
485
530
  // An OCSPResponse root is the only registered structure that leads with an
486
531
  // ENUMERATED child (a SEQUENCE of 1-2: responseStatus + an optional context [0]
487
532
  // responseBytes). Every other root leads with a SEQUENCE (x509/crl/csr/ocsp-request),
@@ -499,7 +544,7 @@ function matchesResponse(root) {
499
544
  // An OCSPRequest root is a SEQUENCE of 1-2 whose first child is the tbsRequest
500
545
  // SEQUENCE (optionally followed by a context [0] EXPLICIT signature). It leads with
501
546
  // a SEQUENCE like x509/crl/csr, but those require EXACTLY 3 children (the
502
- // signed-envelope trio), while an OCSPRequest is 1-2 so it is excluded by arity.
547
+ // signed-envelope trio), while an OCSPRequest is 1-2 -- so it is excluded by arity.
503
548
  function matchesRequest(root) {
504
549
  var T = asn1.TAGS;
505
550
  var k = pkix.rootSequenceChildren(root, 1, 2);
@@ -513,6 +558,7 @@ module.exports = {
513
558
  parseRequest: parseRequest,
514
559
  parseResponse: parseResponse,
515
560
  pemDecode: pemDecode,
561
+ pemEncode: pemEncode,
516
562
  matchesRequest: matchesRequest,
517
563
  matchesResponse: matchesResponse,
518
564
  };