@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.
@@ -11,23 +11,23 @@
11
11
  * @intro
12
12
  * S/MIME Enhanced Security Services signed-attribute values per RFC 5035 (ESS)
13
13
  * and RFC 8551 (S/MIME 4.0). These are the DER-decoded VALUES of CMS signed
14
- * attributes they ride inside a `SignerInfo.signedAttrs`, so this is a
14
+ * attributes -- they ride inside a `SignerInfo.signedAttrs`, so this is a
15
15
  * companion decoder a CMS consumer invokes by attribute OID, NOT a top-level
16
16
  * format the schema orchestrator auto-routes.
17
17
  *
18
18
  * `parseSigningCertificate` / `parseSigningCertificateV2` decode the ESS
19
19
  * signing-certificate attributes that bind a signature to the exact certificate
20
- * that made it: each surfaces its list of `ESSCertID`(v2) the certificate hash
20
+ * that made it: each surfaces its list of `ESSCertID`(v2) -- the certificate hash
21
21
  * (raw), the hash algorithm (decoded for v2, or the implied SHA-1 for v1), and
22
22
  * the optional `issuerSerial` (issuer `GeneralNames` validated + surfaced raw,
23
- * serial as a BigInt + hex) plus the optional certificate policies.
23
+ * serial as a BigInt + hex) -- plus the optional certificate policies.
24
24
  * `parseSmimeCapabilities` decodes the ordered `SMIMECapabilities` list (each a
25
25
  * capability OID + raw parameters). `decodeAttribute` takes a CMS-shaped
26
26
  * `{ type, values }` attribute, enforces the single-`AttributeValue` rule
27
- * (RFC 8551 §2.5.2), routes on the attribute OID, and recognize-and-defers an
27
+ * (RFC 8551 sec. 2.5.2), routes on the attribute OID, and recognize-and-defers an
28
28
  * unknown attribute type with its raw values intact.
29
29
  *
30
- * Structure is decoded; verification is the consumer's the parser surfaces
30
+ * Structure is decoded; verification is the consumer's -- the parser surfaces
31
31
  * `certHash` + `hashAlgorithm` + `issuerSerial` so a verifier recomputes the
32
32
  * certificate hash (compose `webcrypto`) and matches the issuer/serial against
33
33
  * the actual signing certificate; it never recomputes a hash or trusts a cert.
@@ -36,7 +36,7 @@
36
36
  *
37
37
  * @card
38
38
  * Decode RFC 5035 ESS SigningCertificate / SigningCertificateV2 and RFC 8551
39
- * SMIMECapabilities signed-attribute values cert-hash binding, validated
39
+ * SMIMECapabilities signed-attribute values -- cert-hash binding, validated
40
40
  * issuer GeneralNames, ordered capability list, OID-dispatched, fail-closed.
41
41
  */
42
42
 
@@ -54,7 +54,7 @@ var TAGS = asn1.TAGS;
54
54
 
55
55
  var ALGID = pkix.algorithmIdentifier(NS);
56
56
 
57
- // Registry constants never a dotted literal in a format module.
57
+ // Registry constants -- never a dotted literal in a format module.
58
58
  var OID_SHA1 = oid.byName("sha1");
59
59
  var OID_SHA256 = oid.byName("sha256");
60
60
  var OID_SIGNING_CERTIFICATE = oid.byName("signingCertificate");
@@ -62,15 +62,15 @@ var OID_SIGNING_CERTIFICATE_V2 = oid.byName("signingCertificateV2");
62
62
  var OID_SMIME_CAPABILITIES = oid.byName("smimeCapabilities");
63
63
 
64
64
  // IssuerSerial ::= SEQUENCE { issuer GeneralNames, serialNumber CertificateSerialNumber }
65
- // (RFC 5035 App A). A bare universal SEQUENCE of exactly two fields distinct
65
+ // (RFC 5035 App A). A bare universal SEQUENCE of exactly two fields -- distinct
66
66
  // from the RFC 5755 attribute-certificate IssuerSerial (three fields, reached
67
67
  // IMPLICIT-tagged), so declared per-format. `issuer` composes the shared
68
68
  // GeneralNames factory (every CHOICE arm validated + surfaced raw, SIZE 1..MAX)
69
- // and is surfaced unchanged here: the RFC 5035 §5 directoryName narrowing applies
69
+ // and is surfaced unchanged here: the RFC 5035 sec. 5 directoryName narrowing applies
70
70
  // only to a non-attribute (public-key) certificate reference, and an ESSCertID
71
71
  // entry may reference an attribute certificate whose issuer is a full GeneralNames.
72
- // The FIRST cert is known to be the signing public-key cert (RFC 5035 §3), so the
73
- // directoryName rule is enforced there in the SigningCertificate build not here.
72
+ // The FIRST cert is known to be the signing public-key cert (RFC 5035 sec. 3), so the
73
+ // directoryName rule is enforced there -- in the SigningCertificate build -- not here.
74
74
  var GENERAL_NAME_DIRECTORY = 4;
