@blamejs/pki 0.1.21 → 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-crl.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * @slug crl
10
10
  *
11
11
  * @intro
12
- * X.509 Certificate Revocation List handling per RFC 5280 §5. `parse` turns a
12
+ * X.509 Certificate Revocation List handling per RFC 5280 sec. 5. `parse` turns a
13
13
  * DER or PEM CRL into a structured, fully-decoded object: version, issuer
14
14
  * distinguished name, this/next update as real `Date`s, the ordered list of
15
15
  * revoked certificates (serial + revocation date + entry extensions), and the
@@ -19,7 +19,7 @@
19
19
  * rules, and the raw `tbsCertList` bytes are returned for signature checking.
20
20
  *
21
21
  * @card
22
- * Parse DER / PEM X.509 CRLs into structured, validated fields revoked
22
+ * Parse DER / PEM X.509 CRLs into structured, validated fields -- revoked
23
23
  * serials with real-`Date` revocation times, named extensions, fail-closed.
24
24
  */
25
25
 
@@ -34,11 +34,13 @@ var PemError = frameworkError.PemError;
34
34
  var TAGS = asn1.TAGS;
35
35
 
36
36
 
37
- // CRLReason ::= ENUMERATED (RFC 5280 §5.3.1) value 7 is unused/reserved.
38
- var CRL_REASONS = [0n, 1n, 2n, 3n, 4n, 5n, 6n, 8n, 9n, 10n];
37
+ // CRLReason ::= ENUMERATED (RFC 5280 sec. 5.3.1) -- value 7 is unused/reserved.
38
+ // The legal set lives once in pkix.CRL_REASON_NAMES (shared with the OCSP
39
+ // RevokedInfo decoder, which surfaces the name; this decoder the numeric code).
40
+ var CRL_REASONS = pkix.CRL_REASON_NAMES;
39
41
 
40
42
  // Extension-value decoding is keyed off the STABLE dotted OID (resolved once at
41
- // load from the canonical name), not the mutable display name a caller's
43
+ // load from the canonical name), not the mutable display name -- a caller's
42
44
  // pki.oid.register() display override must not change parse behaviour.
43
45
  var OID_CRL_NUMBER = oid.byName("cRLNumber");
44
46
  var OID_REASON_CODE = oid.byName("reasonCode");
@@ -51,16 +53,19 @@ var NS = pkix.makeNS("crl", CrlError, oid);
51
53
  var ALGORITHM_IDENTIFIER = pkix.algorithmIdentifier(NS);
52
54
  var NAME = pkix.name(NS);
53
55
  var EXTENSIONS = pkix.extensions(NS);
54
- var TIME = schema.time(NS);
56
+ // pkix.time enforces the RFC 5280 encoding cutover on decode (sec. 5.1.2.4 -
57
+ // 5.1.2.6: thisUpdate / nextUpdate / revocationDate through 2049 must be
58
+ // UTCTime). invalidityDate stays GeneralizedTime-only (sec. 5.3.2, decodeExt).
59
+ var TIME = pkix.time(NS);
55
60
 
56
- // CRL Version ::= INTEGER { v1(0), v2(1) } a BARE INTEGER (not [0] EXPLICIT).
61
+ // CRL Version ::= INTEGER { v1(0), v2(1) } -- a BARE INTEGER (not [0] EXPLICIT).
57
62
  // A CRL is at most v2; reject an explicit v1 (DER forbids the default) and any
58
63
  // value >= 2. Do NOT reuse the certificate readVersion (it maps 2 -> v3).
59
64
  var CRL_VERSION = pkix.versionReader(NS, { "1": 2 });
60
65
 
61
66
  // The three cheap, high-value CRL extension values are decoded from their raw
62
- // extnValue octets (RFC 5280 §5.25.3); GeneralNames-based extensions
63
- // (issuingDistributionPoint, certificateIssuer, authorityKeyIdentifier, ) stay
67
+ // extnValue octets (RFC 5280 sec. 5.2/sec. 5.3); GeneralNames-based extensions
68
+ // (issuingDistributionPoint, certificateIssuer, authorityKeyIdentifier, ...) stay
64
69
  // raw with their bytes reachable. A malformed decoded value fails closed.
