@blamejs/pki 0.1.22 → 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/CHANGELOG.md +35 -0
- package/README.md +16 -6
- package/index.js +16 -12
- package/lib/asn1-der.js +126 -68
- package/lib/constants.js +46 -21
- package/lib/ct.js +54 -39
- package/lib/framework-error.js +57 -34
- package/lib/oid.js +110 -59
- package/lib/path-validate.js +302 -135
- package/lib/schema-all.js +49 -41
- package/lib/schema-attrcert.js +101 -64
- package/lib/schema-cmp.js +152 -131
- package/lib/schema-cms.js +621 -163
- package/lib/schema-crl.js +43 -21
- package/lib/schema-crmf.js +136 -79
- package/lib/schema-csr.js +63 -14
- package/lib/schema-engine.js +92 -43
- package/lib/schema-ocsp.js +101 -55
- package/lib/schema-pkcs12.js +80 -64
- package/lib/schema-pkcs8.js +12 -12
- package/lib/schema-pkix.js +283 -100
- package/lib/schema-smime.js +39 -39
- package/lib/schema-tsp.js +108 -59
- package/lib/schema-x509.js +36 -25
- package/lib/webcrypto.js +156 -22
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/schema-smime.js
CHANGED
|
@@ -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
|
|
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)
|
|
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)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
73
|
-
// directoryName rule is enforced there
|
|
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
|
|
90
|
-
// the signature per
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
191
|
-
// order-preserving
|
|
192
|
-
//
|
|
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
|
|
214
|
-
// domain from AlgorithmIdentifier though isomorphic
|
|
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
|
|
230
|
-
// (RFC 8551
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
294
|
-
* `{ capabilities }`
|
|
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
|
|
319
|
-
* `smime/multi-valued-attribute`
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
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
|
|
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) }
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
|
|
71
|
-
|
|
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
|
|
79
|
-
// shared pkix.generalName primitive (RFC 5280
|
|
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
|
|
84
|
-
var UTF8_TEXT =
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
225
|
-
// MUST error on a failInfo it does not understand (RFC 3161
|
|
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
|
|
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
|
|
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
|
|
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
|
|
254
|
-
if (!granted && present) throw NS.E("tsp/unexpected-token", "a non-granted TimeStampResp must not carry a timeStampToken (RFC 3161
|
|
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)
|
|
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
|
|
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`)
|
|
279
|
-
* encapsulates
|
|
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
|
|
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)
|
|
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`),
|
|
325
|
-
* RFC 3161
|
|
326
|
-
*
|
|
327
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
};
|