75
75
  var ISSUER_SERIAL = schema.seq([
76
76
  schema.field("issuer", pkix.generalNames(NS, { code: "smime/bad-general-names" })),
@@ -86,21 +86,21 @@ var ISSUER_SERIAL = schema.seq([
86
86
  },
87
87
  });
88
88
 
89
- // RFC 5035 §5: the signing certificate (certs[0], the public-key cert that made
90
- // the signature per §3) MUST identify its issuer as exactly one directoryName [4].
89
+ // RFC 5035 sec. 5: the signing certificate (certs[0], the public-key cert that made
90
+ // the signature per sec. 3) MUST identify its issuer as exactly one directoryName [4].
91
91
  // Additional certs[1..] may reference attribute certificates, whose issuer is a
92
- // GeneralNames left unconstrained (a verifier-tier profile concern). `fail`
92
+ // GeneralNames -- left unconstrained (a verifier-tier profile concern). `fail`
93
93
  // throws the caller's code.
94
94
  function assertSignerIssuerIsDirectoryName(certs, fail) {
95
95
  if (!certs.length || !certs[0].issuerSerial) return;
96
96
  var names = certs[0].issuerSerial.issuer.names;
97
97
  if (names.length !== 1 || names[0].tagClass !== "context" || names[0].tagNumber !== GENERAL_NAME_DIRECTORY) {
98
- fail("the signing certificate's IssuerSerial issuer MUST be exactly one directoryName [4] GeneralName (RFC 5035 §5)");
98
+ fail("the signing certificate's IssuerSerial issuer MUST be exactly one directoryName [4] GeneralName (RFC 5035 sec. 5)");
99
99
  }
100
100
  }
101
101
 
102
102
  // PolicyInformation ::= SEQUENCE { policyIdentifier CertPolicyId,
103
- // policyQualifiers SEQUENCE OF PolicyQualifierInfo OPTIONAL } (RFC 5280 §4.2.1.4).
103
+ // policyQualifiers SEQUENCE OF PolicyQualifierInfo OPTIONAL } (RFC 5280 sec. 4.2.1.4).
104
104
  // Decode the policy OID (+ registry name); surface policyQualifiers raw.
105
105
  var POLICY_INFORMATION = schema.seq([
106
106
  schema.field("policyIdentifier", schema.oidLeaf()),
@@ -111,7 +111,7 @@ var POLICY_INFORMATION = schema.seq([
111
111
  var qualifiers = null;
112
112
  if (m.fields.policyQualifiers.present) {
113
113
  // The qualifier body stays raw (per the ESS scope), but the RFC 5280
114
- // §4.2.1.4 structure is validated fail-closed by the shared assertion the
114
+ // sec. 4.2.1.4 structure is validated fail-closed by the shared assertion -- the
115
115
  // same PolicyInformation shape the certificatePolicies decoder enforces.
116
116
  var q = m.fields.policyQualifiers.node;
117
117
  pkix.assertPolicyQualifiers(q, function (msg, cause) { throw ctx.E("smime/bad-policy-information", msg, cause); });
@@ -124,7 +124,7 @@ var POLICY_INFORMATION = schema.seq([
124
124
  };
125
125
  },
126
126
  });
127
- // policies SEQUENCE OF PolicyInformation OPTIONAL order-preserving; the ASN.1
127
+ // policies SEQUENCE OF PolicyInformation OPTIONAL -- order-preserving; the ASN.1
128
128
  // module carries no SIZE bound, so an explicitly present but empty list is legal.
129
129
  var POLICIES = schema.seqOf(POLICY_INFORMATION, {
130
130
  assert: "sequence", min: 0, code: "smime/bad-policies", what: "policies",
@@ -132,7 +132,7 @@ var POLICIES = schema.seqOf(POLICY_INFORMATION, {
132
132
  });
133
133
 
134
134
  // ESSCertID ::= SEQUENCE { certHash Hash, issuerSerial IssuerSerial OPTIONAL }
135
- // (RFC 5035 §5.4.2). Hash ::= OCTET STRING for v1 it is the SHA-1 hash of the
135
+ // (RFC 5035 sec. 5.4.2). Hash ::= OCTET STRING -- for v1 it is the SHA-1 hash of the
136
136
  // whole certificate with NO algorithm field, so the hash algorithm is SYNTHESIZED
137
137
  // as the implied SHA-1 to make v1 shape-compatible with v2 for a verifier.
138
138
  var ESS_CERT_ID = schema.seq([
@@ -151,7 +151,7 @@ var ESS_CERT_ID = schema.seq([
151
151
 
152
152
  // ESSCertIDv2 ::= SEQUENCE { hashAlgorithm AlgorithmIdentifier DEFAULT
153
153
  // {algorithm id-sha256}, certHash Hash, issuerSerial IssuerSerial OPTIONAL }
154
- // (RFC 5035 §5.4.1 / §4). The mandatory certHash OCTET STRING pivots between the
154
+ // (RFC 5035 sec. 5.4.1 / sec. 4). The mandatory certHash OCTET STRING pivots between the
155
155
  // two optional SEQUENCEs, so a leading SEQUENCE is hashAlgorithm and a leading
156
156
  // OCTET STRING means hashAlgorithm defaulted.
157
157
  var ESS_CERT_ID_V2 = schema.seq([
@@ -164,7 +164,7 @@ var ESS_CERT_ID_V2 = schema.seq([
164
164
  var hashAlgorithm;
165
165
  if (m.fields.hashAlgorithm.present) {
166
166
  var alg = m.fields.hashAlgorithm.value.result;
167
- // X.690 §11.5: a DEFAULT value MUST be omitted in DER. The default is
167
+ // X.690 sec. 11.5: a DEFAULT value MUST be omitted in DER. The default is
168
168
  // {algorithm id-sha256, parameters ABSENT}; an explicit encoding byte-equal
169
169
  // to it is a non-canonical DEFAULT and is rejected fail-closed (the
170
170
  // structured-value analogue of the primitive-DEFAULT rejection the toolkit
@@ -172,7 +172,7 @@ var ESS_CERT_ID_V2 = schema.seq([
172
172
  // parameters is NOT byte-equal to the params-absent default and decodes.
173
173
  if (alg.oid === OID_SHA256 && alg.parameters === null) {
174
174
  throw ctx.E("smime/non-canonical-default",
175
- "ESSCertIDv2 hashAlgorithm equal to the DEFAULT {algorithm id-sha256} MUST be omitted (X.690 §11.5)");
175
+ "ESSCertIDv2 hashAlgorithm equal to the DEFAULT {algorithm id-sha256} MUST be omitted (X.690 sec. 11.5)");
176
176
  }
177
177
  hashAlgorithm = { oid: alg.oid, name: alg.name, parameters: alg.parameters, defaulted: false };
178
178
  } else {
@@ -187,9 +187,9 @@ var ESS_CERT_ID_V2 = schema.seq([
187
187
  });
188
188
 
189
189
  // SigningCertificate ::= SEQUENCE { certs SEQUENCE OF ESSCertID,
190
- // policies SEQUENCE OF PolicyInformation OPTIONAL } (RFC 5035 §5.4.2). certs is
191
- // order-preserving RFC 5035 §3 makes the FIRST element the signing certificate
192
- // and non-empty (an empty certs cannot name a signing cert).
190
+ // policies SEQUENCE OF PolicyInformation OPTIONAL } (RFC 5035 sec. 5.4.2). certs is
191
+ // order-preserving -- RFC 5035 sec. 3 makes the FIRST element the signing certificate
192
+ // -- and non-empty (an empty certs cannot name a signing cert).
193
193
  function signingCertificateSchema(essCertId, code, what) {
194
194
  return schema.seq([
195
195
  schema.field("certs", schema.seqOf(essCertId, { assert: "sequence", min: 1, code: "smime/bad-certs", what: "certs" })),
@@ -210,8 +210,8 @@ var SIGNING_CERTIFICATE = signingCertificateSchema(ESS_CERT_ID, "smime/bad-signi
210
210
  var SIGNING_CERTIFICATE_V2 = signingCertificateSchema(ESS_CERT_ID_V2, "smime/bad-signing-certificate-v2", "SigningCertificateV2");
211
211
 
212
212
  // SMIMECapability ::= SEQUENCE { capabilityID OBJECT IDENTIFIER,
213
- // parameters ANY DEFINED BY capabilityID OPTIONAL } (RFC 8551 §2.5.2). Distinct
214
- // domain from AlgorithmIdentifier though isomorphic surfaced as capabilityID +
213
+ // parameters ANY DEFINED BY capabilityID OPTIONAL } (RFC 8551 sec. 2.5.2). Distinct
214
+ // domain from AlgorithmIdentifier though isomorphic -- surfaced as capabilityID +
215
215
  // raw parameters (the ANY-DEFINED-BY interpretation is a negotiation concern).
216
216
  var SMIME_CAPABILITY = schema.seq([
217
217
  schema.field("capabilityID", schema.oidLeaf()),
@@ -226,8 +226,8 @@ var SMIME_CAPABILITY = schema.seq([
226
226
  };
227
227
  },
228
228
  });
229
- // SMIMECapabilities ::= SEQUENCE OF SMIMECapability ordered by preference
230
- // (RFC 8551 §2.5.2), never sorted; an empty list is legal.
229
+ // SMIMECapabilities ::= SEQUENCE OF SMIMECapability -- ordered by preference
230
+ // (RFC 8551 sec. 2.5.2), never sorted; an empty list is legal.
231
231
  var SMIME_CAPABILITIES = schema.seqOf(SMIME_CAPABILITY, {
232
232
  assert: "sequence", min: 0, code: "smime/bad-capabilities", what: "SMIMECapabilities",
233
233
  build: function (m) { return { capabilities: m.items.map(function (it) { return it.value.result; }) }; },
@@ -241,7 +241,7 @@ var SMIME_CAPABILITIES = schema.seqOf(SMIME_CAPABILITY, {
241
241
  * @spec RFC 5035, RFC 2634
242
242
  * @related pki.schema.smime.parseSigningCertificateV2, pki.schema.smime.decodeAttribute
243
243
  *
244
- * Decode an ESS v1 `SigningCertificate` attribute value (RFC 5035 §5.4.2) the
244
+ * Decode an ESS v1 `SigningCertificate` attribute value (RFC 5035 sec. 5.4.2) -- the
245
245
  * raw `AttributeValue` a CMS consumer plucks off `SignerInfo.signedAttrs`. Returns
246
246
  * `{ certs, policies }`: each `certs` entry is `{ certHash, hashAlgorithm,
247
247
  * issuerSerial }` in wire order (the first is the signing certificate), where
@@ -266,12 +266,12 @@ var parseSigningCertificate = pkix.makeParser({ pemLabel: null, PemError: PemErr
266
266
  * @spec RFC 5035, RFC 5816
267
267
  * @related pki.schema.smime.parseSigningCertificate, pki.schema.smime.decodeAttribute
268
268
  *
269
- * Decode an ESS v2 `SigningCertificateV2` attribute value (RFC 5035 §5.4.1).
269
+ * Decode an ESS v2 `SigningCertificateV2` attribute value (RFC 5035 sec. 5.4.1).
270
270
  * Identical shape to v1 but each `certs` entry carries a real `hashAlgorithm`:
271
- * decoded when present, or the RFC 5035 §4 default `id-sha256` (with
271
+ * decoded when present, or the RFC 5035 sec. 4 default `id-sha256` (with
272
272
  * `defaulted: true`) when omitted. An explicit `hashAlgorithm` byte-equal to that
273
273
  * default is a non-canonical DER encoding and is rejected `smime/non-canonical-default`
274
- * (X.690 §11.5). Throws a typed `smime/*` error on malformed input.
274
+ * (X.690 sec. 11.5). Throws a typed `smime/*` error on malformed input.
275
275
  *
276
276
  * @example
277
277
  * var b = pki.asn1.build;
@@ -290,8 +290,8 @@ var parseSigningCertificateV2 = pkix.makeParser({ pemLabel: null, PemError: PemE
290
290
  * @spec RFC 8551
291
291
  * @related pki.schema.smime.decodeAttribute
292
292
  *
293
- * Decode an `SMIMECapabilities` attribute value (RFC 8551 §2.5.2) into
294
- * `{ capabilities }` an ORDERED list (preference order, never sorted), each
293
+ * Decode an `SMIMECapabilities` attribute value (RFC 8551 sec. 2.5.2) into
294
+ * `{ capabilities }` -- an ORDERED list (preference order, never sorted), each
295
295
  * `{ capabilityID, name, parameters }` with `parameters` the raw
296
296
  * `ANY DEFINED BY capabilityID` bytes (or `null`). Throws a typed `smime/*` error
297
297
  * on malformed input.
@@ -315,8 +315,8 @@ var parseSmimeCapabilities = pkix.makeParser({ pemLabel: null, PemError: PemErro
315
315
  * OID-dispatch convenience over the three value decoders for a CMS-shaped
316
316
  * `{ type, values }` attribute (the shape `cms.parse` surfaces on
317
317
  * `signerInfos[i].signedAttrs`). Enforces the single-`AttributeValue` rule
318
- * (RFC 8551 §2.5.2 / §2.5) a `values` length other than 1 is rejected
319
- * `smime/multi-valued-attribute` then routes on `attr.type`:
318
+ * (RFC 8551 sec. 2.5.2 / sec. 2.5) -- a `values` length other than 1 is rejected
319
+ * `smime/multi-valued-attribute` -- then routes on `attr.type`:
320
320
  * `signingCertificate` / `signingCertificateV2` / `smimeCapabilities` decode to
321
321
  * `{ kind, ...result }`; any other type is recognize-and-deferred
322
322
  * `smime/unsupported-attribute` (its `type`, registry `name`, and raw `values`
@@ -334,14 +334,14 @@ function decodeAttribute(attr) {
334
334
  throw new SmimeError("smime/bad-input", "decodeAttribute expects a CMS attribute { type, values }");
335
335
  }
336
336
  // Route on the attribute OID FIRST. The single-AttributeValue MUST (RFC 8551
337
- // §2.5.2) is specific to the ESS / SMIMECapabilities attributes, so it is
337
+ // sec. 2.5.2) is specific to the ESS / SMIMECapabilities attributes, so it is
338
338
  // enforced only inside the known-type branch; an unknown / custom attribute
339
339
  // recognize-and-defers with its raw values intact regardless of value count
340
340
  // (its own cardinality rules are not this decoder's to enforce).
341
341
  if (attr.type === OID_SIGNING_CERTIFICATE || attr.type === OID_SIGNING_CERTIFICATE_V2 || attr.type === OID_SMIME_CAPABILITIES) {
342
342
  if (attr.values.length !== 1) {
343
343
  throw new SmimeError("smime/multi-valued-attribute",
344
- "an ESS / SMIMECapabilities attribute MUST carry exactly one AttributeValue, got " + attr.values.length + " (RFC 8551 §2.5.2)");
344
+ "an ESS / SMIMECapabilities attribute MUST carry exactly one AttributeValue, got " + attr.values.length + " (RFC 8551 sec. 2.5.2)");
345
345
  }
346
346
  var value = attr.values[0];
347
347
  if (attr.type === OID_SIGNING_CERTIFICATE) { var v1 = parseSigningCertificate(value); return { kind: "signingCertificate", certs: v1.certs, policies: v1.policies }; }
package/lib/schema-tsp.js CHANGED
@@ -10,7 +10,7 @@
10
10
  *
11
11
  * @intro
12
12
  * RFC 3161 Time-Stamp Protocol handling. A `TimeStampResp` is what a client
13
- * receives from a TSA a `PKIStatusInfo` plus, on success, a `TimeStampToken`;
13
+ * receives from a TSA -- a `PKIStatusInfo` plus, on success, a `TimeStampToken`;
14
14
  * `parse` decodes it and enforces the status-to-token coupling (a granted
15
15
  * response carries a token, a rejection does not). A `TimeStampToken` is itself a
16
16
  * CMS SignedData whose encapsulated content is a `TSTInfo`, so `parseToken`
@@ -21,13 +21,14 @@
21
21
  * The parser surfaces everything a verifier needs and interprets nothing it
22
22
  * cannot: `messageImprint.hashAlgorithm` and the raw `hashedMessage`, the
23
23
  * `genTime` (with sub-second precision), the `serialNumber` and `nonce` (lossless,
24
- * as BigInt + hex), and the `policy`. The imprint-to-request and nonce-to-request
25
- * round-trips, the ESS signing-certificate binding, the timestamping EKU, and the
26
- * signature are verification-layer concerns above parse altitude. DER-only,
27
- * fail-closed.
24
+ * as BigInt + hex), and the `policy`. The PRESENCE of the ESS
25
+ * SigningCertificate(V2) attribute is asserted at parse (a structural token
26
+ * property); the imprint-to-request and nonce-to-request round-trips, the ESS
27
+ * hash-vs-certificate binding, the timestamping EKU, and the signature are
28
+ * verification-layer concerns above parse altitude. DER-only, fail-closed.
28
29
  *
29
30
  * @card
30
- * Parse DER / PEM RFC 3161 timestamp responses and tokens per-response status,
31
+ * Parse DER / PEM RFC 3161 timestamp responses and tokens -- per-response status,
31
32
  * the TSTInfo payload (imprint, genTime, serial, nonce, accuracy), raw verifier
32
33
  * inputs, single-signer token composition over CMS, fail-closed.
33
34
  */
@@ -47,16 +48,22 @@ var NS = pkix.makeNS("tsp", TspError, oid);
47
48
  var ALGORITHM_IDENTIFIER = pkix.algorithmIdentifier(NS);
48
49
  var EXTENSION = pkix.extension(NS);
49
50
 
50
- // TSTInfo.version is INTEGER { v1(1) } the only legal value is 1.
51
+ // TSTInfo.version is INTEGER { v1(1) } -- the only legal value is 1.
51
52
  var VERSION = pkix.versionReader(NS, { "1": 1 });
52
53
 
53
54
  // id-ct-TSTInfo is the eContentType that identifies a timestamp token (RFC 3161
54
- // §2.4.2); resolved from the registry, never a dotted literal.
55
+ // sec. 2.4.2); resolved from the registry, never a dotted literal.
55
56
  var OID_TST_INFO = oid.byName("tSTInfo");
56
57
 
58
+ // The ESS certificate-identifier attributes: RFC 3161 sec. 2.4.2 requires the TSA
59
+ // certificate identifier as a SigningCertificate signed attribute; RFC 5816
60
+ // additionally permits SigningCertificateV2.
61
+ var OID_SIGNING_CERT = oid.byName("signingCertificate");
62
+ var OID_SIGNING_CERT_V2 = oid.byName("signingCertificateV2");
63
+
57
64
  var TAGS = asn1.TAGS;
58
65
 
59
- // PKIFailureInfo ::= BIT STRING NamedBitList (RFC 3161 §2.4.2). bit 0 is the MSB of
66
+ // PKIFailureInfo ::= BIT STRING NamedBitList (RFC 3161 sec. 2.4.2). bit 0 is the MSB of
60
67
  // the first content octet; a set bit outside the named set is surfaced by index.
61
68
  var FAILURE_BITS = {
62
69
  0: "badAlg", 2: "badRequest", 5: "badDataFormat", 14: "timeNotAvailable",
@@ -65,33 +72,23 @@ var FAILURE_BITS = {
65
72
 
66
73
  // ---- shared leaves ---------------------------------------------------
67
74
 
68
- // GeneralizedTime-only leaf. RFC 3161 §2.4.2 requires genTime to be GeneralizedTime,
75
+ // GeneralizedTime-only leaf. RFC 3161 sec. 2.4.2 requires genTime to be GeneralizedTime,
69
76
  // never UTCTime; assert the tag before the (fractional-capable) codec read.
70
- var GEN_TIME = schema.decode(function (n, ctx) {
71
- if (n.tagClass !== "universal" || n.tagNumber !== TAGS.GENERALIZED_TIME) {
72
- throw ctx.E("tsp/bad-gentime", "genTime must be a GeneralizedTime (RFC 3161 §2.4.2)");
73
- }
74
- // RFC 3161 genTime may carry sub-second precision (X.690 §11.7 fractional profile).
75
- return asn1.read.time(n, { allowFractional: true });
76
- });
77
+ // RFC 3161 genTime may carry sub-second precision (X.690 sec. 11.7 fractional profile).
78
+ var GEN_TIME = pkix.generalizedTime(NS, { code: "tsp/bad-gentime", message: "genTime must be a GeneralizedTime (RFC 3161 sec. 2.4.2)", allowFractional: true });
77
79
 
78
- // tsa [0] EXPLICIT GeneralName (RFC 3161 §2.4.2) validated + surfaced raw via the
79
- // shared pkix.generalName primitive (RFC 5280 §4.2.1.6), which checks the chosen
80
+ // tsa [0] EXPLICIT GeneralName (RFC 3161 sec. 2.4.2) -- validated + surfaced raw via the
81
+ // shared pkix.generalName primitive (RFC 5280 sec. 4.2.1.6), which checks the chosen
80
82
  // alternative's form and content so a malformed GeneralName fails closed.
81
83
  var GENERAL_NAME_RAW = pkix.generalName(NS, { code: "tsp/bad-tsa" });
82
84
 
83
- // A PKIFreeText element MUST be a UTF8String (RFC 3161 §2.4.2).
84
- var UTF8_TEXT = schema.decode(function (n, ctx) {
85
- if (n.tagClass !== "universal" || n.tagNumber !== TAGS.UTF8_STRING) {
86
- throw ctx.E("tsp/bad-status-info", "PKIFreeText elements must be UTF8String");
87
- }
88
- return asn1.read.string(n);
89
- });
85
+ // A PKIFreeText element MUST be a UTF8String (RFC 3161 sec. 2.4.2).
86
+ var UTF8_TEXT = pkix.utf8Text(NS, { code: "tsp/bad-status-info", message: "PKIFreeText elements must be UTF8String" });
90
87
 
91
88
  // ---- MessageImprint --------------------------------------------------
92
89
 
93
90
  // MessageImprint ::= SEQUENCE { hashAlgorithm AlgorithmIdentifier, hashedMessage
94
- // OCTET STRING } (RFC 3161 §2.4.1). hashedMessage is a digest surfaced RAW.
91
+ // OCTET STRING } (RFC 3161 sec. 2.4.1). hashedMessage is a digest -- surfaced RAW.
95
92
  var MESSAGE_IMPRINT = schema.seq([
96
93
  schema.field("hashAlgorithm", ALGORITHM_IDENTIFIER),
97
94
  schema.field("hashedMessage", schema.octetString()),
@@ -106,7 +103,7 @@ var MESSAGE_IMPRINT = schema.seq([
106
103
 
107
104
  // Accuracy ::= SEQUENCE { seconds INTEGER OPTIONAL, millis [0] IMPLICIT INTEGER
108
105
  // (1..999) OPTIONAL, micros [1] IMPLICIT INTEGER (1..999) OPTIONAL } (RFC 3161
109
- // §2.4.2). Every sub-field is optional; a missing one defaults to 0.
106
+ // sec. 2.4.2). Every sub-field is optional; a missing one defaults to 0.
110
107
  var ACCURACY = schema.seq([
111
108
  schema.optional("seconds", schema.integerLeaf(), { whenUniversal: [TAGS.INTEGER] }),
112
109
  schema.trailing([
@@ -119,13 +116,19 @@ var ACCURACY = schema.seq([
119
116
  function sub(f) {
120
117
  if (!f.present) return 0;
121
118
  var v = f.value;
122
- // millis / micros are constrained to 1..999 (RFC 3161 §2.4.2); 0 is also
119
+ // millis / micros are constrained to 1..999 (RFC 3161 sec. 2.4.2); 0 is also
123
120
  // excluded (and non-canonical for a positive count).
124
121
  if (v < 1n || v > 999n) throw NS.E("tsp/bad-accuracy", "Accuracy millis/micros must be in 1..999");
125
122
  return Number(v);
126
123
  }
124
+ // Accuracy is the +/- deviation added to and subtracted from genTime (RFC
125
+ // 3161 sec. 2.4.2), so a negative seconds inverts the time window a verifier
126
+ // computes -- the same semantically-impossible class as a 0 millis/micros.
127
+ if (m.fields.seconds.present && m.fields.seconds.value < 0n) {
128
+ throw NS.E("tsp/bad-accuracy", "Accuracy seconds must not be negative (RFC 3161 sec. 2.4.2)");
129
+ }
127
130
  return {
128
- // seconds is an unbounded INTEGER keep it lossless as a BigInt (millis /
131
+ // seconds is an unbounded INTEGER -- keep it lossless as a BigInt (millis /
129
132
  // micros are constrained to 1..999, so Number is exact for those).
130
133
  seconds: m.fields.seconds.present ? m.fields.seconds.value : 0n,
131
134
  millis: sub(m.fields.millis),
@@ -139,7 +142,7 @@ var ACCURACY = schema.seq([
139
142
  // TSTInfo ::= SEQUENCE { version, policy, messageImprint, serialNumber, genTime,
140
143
  // accuracy OPTIONAL, ordering DEFAULT FALSE, nonce OPTIONAL, tsa [0] EXPLICIT
141
144
  // GeneralName OPTIONAL, extensions [1] IMPLICIT Extensions OPTIONAL } (RFC 3161
142
- // §2.4.2). The five mandatory fields come first; the optionals are consumed by tag.
145
+ // sec. 2.4.2). The five mandatory fields come first; the optionals are consumed by tag.
143
146
  var TST_INFO = schema.seq([
144
147
  schema.field("version", VERSION),
145
148
  schema.field("policy", schema.oidLeaf()),
@@ -156,9 +159,9 @@ var TST_INFO = schema.seq([
156
159
  ], {
157
160
  assert: "sequence", code: "tsp/bad-tst-info", what: "TSTInfo",
158
161
  build: function (m, ctx) {
159
- // ordering BOOLEAN DEFAULT FALSE an explicit FALSE must be omitted (DER).
162
+ // ordering BOOLEAN DEFAULT FALSE -- an explicit FALSE must be omitted (DER).
160
163
  if (m.fields.ordering.present && m.fields.ordering.value === false) {
161
- throw NS.E("tsp/bad-ordering", "ordering is BOOLEAN DEFAULT FALSE an explicit FALSE must be omitted");
164
+ throw NS.E("tsp/bad-ordering", "ordering is BOOLEAN DEFAULT FALSE -- an explicit FALSE must be omitted");
162
165
  }
163
166
  var policy = m.fields.policy.value;
164
167
  var tsa = m.fields.tsa;
@@ -187,7 +190,7 @@ var TST_INFO = schema.seq([
187
190
  // ---- PKIStatusInfo / TimeStampResp -----------------------------------
188
191
 
189
192
  // PKIStatusInfo ::= SEQUENCE { status PKIStatus (INTEGER), statusString PKIFreeText
190
- // OPTIONAL, failInfo PKIFailureInfo OPTIONAL } (RFC 3161 §2.4.2, from RFC 4210).
193
+ // OPTIONAL, failInfo PKIFailureInfo OPTIONAL } (RFC 3161 sec. 2.4.2, from RFC 4210).
191
194
  var PKI_STATUS_INFO = schema.seq([
192
195
  schema.field("status", schema.integerLeaf()),
193
196
  schema.optional("statusString", schema.seqOf(UTF8_TEXT, { assert: "sequence", min: 1, code: "tsp/bad-status-info", what: "statusString" }), { whenUniversal: [TAGS.SEQUENCE] }),
@@ -197,7 +200,7 @@ var PKI_STATUS_INFO = schema.seq([
197
200
  build: function (m) {
198
201
  var status = m.fields.status.value;
199
202
  // PKIStatus ::= INTEGER { granted(0) .. revocationNotification(5) }.
200
- if (status < 0n || status > 5n) throw NS.E("tsp/bad-status", "PKIStatus " + status + " is outside 0..5 (RFC 3161 §2.4.2)");
203
+ if (status < 0n || status > 5n) throw NS.E("tsp/bad-status", "PKIStatus " + status + " is outside 0..5 (RFC 3161 sec. 2.4.2)");
201
204
  var failInfo = null;
202
205
  if (m.fields.failInfo.present) {
203
206
  var bs = m.fields.failInfo.value;
@@ -212,7 +215,7 @@ var PKI_STATUS_INFO = schema.seq([
212
215
  },
213
216
  });
214
217
 
215
- // X.690 §11.2.2 a BIT STRING typed with a NamedBitList (PKIFailureInfo) MUST have
218
+ // X.690 sec. 11.2.2 -- a BIT STRING typed with a NamedBitList (PKIFailureInfo) MUST have
216
219
  // all trailing 0 bits removed under DER: no trailing all-zero content octet, and the
217
220
  // declared unusedBits must sit exactly below the lowest set bit of the last octet (so
218
221
  // the encoding is minimal). The empty value encodes as 0 content octets, 0 unusedBits.
@@ -221,15 +224,15 @@ function _assertMinimalNamedBits(unusedBits, bytes) {
221
224
  }
222
225
 
223
226
  // Decode the set NamedBitList bits to their RFC 3161 names (bit 0 = MSB of byte 0).
224
- // A set bit outside the defined set is an unsupported PKIFailureInfo value a client
225
- // MUST error on a failInfo it does not understand (RFC 3161 §2.4.2), so reject it
227
+ // A set bit outside the defined set is an unsupported PKIFailureInfo value -- a client
228
+ // MUST error on a failInfo it does not understand (RFC 3161 sec. 2.4.2), so reject it
226
229
  // rather than surfacing an opaque "bitN".
227
230
  function _namedBits(bytes) {
228
231
  var out = [];
229
232
  for (var i = 0; i < bytes.length * 8; i++) {
230
233
  if ((bytes[i >> 3] >> (7 - (i & 7))) & 1) {
231
234
  var nm = FAILURE_BITS[i];
232
- if (!nm) throw NS.E("tsp/bad-failinfo", "unsupported PKIFailureInfo bit " + i + " (RFC 3161 §2.4.2)");
235
+ if (!nm) throw NS.E("tsp/bad-failinfo", "unsupported PKIFailureInfo bit " + i + " (RFC 3161 sec. 2.4.2)");
233
236
  out.push(nm);
234
237
  }
235
238
  }
@@ -237,8 +240,10 @@ function _namedBits(bytes) {
237
240
  }
238
241
 
239
242
  // TimeStampResp ::= SEQUENCE { status PKIStatusInfo, timeStampToken TimeStampToken
240
- // OPTIONAL } (RFC 3161 §2.4.2). There is NO version field. The token is a CMS
243
+ // OPTIONAL } (RFC 3161 sec. 2.4.2). There is NO version field. The token is a CMS
241
244
  // ContentInfo, decoded via cms.parse; the status-to-token coupling is load-bearing.
245
+ // The top-level reject code predates the `<prefix>/not-a-<structure>` convention
246
+ // and stays frozen on the public error-code surface.
242
247
  var TIME_STAMP_RESP = schema.seq([
243
248
  schema.field("status", PKI_STATUS_INFO),
244
249
  schema.optional("timeStampToken", schema.any(), { whenUniversal: [TAGS.SEQUENCE] }),
@@ -247,15 +252,15 @@ var TIME_STAMP_RESP = schema.seq([
247
252
  build: function (m) {
248
253
  var status = m.fields.status.value.result;
249
254
  var present = m.fields.timeStampToken.present;
250
- // RFC 3161 §2.4.2 granted / grantedWithMods carry a token; any other status
255
+ // RFC 3161 sec. 2.4.2 -- granted / grantedWithMods carry a token; any other status
251
256
  // (rejection / waiting / revocation*) MUST NOT.
252
257
  var granted = status.status === 0 || status.status === 1;
253
- if (granted && !present) throw NS.E("tsp/missing-token", "a granted TimeStampResp must carry a timeStampToken (RFC 3161 §2.4.2)");
254
- if (!granted && present) throw NS.E("tsp/unexpected-token", "a non-granted TimeStampResp must not carry a timeStampToken (RFC 3161 §2.4.2)");
258
+ if (granted && !present) throw NS.E("tsp/missing-token", "a granted TimeStampResp must carry a timeStampToken (RFC 3161 sec. 2.4.2)");
259
+ if (!granted && present) throw NS.E("tsp/unexpected-token", "a non-granted TimeStampResp must not carry a timeStampToken (RFC 3161 sec. 2.4.2)");
255
260
  // failInfo is the reason a request was rejected, so it is present ONLY when the
256
- // status is rejection(2) not on granted(0/1) nor on waiting / revocation* (3/4/5).
257
- if (status.status !== 2 && status.failInfo) throw NS.E("tsp/unexpected-failinfo", "failInfo is present only when the status is rejection(2) (RFC 3161 §2.4.2)");
258
- // A granted response's timeStampToken MUST be a well-formed TimeStampToken
261
+ // status is rejection(2) -- not on granted(0/1) nor on waiting / revocation* (3/4/5).
262
+ if (status.status !== 2 && status.failInfo) throw NS.E("tsp/unexpected-failinfo", "failInfo is present only when the status is rejection(2) (RFC 3161 sec. 2.4.2)");
263
+ // A granted response's timeStampToken MUST be a well-formed TimeStampToken --
259
264
  // decode it (composing the CMS parser) rather than surfacing arbitrary SEQUENCE
260
265
  // bytes as a token; a malformed token fails the response parse.
261
266
  return {
@@ -275,8 +280,8 @@ var TIME_STAMP_RESP = schema.seq([
275
280
  * @spec RFC 3161
276
281
  * @related pki.schema.tsp.parseToken, pki.schema.tsp.parse
277
282
  *
278
- * Parse a bare `TSTInfo` payload (a DER `Buffer`) the structure a timestamp token
279
- * encapsulates into `{ version, policy, messageImprint, serialNumber, genTime,
283
+ * Parse a bare `TSTInfo` payload (a DER `Buffer`) -- the structure a timestamp token
284
+ * encapsulates -- into `{ version, policy, messageImprint, serialNumber, genTime,
280
285
  * accuracy, ordering, nonce, tsa, extensions }`. `messageImprint.hashedMessage` is
281
286
  * the raw digest; `serialNumber` / `nonce` are lossless (BigInt + hex); `genTime` is
282
287
  * a `Date` with sub-second precision. A malformed structure throws a typed
@@ -299,7 +304,7 @@ var parseTstInfo = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorCl
299
304
  *
300
305
  * Parse a DER `Buffer` or a PEM string into a `TimeStampResp`: `{ status,
301
306
  * statusString, failInfo, timeStampToken }`. The status-to-token coupling is
302
- * enforced a granted response (status 0/1) carries `timeStampToken` (surfaced raw
307
+ * enforced -- a granted response (status 0/1) carries `timeStampToken` (surfaced raw
303
308
  * for `parseToken`), any other status does not. `failInfo` decodes the
304
309
  * `PKIFailureInfo` named bits. A malformed structure throws a typed `TspError`.
305
310
  *
@@ -318,13 +323,17 @@ var parse = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: Ts
318
323
  * @spec RFC 3161, RFC 5652
319
324
  * @related pki.schema.tsp.parse, pki.schema.cms.parse
320
325
  *
321
- * Parse a `TimeStampToken` (a DER `Buffer` or PEM) a CMS SignedData whose
326
+ * Parse a `TimeStampToken` (a DER `Buffer` or PEM) -- a CMS SignedData whose
322
327
  * encapsulated content is a `TSTInfo`. Composes `pki.schema.cms.parse`, asserts the
323
328
  * `id-ct-TSTInfo` content type (`tsp/wrong-econtent-type`), that the content is
324
- * attached (`tsp/detached-token`), and the single-signer rule (`tsp/multi-signer`,
325
- * RFC 3161 §2.4.2), then decodes the inner `TSTInfo`. Returns `{ tstInfo, eContent,
326
- * signerInfo, certificates }` — the decoded payload, the raw eContent bytes a verifier
327
- * hashes for the CMS message-digest, and the CMS signer material.
329
+ * attached (`tsp/detached-token`), the single-signer rule (`tsp/multi-signer`,
330
+ * RFC 3161 sec. 2.4.2), and that the signerInfo carries a SigningCertificate or
331
+ * SigningCertificateV2 signed attribute (`tsp/missing-signing-certificate`,
332
+ * RFC 3161 sec. 2.4.2 / RFC 5816 -- the hash-vs-certificate binding stays a
333
+ * verification-layer concern), then decodes the inner `TSTInfo`. Returns
334
+ * `{ tstInfo, eContent, signerInfo, certificates }` -- the decoded payload, the raw
335
+ * eContent bytes a verifier hashes for the CMS message-digest, and the CMS signer
336
+ * material.
328
337
  *
329
338
  * @example
330
339
  * var token = pki.schema.tsp.parseToken(tokenDer);
@@ -333,10 +342,10 @@ var parse = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: Ts
333
342
  */
334
343
  function parseToken(input) {
335
344
  // De-armor with TSP's label-agnostic PEM rules first (RFC 3161 has no standard
336
- // label), THEN hand DER to the CMS parser otherwise cms.parse would reject any
345
+ // label), THEN hand DER to the CMS parser -- otherwise cms.parse would reject any
337
346
  // PEM block not labeled "CMS" before the TSP checks run.
338
347
  var der = pkix.coerceToDer(input, { pemLabel: null, PemError: PemError, ErrorClass: TspError, prefix: "tsp" });
339
- // Wrap the CMS decode so tsp.parse / parseToken keep their typed-TspError contract
348
+ // Wrap the CMS decode so tsp.parse / parseToken keep their typed-TspError contract --
340
349
  // a malformed token surfaces tsp/bad-token, not a bare cms/* error.
341
350
  var signed;
342
351
  try { signed = cms.parse(der); }
@@ -344,13 +353,35 @@ function parseToken(input) {
344
353
  if (e instanceof TspError) throw e;
345
354
  throw new TspError("tsp/bad-token", "the timeStampToken did not decode as a CMS SignedData: " + ((e && e.message) || String(e)), e);
346
355
  }
356
+ // cms.parse decodes every CMS content type; a TimeStampToken MUST be a
357
+ // SignedData (RFC 3161 sec. 2.4.2). Gate on the dispatched content type before
358
+ // any SignedData-only field access -- an AuthenticatedData / EnvelopedData
359
+ // whose eContentType happens to be id-ct-TSTInfo would otherwise reach a raw
360
+ // TypeError on the absent signerInfos.
361
+ if (signed.contentTypeName !== "signedData") {
362
+ throw new TspError("tsp/not-signed-data", "a TimeStampToken must be a CMS SignedData, got " + (signed.contentTypeName || signed.contentType));
363
+ }
347
364
  var encap = signed.encapContentInfo;
348
365
  if (encap.eContentType !== OID_TST_INFO) {
349
366
  throw new TspError("tsp/wrong-econtent-type", "a TimeStampToken must encapsulate id-ct-TSTInfo, got " + encap.eContentType);
350
367
  }
351
- if (encap.eContent === null) throw new TspError("tsp/detached-token", "a TimeStampToken must carry attached eContent (RFC 3161 §2.4.2)");
368
+ if (encap.eContent === null) throw new TspError("tsp/detached-token", "a TimeStampToken must carry attached eContent (RFC 3161 sec. 2.4.2)");
352
369
  if (signed.signerInfos.length !== 1) {
353
- throw new TspError("tsp/multi-signer", "a TimeStampToken must contain exactly one (TSA) signerInfo (RFC 3161 §2.4.2)");
370
+ throw new TspError("tsp/multi-signer", "a TimeStampToken must contain exactly one (TSA) signerInfo (RFC 3161 sec. 2.4.2)");
371
+ }
372
+ // RFC 3161 sec. 2.4.2 / RFC 5816 -- the TSA certificate identifier MUST be present
373
+ // as a SigningCertificate (or SigningCertificateV2) signed attribute. Presence
374
+ // is a structural token property, same altitude as the single-signer rule; the
375
+ // hash-vs-certificate BINDING stays a verification-layer concern. signedAttrs
376
+ // is non-null here: the CMS parse already required it for a non-id-data
377
+ // eContentType (RFC 5652 sec. 5.3), and the eContentType is id-ct-TSTInfo.
378
+ var signedAttrs = signed.signerInfos[0].signedAttrs || [];
379
+ var hasSigningCert = false;
380
+ for (var a = 0; a < signedAttrs.length; a++) {
381
+ if (signedAttrs[a].type === OID_SIGNING_CERT || signedAttrs[a].type === OID_SIGNING_CERT_V2) { hasSigningCert = true; break; }
382
+ }
383
+ if (!hasSigningCert) {
384
+ throw new TspError("tsp/missing-signing-certificate", "a TimeStampToken signerInfo must carry a SigningCertificate or SigningCertificateV2 signed attribute (RFC 3161 sec. 2.4.2, RFC 5816)");
354
385
  }
355
386
  var tstInfo;
356
387
  try { tstInfo = schema.embeddedDer(TST_INFO, encap.eContent, NS, { code: "tsp/bad-der", what: "the encapsulated TSTInfo" }); }
@@ -359,7 +390,7 @@ function parseToken(input) {
359
390
  throw new TspError("tsp/bad-der", "the encapsulated TSTInfo did not decode: " + ((e && e.message) || String(e)), e);
360
391
  }
361
392
  // Surface the RAW eContent (the exact DER a verifier hashes for the CMS
362
- // message-digest signed attribute) alongside the decoded TSTInfo a re-serialized
393
+ // message-digest signed attribute) alongside the decoded TSTInfo -- a re-serialized
363
394
  // tstInfo may not byte-match, so the raw bytes are the verification feed.
364
395
  return { tstInfo: tstInfo.result, eContent: encap.eContent, signerInfo: signed.signerInfos[0], certificates: signed.certificates };
365
396
  }
@@ -381,6 +412,23 @@ function parseToken(input) {
381
412
  */
382
413
  function pemDecode(text, label) { return pkix.pemDecode(text, label || null, PemError); }
383
414
 
415
+ /**
416
+ * @primitive pki.schema.tsp.pemEncode
417
+ * @signature pki.schema.tsp.pemEncode(der, label) -> string
418
+ * @since 0.1.23
419
+ * @status stable
420
+ * @spec RFC 7468, RFC 3161
421
+ * @related pki.schema.tsp.pemDecode
422
+ *
423
+ * Wrap DER bytes in a PEM envelope. RFC 3161 defines no standard PEM label, so
424
+ * `label` is REQUIRED -- the operator names the envelope explicitly (mirroring
425
+ * `pemDecode`, which accepts any label). Omitting it throws `pem/bad-label`.
426
+ *
427
+ * @example
428
+ * var pem = pki.schema.tsp.pemEncode(der, "TSP RESPONSE");
429
+ */
430
+ function pemEncode(der, label) { return pkix.pemEncode(der, label, PemError); }
431
+
384
432
  // A TimeStampResp root is a SEQUENCE of 1-2 whose first child is a PKIStatusInfo (a
385
433
  // SEQUENCE whose own first child is an INTEGER status). Disjoint from the OID-first
386
434
  // CMS ContentInfo, the INTEGER-first PKCS#8 key, and the exactly-3 signed-envelope
@@ -400,5 +448,6 @@ module.exports = {
400
448
  parseTstInfo: parseTstInfo,
401
449
  parseToken: parseToken,
402
450
  pemDecode: pemDecode,
451
+ pemEncode: pemEncode,
403
452
  matches: matches,
404
453
  };