65
70
  function decodeExt(ext) {
66
71
  var value = ext.value;
@@ -69,10 +74,10 @@ function decodeExt(ext) {
69
74
  value = asn1.read.integer(asn1.decode(ext.value));
70
75
  if (value < 0n) throw new Error("cRLNumber must be non-negative (INTEGER 0..MAX)");
71
76
  } else if (ext.oid === OID_REASON_CODE) { // reasonCode ::= ENUMERATED (CRLReason)
72
- // read.enumerated asserts the ENUMERATED tag (a bare INTEGER is rejected)
77
+ // read.enumerated asserts the ENUMERATED tag (a bare INTEGER is rejected) --
73
78
  // the codec owns the tag check, so this reader need not repeat it.
74
79
  var reason = asn1.read.enumerated(asn1.decode(ext.value));
75
- if (CRL_REASONS.indexOf(reason) === -1) throw new Error("undefined CRLReason " + reason.toString());
80
+ if (!Object.prototype.hasOwnProperty.call(CRL_REASONS, reason.toString())) throw new Error("undefined CRLReason " + reason.toString());
76
81
  value = Number(reason);
77
82
  } else if (ext.oid === OID_INVALIDITY_DATE) { // invalidityDate ::= GeneralizedTime
78
83
  var n = asn1.decode(ext.value);
@@ -121,7 +126,7 @@ var REVOKED_LIST = schema.seqOf(REVOKED_ENTRY, {
121
126
  // revokedCertificates=SEQUENCE) are disambiguated by their universal tag;
122
127
  // crlExtensions is modeled as a trailing [0]..[0] so a stray non-[0] trailing
123
128
  // context tag is REJECTED (crl/bad-tbs), not silently ignored.
124
- // `signature` is consumed by the CERTIFICATE_LIST build (the §5.1.1.2
129
+ // `signature` is consumed by the CERTIFICATE_LIST build (the sec. 5.1.1.2
125
130
  // outer==inner agreement check reads tbsMatch.fields.signature.node.bytes);
126
131
  // the operator reads the surfaced outer signatureAlgorithm, which that
127
132
  // check proves byte-identical.
@@ -140,7 +145,7 @@ var TBS_CERTLIST = schema.seq([
140
145
  build: function (m) {
141
146
  return {
142
147
  version: m.fields.version.present ? m.fields.version.value : 1,
143
- issuer: m.fields.issuer.value.result, // Name is a seqOf field.value is the match; .result is the {rdns, dn} build
148
+ issuer: m.fields.issuer.value.result, // Name is a seqOf -> field.value is the match; .result is the {rdns, dn} build
144
149
  thisUpdate: m.fields.thisUpdate.value,
145
150
  nextUpdate: m.fields.nextUpdate.present ? m.fields.nextUpdate.value : null,
146
151
  revokedCertificates: m.fields.revokedCertificates.present ? m.fields.revokedCertificates.value.result : [],
@@ -151,22 +156,22 @@ var TBS_CERTLIST = schema.seq([
151
156
  });
152
157
 
153
158
  // CertificateList ::= SEQUENCE { tbsCertList, signatureAlgorithm, signatureValue }
154
- // the shared SIGNED envelope. The CRL-specific invariants (outer==inner
159
+ // -- the shared SIGNED envelope. The CRL-specific invariants (outer==inner
155
160
  // signatureAlgorithm agreement, non-empty issuer, v2-only extensions) live in the
156
161
  // build; the envelope owns the SEQUENCE-of-3 shape and signature extraction.
157
162
  var CERTIFICATE_LIST = pkix.signedEnvelope(NS, TBS_CERTLIST, {
158
163
  code: "crl/not-a-crl", what: "CertificateList",
159
164
  build: function (e) {
160
165
  var tbs = e.tbsMatch.result;
161
- // RFC 5280 §5.1.1.2 the outer signatureAlgorithm MUST equal tbsCertList.signature.
166
+ // RFC 5280 sec. 5.1.1.2 -- the outer signatureAlgorithm MUST equal tbsCertList.signature.
162
167
  if (!e.outerSignatureAlgorithmBytes.equals(e.tbsMatch.fields.signature.node.bytes)) {
163
- throw NS.E("crl/bad-signature-algorithm", "signatureAlgorithm must match tbsCertList.signature (RFC 5280 §5.1.1.2)");
168
+ throw NS.E("crl/bad-signature-algorithm", "signatureAlgorithm must match tbsCertList.signature (RFC 5280 sec. 5.1.1.2)");
164
169
  }
165
- // RFC 5280 §5.1.2.3 the issuer MUST be a non-empty distinguished name.
170
+ // RFC 5280 sec. 5.1.2.3 -- the issuer MUST be a non-empty distinguished name.
166
171
  if (!tbs.issuer.rdns.length) {
167
172
  throw NS.E("crl/bad-issuer", "issuer must be a non-empty distinguished name");
168
173
  }
169
- // RFC 5280 §5.1.2.1 crlExtensions / crlEntryExtensions appear only in a v2 CRL.
174
+ // RFC 5280 sec. 5.1.2.1 -- crlExtensions / crlEntryExtensions appear only in a v2 CRL.
170
175
  var hasExtensions = tbs.crlExtensionsPresent ||
171
176
  tbs.revokedCertificates.some(function (r) { return r.crlEntryExtensions.length > 0; });
172
177
  if (hasExtensions && tbs.version !== 2) {
@@ -202,7 +207,7 @@ var CERTIFICATE_LIST = pkix.signedEnvelope(NS, TBS_CERTLIST, {
202
207
  *
203
208
  * @example
204
209
  * var crl = pki.schema.crl.parse(der);
205
- * crl.revokedCertificates[0].serialNumberHex; // "0a3f"
210
+ * crl.revokedCertificates[0].serialNumberHex; // -> "0a3f..."
206
211
  */
207
212
  var parse = pkix.makeParser({ pemLabel: "X509 CRL", PemError: PemError, ErrorClass: CrlError, prefix: "crl", what: "CRL", topSchema: CERTIFICATE_LIST, ns: NS });
208
213
 
@@ -222,8 +227,24 @@ var parse = pkix.makeParser({ pemLabel: "X509 CRL", PemError: PemError, ErrorCla
222
227
  */
223
228
  function pemDecode(text, label) { return pkix.pemDecode(text, label || "X509 CRL", PemError); }
224
229
 
230
+ /**
231
+ * @primitive pki.schema.crl.pemEncode
232
+ * @signature pki.schema.crl.pemEncode(der, label?) -> string
233
+ * @since 0.1.23
234
+ * @status stable
235
+ * @spec RFC 7468, RFC 5280
236
+ * @related pki.schema.crl.pemDecode
237
+ *
238
+ * Wrap CRL DER bytes in a PEM envelope with 64-column base64 lines (default
239
+ * label `X509 CRL`, the RFC 7468 sec. 6 armor `pemDecode` expects back).
240
+ *
241
+ * @example
242
+ * var pem = pki.schema.crl.pemEncode(der);
243
+ */
244
+ function pemEncode(der, label) { return pkix.pemEncode(der, label || "X509 CRL", PemError); }
245
+
225
246
  // Certificate and CertificateList share the outer SEQUENCE-of-3 shape; a CRL is
226
- // distinguished by its tbsCertList the first tbs element is a bare INTEGER
247
+ // distinguished by its tbsCertList -- the first tbs element is a bare INTEGER
227
248
  // (version) or an AlgorithmIdentifier (signature) SEQUENCE, and crucially the
228
249
  // field at the certificate's Validity position is a bare Time (thisUpdate). The
229
250
  // orchestrator uses `matches` to route; a cert's tbs leads with a [0] EXPLICIT
@@ -232,7 +253,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || "X509 CRL
232
253
  function matches(root) {
233
254
  var tbs = pkix.signedEnvelopeTbs(root);
234
255
  if (!tbs) return false;
235
- // A certificate's tbs leads with [0] EXPLICIT version a CRL never does.
256
+ // A certificate's tbs leads with [0] EXPLICIT version -- a CRL never does.
236
257
  if (tbs.children[0] && tbs.children[0].tagClass === "context") return false;
237
258
  // Walk to the thisUpdate / validity position: skip an optional bare INTEGER
238
259
  // version, then signature (SEQUENCE) + issuer (SEQUENCE); the next element is
@@ -247,5 +268,6 @@ function matches(root) {
247
268
  module.exports = {
248
269
  parse: parse,
249
270
  pemDecode: pemDecode,
271
+ pemEncode: pemEncode,
250
272
  matches: matches,
251
273
  };