@blamejs/pki 0.6.14 → 0.6.15
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 +14 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +8 -252
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/tsp-sign.js
CHANGED
|
@@ -36,58 +36,43 @@ var _NS = pkix.makeNS("tsp", TspError, oid);
|
|
|
36
36
|
var b = asn1.build;
|
|
37
37
|
function _err(code, message, cause) { return new TspError(code, message, cause); }
|
|
38
38
|
|
|
39
|
-
// Any ASCII digit 1-9 present (a non-zero fractional digit), by character scan -- no regex.
|
|
40
39
|
function _hasNonZeroDigit(s) {
|
|
41
40
|
for (var i = 0; i < s.length; i++) { var c = _charCodeAt(s, i); if (c >= 49 && c <= 57) return true; }
|
|
42
41
|
return false;
|
|
43
42
|
}
|
|
44
43
|
function O(name) { return oid.byName(name); }
|
|
45
44
|
|
|
46
|
-
// The shared authoring-side builder, for the same pre-encoded-Extension validation every other
|
|
47
|
-
// request builder uses (a CSR's extensionRequest, a CRMF CertTemplate, a CRL's entry extensions).
|
|
48
45
|
var _b = pkiBuild.makeBuilder({
|
|
49
46
|
ErrorClass: TspError, prefix: "tsp", O: O, NS: _NS,
|
|
50
47
|
NAME_SCHEMA: pkix.name(_NS), SPKI_SCHEMA: pkix.spki(_NS),
|
|
51
48
|
});
|
|
52
49
|
|
|
53
|
-
// Digest names whose imprint / certHash this producer supports (the SHA-2 family).
|
|
54
50
|
var NODE_DIGEST = { sha256: "sha256", sha384: "sha384", sha512: "sha512" };
|
|
55
|
-
// The message-imprint hash MUST be exactly the digest algorithm's output length (RFC 3161 sec.
|
|
56
|
-
// 2.4.1 -- hashedMessage is "the hash of the datum to be time-stamped").
|
|
57
51
|
var HASH_LEN = { sha256: 32, sha384: 48, sha512: 64 };
|
|
58
52
|
|
|
59
|
-
// How much a `pki.path.validate` revocationChecked outcome ESTABLISHED, ordered. `false` is "no
|
|
60
|
-
// checker ran at all"; "undetermined" is "one ran and could not answer"; "waived" is an
|
|
61
|
-
// undetermined one a caller chose to pass; "determined" is an explicit good-or-revoked answer.
|
|
62
|
-
// Ranking them lets a caller trying several candidate paths keep the most established outcome any
|
|
63
|
-
// of them reached. Order of evaluation then does not decide the answer.
|
|
64
53
|
var _REVOCATION_RANK = { "false": 0, "undetermined": 1, "waived": 2, "determined": 3 };
|
|
65
54
|
function _rankRevocation(v) {
|
|
66
55
|
var r = _REVOCATION_RANK[String(v)];
|
|
67
|
-
return r === undefined ? 0 : r;
|
|
56
|
+
return r === undefined ? 0 : r;
|
|
68
57
|
}
|
|
69
58
|
function _moreEstablished(candidate, current) {
|
|
70
59
|
return _rankRevocation(candidate) > _rankRevocation(current);
|
|
71
60
|
}
|
|
72
61
|
|
|
73
|
-
// A hash AlgorithmIdentifier { OID, NULL }: messageImprint and ESSCertIDv2 hash algorithms
|
|
74
|
-
// carry an explicit NULL parameter (the form RFC 3161 / RFC 5035 producers emit).
|
|
75
62
|
function _hashAlgId(name) {
|
|
76
63
|
if (!NODE_DIGEST[name]) throw _err("tsp/unsupported-algorithm", "unsupported hash algorithm " + JSON.stringify(name));
|
|
77
64
|
return b.sequence([b.oid(O(name)), b.nullValue()]);
|
|
78
65
|
}
|
|
79
|
-
// A policy identifier: an OID name or a dotted-decimal string.
|
|
80
66
|
function _policy(p) {
|
|
81
67
|
if (typeof p !== "string") throw _err("tsp/bad-input", "the timestamp policy must be an OID name or dotted string");
|
|
82
68
|
return oid.isDottedDecimal(p) ? b.oid(p) : b.oid(O(p));
|
|
83
69
|
}
|
|
84
|
-
// The signer certificate DER (a DER BufferSource / PEM string) -- for the ESSCertIDv2 hash.
|
|
85
70
|
function _certDer(c) {
|
|
86
71
|
if (c == null) throw _err("tsp/bad-input", "the TSA signer requires a certificate (cert)");
|
|
87
|
-
if (guard.bytes.isByteSource(c)) {
|
|
72
|
+
if (guard.bytes.isByteSource(c)) {
|
|
88
73
|
var buf = guard.bytes.source(c, TspError, "tsp/bad-input", "the TSA certificate");
|
|
89
|
-
if (buf[0] === 0x30) return buf;
|
|
90
|
-
c = buf.toString("latin1");
|
|
74
|
+
if (buf[0] === 0x30) return buf;
|
|
75
|
+
c = buf.toString("latin1");
|
|
91
76
|
}
|
|
92
77
|
if (typeof c !== "string") throw _err("tsp/bad-input", "the TSA certificate must be a DER BufferSource or a PEM string");
|
|
93
78
|
var der = pkix.pemDecodeLenient(c, "CERTIFICATE");
|
|
@@ -95,14 +80,10 @@ function _certDer(c) {
|
|
|
95
80
|
return der;
|
|
96
81
|
}
|
|
97
82
|
|
|
98
|
-
// The RFC 5035 SigningCertificateV2 signed-attribute value binding the token to the TSA cert:
|
|
99
|
-
// SigningCertificateV2 ::= SEQUENCE { certs SEQUENCE OF ESSCertIDv2 }, ESSCertIDv2 ::= SEQUENCE {
|
|
100
|
-
// hashAlgorithm DEFAULT sha256, certHash OCTET STRING, issuerSerial OPTIONAL }. The default
|
|
101
|
-
// sha256 hashAlgorithm is omitted; certHash is the digest of the certificate.
|
|
102
83
|
function _signingCertV2(certDer, hashName) {
|
|
103
84
|
var certHash = nodeCrypto.createHash(NODE_DIGEST[hashName]).update(certDer).digest();
|
|
104
85
|
var essCertId = hashName === "sha256"
|
|
105
|
-
? b.sequence([b.octetString(certHash)])
|
|
86
|
+
? b.sequence([b.octetString(certHash)])
|
|
106
87
|
: b.sequence([_hashAlgId(hashName), b.octetString(certHash)]);
|
|
107
88
|
return b.sequence([b.sequence([essCertId])]);
|
|
108
89
|
}
|
|
@@ -143,26 +124,12 @@ function _signingCertV2(certDer, hashName) {
|
|
|
143
124
|
* var token = await pki.tsp.sign(imprint, { cert: signerCertDer, key: signerKeyPkcs8 }, { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
|
|
144
125
|
* (await pki.cms.verify(token)).valid; // true
|
|
145
126
|
*/
|
|
146
|
-
// Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async); the checks stay
|
|
147
|
-
// synchronous because they read the caller's mutable imprint, TSA material and options.
|
|
148
127
|
function sign(messageImprint, tsa, opts) {
|
|
149
|
-
// Every caller-owned argument copied at entry and released when the call settles -- see the note
|
|
150
|
-
// on the same call in x509-sign. The imprint matters most: it is the digest the token attests to.
|
|
151
128
|
return guard.bytes.fixedCall(TspError, "tsp/bad-input", [
|
|
152
129
|
[messageImprint, "the messageImprint"], [tsa, "the TSA"], [opts, "pki.tsp.sign options"],
|
|
153
130
|
], _sign);
|
|
154
131
|
}
|
|
155
132
|
|
|
156
|
-
// The three caller-owned objects pki.tsp.sign reads. A dropped option here is a token that quietly
|
|
157
|
-
// lacks what was asked for -- `odering` for `ordering` emitted a token with the ordering flag unset,
|
|
158
|
-
// which is a claim about the timestamp's orderability that the caller believes they made.
|
|
159
|
-
// The TSA is gated too, and the reason it needs to be is not the one it looks like. Both fields it
|
|
160
|
-
// carries are REQUIRED, so a MISSPELLING of either is already refused for what it leaves missing --
|
|
161
|
-
// but that is only half the class. A name belonging on the OPTIONS, written on the TSA instead, is
|
|
162
|
-
// read by nothing here: `ordering` placed on the TSA emits a token the same size as one that never
|
|
163
|
-
// asked for it, with the flag unset, while the caller believes they set it. Requiring the argument
|
|
164
|
-
// to carry only what it reads is what closes that, so all three caller-owned arguments answer the
|
|
165
|
-
// same question -- is this a name I read? -- rather than two of them answering it.
|
|
166
133
|
var _TSA_KEYS = { cert: 1, key: 1 };
|
|
167
134
|
var _IMPRINT_KEYS = { hashAlgorithm: 1, hashedMessage: 1 };
|
|
168
135
|
var _SIGN_OPTS = {
|
|
@@ -197,16 +164,9 @@ function _sign(messageImprint, tsa, opts) {
|
|
|
197
164
|
var certHashAlg = opts.certHashAlgorithm || "sha256";
|
|
198
165
|
if (!NODE_DIGEST[certHashAlg]) throw _err("tsp/unsupported-algorithm", "unsupported certHashAlgorithm " + JSON.stringify(certHashAlg));
|
|
199
166
|
|
|
200
|
-
// TSTInfo (RFC 3161 sec. 2.4.2): version(1), policy, messageImprint, serialNumber, genTime,
|
|
201
|
-
// then the OPTIONAL accuracy / ordering / nonce in ascending order.
|
|
202
167
|
var imprint = b.sequence([_hashAlgId(mi.hashAlgorithm), b.octetString(guard.bytes.source(mi.hashedMessage, TspError, "tsp/bad-input", "messageImprint.hashedMessage"))]);
|
|
203
|
-
// genTime defaults to now; a supplied value MUST be a valid Date (never a silently-ignored
|
|
204
|
-
// non-Date or an Invalid Date that would encode a garbage GeneralizedTime).
|
|
205
168
|
if (opts.genTime != null) guard.time.assertValid(opts.genTime, _err, "tsp/bad-input", "genTime");
|
|
206
169
|
var genTime = guard.time.isDate(opts.genTime) ? opts.genTime : new Date();
|
|
207
|
-
// A caller-authored INTEGER is coerced through the shared guard, so a value that is not one is a
|
|
208
|
-
// typed tsp/bad-input rather than a raw SyntaxError or RangeError out of BigInt() -- an untyped
|
|
209
|
-
// fault escaping a public verb, which a caller cannot catch by code.
|
|
210
170
|
var serial = guard.range.authoredInteger(opts.serialNumber, _err, "tsp/bad-input", "serialNumber");
|
|
211
171
|
var fields = [b.integer(1n), _policy(opts.policy), imprint, b.integer(serial), b.generalizedTime(genTime)];
|
|
212
172
|
if (opts.accuracy) fields.push(_accuracy(opts.accuracy));
|
|
@@ -225,14 +185,6 @@ function _sign(messageImprint, tsa, opts) {
|
|
|
225
185
|
});
|
|
226
186
|
}
|
|
227
187
|
|
|
228
|
-
// Accuracy ::= SEQUENCE { seconds INTEGER OPTIONAL, millis [0] INTEGER (1..999) OPTIONAL,
|
|
229
|
-
// micros [1] INTEGER (1..999) OPTIONAL } (RFC 3161 sec. 2.4.2). seconds is a non-negative
|
|
230
|
-
// integer; millis and micros MUST each be in 1..999 -- enforced before encoding.
|
|
231
|
-
// Accuracy ::= SEQUENCE { seconds INTEGER OPTIONAL, millis [0] OPTIONAL, micros [1] OPTIONAL }
|
|
232
|
-
// (RFC 3161 sec. 2.4.2). All three are OPTIONAL and encoded only when present, so a misspelling is
|
|
233
|
-
// missed by nothing: `milis` emitted an EMPTY Accuracy that parses back as zero seconds, millis and
|
|
234
|
-
// micros, and the token then understates its own precision -- a claim about the timestamp that the
|
|
235
|
-
// caller believed they had made.
|
|
236
188
|
var _ACCURACY_KEYS = { seconds: 1, millis: 1, micros: 1 };
|
|
237
189
|
|
|
238
190
|
function _accuracy(a) {
|
|
@@ -249,25 +201,15 @@ function _accuracy(a) {
|
|
|
249
201
|
if (a.micros != null) f.push(b.contextPrimitive(1, _subMilliBytes(a.micros, "micros")));
|
|
250
202
|
return b.sequence(f);
|
|
251
203
|
}
|
|
252
|
-
// The content octets of an Accuracy millis/micros field: an INTEGER in 1..999 (RFC 3161 sec.
|
|
253
|
-
// 2.4.2), without the universal INTEGER tag (the field is IMPLICIT [0]/[1]).
|
|
254
204
|
function _subMilliBytes(n, label) {
|
|
255
205
|
var v = Number(n);
|
|
256
206
|
if (!Number.isInteger(v) || v < 1 || v > 999) throw _err("tsp/bad-input", "Accuracy " + label + " must be an integer in 1..999 (RFC 3161 sec. 2.4.2)");
|
|
257
|
-
return b.integer(BigInt(v)).subarray(2);
|
|
207
|
+
return b.integer(BigInt(v)).subarray(2);
|
|
258
208
|
}
|
|
259
209
|
|
|
260
|
-
// Coverage residual -- `_hashAlgId`'s unsupported-hash throw is unreachable through the shipped
|
|
261
|
-
// path: both callers (the messageImprint hash and the ESSCertIDv2 certHashAlgorithm) validate
|
|
262
|
-
// the name against NODE_DIGEST before `_hashAlgId` runs, so the guard is belt-and-suspenders.
|
|
263
210
|
|
|
264
|
-
// PKIFailureInfo name -> NamedBitList bit index (RFC 3161 sec. 2.4.2; the reverse of the
|
|
265
|
-
// schema-tsp FAILURE_BITS decode map). Build maps names -> bits; an unknown name fails closed.
|
|
266
211
|
var FAILINFO_BIT = { badAlg: 0, badRequest: 2, badDataFormat: 5, timeNotAvailable: 14, unacceptedPolicy: 15, unacceptedExtension: 16, addInfoNotAvailable: 17, systemFailure: 25 };
|
|
267
212
|
|
|
268
|
-
// Encode a PKIFailureInfo as a minimal DER NamedBitList BIT STRING from a set of names: set each
|
|
269
|
-
// named bit (bit 0 = MSB of byte 0), strip trailing zero octets, and place unusedBits exactly
|
|
270
|
-
// below the lowest set bit (X.690 sec. 11.2.2).
|
|
271
213
|
function _failInfoBits(names) {
|
|
272
214
|
if (!Array.isArray(names)) throw _err("tsp/bad-input", "failInfo must be an array of PKIFailureInfo names");
|
|
273
215
|
var idxs = names.map(function (n) {
|
|
@@ -275,11 +217,9 @@ function _failInfoBits(names) {
|
|
|
275
217
|
if (i == null) throw _err("tsp/bad-input", "unknown PKIFailureInfo name " + JSON.stringify(n) + " (RFC 3161 sec. 2.4.2)");
|
|
276
218
|
return i;
|
|
277
219
|
});
|
|
278
|
-
return b.namedBitString(idxs);
|
|
220
|
+
return b.namedBitString(idxs);
|
|
279
221
|
}
|
|
280
222
|
|
|
281
|
-
// Coerce a timeStampToken (a CMS ContentInfo the token producer emits) to DER for embedding in a
|
|
282
|
-
// TimeStampResp -- a DER Buffer as-is, a Uint8Array copied, or a PEM string de-armored.
|
|
283
223
|
function _tokenDer(t) {
|
|
284
224
|
if (guard.bytes.isByteSource(t)) { var u = guard.bytes.source(t, TspError, "tsp/bad-input", "the timeStampToken"); if (u[0] === 0x30) return u; }
|
|
285
225
|
if (typeof t === "string") return schemaTsp.pemDecode(t);
|
|
@@ -315,10 +255,6 @@ function _assertImprint(mi) {
|
|
|
315
255
|
* var sha256Digest = Buffer.from(await pki.webcrypto.subtle.digest("SHA-256", Buffer.from("hello")));
|
|
316
256
|
* var req = pki.tsp.request({ hashAlgorithm: "sha256", hashedMessage: sha256Digest }, { nonce: 0x0102030405060708n, certReq: true });
|
|
317
257
|
*/
|
|
318
|
-
// The options pki.tsp.request encodes into the TimeStampReq. Every one is OPTIONAL in the
|
|
319
|
-
// structure, so a misspelled name is missed by nothing and the request goes out claiming less than
|
|
320
|
-
// was asked for. `nonce` is the one that costs most: misspelled, the request carries no nonce at
|
|
321
|
-
// all, and the caller matches a reply against a replay defense that was never asked for.
|
|
322
258
|
var _REQUEST_OPTS = { reqPolicy: 1, nonce: 1, certReq: 1, extensions: 1, pem: 1 };
|
|
323
259
|
|
|
324
260
|
function request(messageImprint, opts) {
|
|
@@ -326,12 +262,6 @@ function request(messageImprint, opts) {
|
|
|
326
262
|
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("tsp/bad-input", "pki.tsp.request options must be an object");
|
|
327
263
|
guard.identifier.assertKnownKeys(opts, _REQUEST_OPTS, _err, "tsp/bad-input", "pki.tsp.request has an unknown option ");
|
|
328
264
|
var mi = messageImprint || {};
|
|
329
|
-
// The same table pki.tsp.sign holds its imprint to. The request's own options live in the second
|
|
330
|
-
// argument, so `nonce` written on the imprint reaches nothing and the request goes out without
|
|
331
|
-
// the replay defense the caller believes they asked for.
|
|
332
|
-
// The same table pki.tsp.sign holds its imprint to. The request's own options live in the second
|
|
333
|
-
// argument, so `nonce` written on the imprint reaches nothing and the request goes out without
|
|
334
|
-
// the replay defense the caller believes they asked for.
|
|
335
265
|
if (mi && typeof mi === "object" && !Buffer.isBuffer(mi)) {
|
|
336
266
|
guard.identifier.assertKnownKeys(mi, _IMPRINT_KEYS, _err, "tsp/bad-input", function (k) {
|
|
337
267
|
return "unknown messageImprint field " + JSON.stringify(k) +
|
|
@@ -340,8 +270,6 @@ function request(messageImprint, opts) {
|
|
|
340
270
|
}
|
|
341
271
|
_assertImprint(mi);
|
|
342
272
|
var imprint = b.sequence([_hashAlgId(mi.hashAlgorithm), b.octetString(guard.bytes.source(mi.hashedMessage, TspError, "tsp/bad-input", "messageImprint.hashedMessage"))]);
|
|
343
|
-
// TimeStampReq: version(1), messageImprint, then OPTIONAL reqPolicy / nonce / certReq /
|
|
344
|
-
// extensions in schema order. certReq DEFAULT FALSE -> only an explicit TRUE is encoded (DER).
|
|
345
273
|
var fields = [b.integer(1n), imprint];
|
|
346
274
|
if (opts.reqPolicy != null) fields.push(_policy(opts.reqPolicy));
|
|
347
275
|
if (opts.nonce != null) fields.push(b.integer(guard.range.authoredInteger(opts.nonce, _err, "tsp/bad-input", "nonce")));
|
|
@@ -349,11 +277,6 @@ function request(messageImprint, opts) {
|
|
|
349
277
|
if (opts.certReq === true) fields.push(b.boolean(true));
|
|
350
278
|
if (opts.extensions != null) {
|
|
351
279
|
if (!Array.isArray(opts.extensions) || !guard.list.allMatch(opts.extensions, function (e) { return guard.bytes.isByteSource(e); })) throw _err("tsp/bad-input", "extensions must be an array of encoded Extension DER buffers");
|
|
352
|
-
// Each pre-encoded Extension is validated as one, and its extnID must not repeat -- the same
|
|
353
|
-
// gate every other request builder applies. Spliced in unchecked, a caller relaying a blob it
|
|
354
|
-
// did not author put fully chosen bytes inside [0] and this encoder emitted DER its own
|
|
355
|
-
// parseRequest refuses: an undecodable value, a duplicate extnID, an explicit critical=FALSE
|
|
356
|
-
// the DER DEFAULT rule forbids.
|
|
357
280
|
var seenExt = {};
|
|
358
281
|
var encoded = opts.extensions.map(function (e, i) {
|
|
359
282
|
var der = guard.bytes.source(e, TspError, "tsp/bad-input", "an extension");
|
|
@@ -419,9 +342,6 @@ function parseRequest(input) { return schemaTsp.parseRequest(input); }
|
|
|
419
342
|
* var resp = pki.tsp.response(token, {}); // granted
|
|
420
343
|
* var rej = pki.tsp.response(null, { status: 2, failInfo: ["badAlg"] }); // rejection
|
|
421
344
|
*/
|
|
422
|
-
// The options pki.tsp.response encodes into the TimeStampResp. `status` carries the verdict and
|
|
423
|
-
// defaults to granted(0), so a misspelled name does not lose a detail -- it INVERTS the answer: a
|
|
424
|
-
// TSA refusing a request emits a granted response instead, over a token it did mean to withhold.
|
|
425
345
|
var _RESPONSE_OPTS = { status: 1, statusString: 1, failInfo: 1, pem: 1 };
|
|
426
346
|
|
|
427
347
|
function response(token, opts) {
|
|
@@ -436,8 +356,6 @@ function response(token, opts) {
|
|
|
436
356
|
var siFields = [b.integer(BigInt(status))];
|
|
437
357
|
if (opts.statusString != null) {
|
|
438
358
|
var texts = Array.isArray(opts.statusString) ? opts.statusString : [opts.statusString];
|
|
439
|
-
// PKIFreeText is SEQUENCE SIZE (1..MAX) OF UTF8String -- an empty statusString would build an
|
|
440
|
-
// empty SEQUENCE the parser (seqOf min:1) rejects, so reject it at the builder instead.
|
|
441
359
|
if (!texts.length) throw _err("tsp/bad-input", "statusString must carry at least one PKIFreeText element (RFC 4210 SEQUENCE SIZE 1..MAX)");
|
|
442
360
|
siFields.push(b.sequence(texts.map(function (t) { return b.utf8(String(t)); })));
|
|
443
361
|
}
|
|
@@ -478,61 +396,22 @@ function response(token, opts) {
|
|
|
478
396
|
*/
|
|
479
397
|
function parseResponse(input) { return schemaTsp.parse(input); }
|
|
480
398
|
|
|
481
|
-
|
|
482
|
-
// belt-and-suspenders on values already guaranteed upstream, so their failure arms are unreachable
|
|
483
|
-
// through the shipped path: parseToken has already proven the token is a single-signer SignedData
|
|
484
|
-
// carrying a signing-certificate attribute, so `signerInfo`/`signedAttrs`/the signing-cert attr are
|
|
485
|
-
// present; cms.verify returns a parsed, matched signer certificate, so `tsaCertDer` is set and
|
|
486
|
-
// re-parsing it (or decoding its extensions) cannot fail here; decodeAttribute yields at least one
|
|
487
|
-
// ESSCertID (an empty SEQUENCE OF is rejected at decode -> the `!essCert` guard is defensive); and
|
|
488
|
-
// path.validate returns a fail-closed verdict for a bad anchor rather than throwing, so its catch
|
|
489
|
-
// is defensive. Likewise, in the out-of-path chain step, `parsed.certificates` is always an array
|
|
490
|
-
// (empty when absent), the embedded certs the chain walk parses are the X.509 (universal) choices,
|
|
491
|
-
// and guard.name.dnEqual's control-byte reject is defensive on a cert x509.parse already accepted --
|
|
492
|
-
// so the `|| []`, the non-universal filter arm, and that throw are unreachable through the shipped
|
|
493
|
-
// path. In the tsa-hint check, the tsa GeneralName's decode catch is likewise defensive: parseToken
|
|
494
|
-
// eager-decodes the token, so a constructed GeneralName that would fail decodeGeneralName has already
|
|
495
|
-
// failed there, and a primitive one decodes -- the catch only guards decodeGeneralName's standalone
|
|
496
|
-
// use and yields the same safe no-match. A malformed ESS value, a malformed EKU/keyUsage extnValue,
|
|
497
|
-
// and a malformed subjectAltName ARE reachable (a signed-but-broken cert/attribute) and ARE covered;
|
|
498
|
-
// the reachable verdicts (unsupported hash, EKU shape/wrapper, keyUsage, imprint/nonce/policy/binding
|
|
499
|
-
// mismatch, tsa-hint match + mismatch, unknown critical extension, untrusted TSA, embedded-chain
|
|
500
|
-
// ordering) are each driven by a RED conformance vector.
|
|
501
|
-
|
|
502
|
-
// The imprint / ESSCertID hash algorithms a verifier can recompute: the SHA-2 family, plus SHA-1
|
|
503
|
-
// for MATCHING a legacy token's imprint (a verify-side digest, never a signing choice). An unknown
|
|
504
|
-
// or weak (e.g. MD5) algorithm cannot be recomputed, so the check fails closed rather than assume.
|
|
399
|
+
|
|
505
400
|
var _VERIFY_DIGEST = { sha256: "sha256", sha384: "sha384", sha512: "sha512", sha1: "sha1" };
|
|
506
|
-
// The digest output length each verify hash MUST produce: a messageImprint or precomputed imprint
|
|
507
|
-
// of any other length is not a real digest and can never correspond to data (RFC 3161 sec. 2.4.1).
|
|
508
401
|
var _VERIFY_DIGEST_LEN = { sha1: 20, sha256: 32, sha384: 48, sha512: 64 };
|
|
509
|
-
// The ESSCertID(V2) certificate binding is a pure identity pin, so its digest MUST be
|
|
510
|
-
// collision-resistant: a SHA-1 certHash would let a chosen-prefix collision substitute the TSA
|
|
511
|
-
// certificate. RFC 5816 moved this binding from the SHA-1 ESSCertID to the SHA-2 ESSCertIDv2 for
|
|
512
|
-
// exactly this reason -- accept only SHA-2 here (the messageImprint stays algorithm-agile, since
|
|
513
|
-
// that hash is the requester's documented RFC 3161 choice, not an identity pin).
|
|
514
402
|
var _BIND_DIGEST = { sha256: "sha256", sha384: "sha384", sha512: "sha512" };
|
|
515
403
|
|
|
516
|
-
// M10 -- recompute the imprint over `data` under the token's messageImprint hash algorithm (read
|
|
517
|
-
// from the verified eContent) and compare to hashedMessage. `data` is raw bytes (hashed) or a
|
|
518
|
-
// precomputed { hashAlgorithm, hashedMessage } (algorithm-exact + byte-exact). Returns true or a
|
|
519
|
-
// tsp/* verdict code; a config-shape error throws (the caller's contract).
|
|
520
404
|
function _imprintMatches(mi, data) {
|
|
521
405
|
var name = mi.hashAlgorithm && mi.hashAlgorithm.name;
|
|
522
406
|
var nodeAlg = (name && intrinsic.hasOwn(_VERIFY_DIGEST, name)) ? _VERIFY_DIGEST[name] : null;
|
|
523
|
-
if (!nodeAlg) return "tsp/unsupported-algorithm";
|
|
407
|
+
if (!nodeAlg) return "tsp/unsupported-algorithm";
|
|
524
408
|
var wantLen = _VERIFY_DIGEST_LEN[name];
|
|
525
|
-
// The token's messageImprint MUST be a full-length digest for its algorithm. A short/empty
|
|
526
|
-
// hashedMessage is not a hash and cannot correspond to any data, so it fails closed here -- a
|
|
527
|
-
// matching short precomputed imprint must never make it verify (RFC 3161 sec. 2.4.1).
|
|
528
409
|
if (mi.hashedMessage.length !== wantLen) return "tsp/imprint-mismatch";
|
|
529
410
|
var actual;
|
|
530
411
|
if (guard.bytes.isByteSource(data)) {
|
|
531
412
|
actual = nodeCrypto.createHash(nodeAlg).update(guard.bytes.source(data, TspError, "tsp/bad-input", "data")).digest();
|
|
532
413
|
} else if (data && typeof data === "object" && guard.bytes.isByteSource(data.hashedMessage)) {
|
|
533
|
-
if (data.hashAlgorithm !== name) return "tsp/imprint-mismatch";
|
|
534
|
-
// a caller-supplied precomputed imprint of the wrong length is a config error (the sign path
|
|
535
|
-
// rejects it identically) -- throw rather than silently mismatch (three-tier: caller input).
|
|
414
|
+
if (data.hashAlgorithm !== name) return "tsp/imprint-mismatch";
|
|
536
415
|
var dhmLen = guard.bytes.source(data.hashedMessage, TspError, "tsp/bad-input", "precomputed imprint hashedMessage").length;
|
|
537
416
|
if (dhmLen !== wantLen) throw _err("tsp/bad-input", "precomputed imprint hashedMessage length (" + dhmLen + ") does not match the " + name + " digest length (" + wantLen + ")");
|
|
538
417
|
actual = guard.bytes.source(data.hashedMessage, TspError, "tsp/bad-input", "precomputed imprint hashedMessage");
|
|
@@ -542,41 +421,29 @@ function _imprintMatches(mi, data) {
|
|
|
542
421
|
return Buffer.compare(actual, Buffer.from(mi.hashedMessage)) === 0 ? true : "tsp/imprint-mismatch";
|
|
543
422
|
}
|
|
544
423
|
|
|
545
|
-
// M13 -- the token MUST be bound to the exact signer certificate via the ESS SigningCertificate(V2)
|
|
546
|
-
// signed attribute (RFC 3161 sec. 2.4.2 / RFC 5816): recompute hash(tsaCertDer) under the
|
|
547
|
-
// ESSCertID(V2) hashAlgorithm (v1 implies SHA-1, v2 defaults SHA-256) and compare to certHash.
|
|
548
424
|
function _checkCertBinding(signerInfo, tsaCertDer) {
|
|
549
425
|
var attrs = (signerInfo && signerInfo.signedAttrs) || [];
|
|
550
|
-
// Prefer the RFC 5816 SigningCertificateV2 (SHA-2) binding over the legacy RFC 2634
|
|
551
|
-
// SigningCertificate (SHA-1). A token may carry both for compatibility, and the SHA-1 attribute
|
|
552
|
-
// sorts first in the signed-attribute SET; picking it would fail the SHA-2-only binding pin even
|
|
553
|
-
// though a usable v2 binding is present. Fall back to the legacy attribute only when no v2 exists.
|
|
554
426
|
var sc = null, scV1 = null;
|
|
555
427
|
for (var i = 0; i < attrs.length; i++) {
|
|
556
428
|
if (attrs[i].type === O("signingCertificateV2")) { sc = attrs[i]; break; }
|
|
557
429
|
if (attrs[i].type === O("signingCertificate") && !scV1) scV1 = attrs[i];
|
|
558
430
|
}
|
|
559
431
|
if (!sc) sc = scV1;
|
|
560
|
-
if (!sc) return "tsp/missing-signing-certificate";
|
|
432
|
+
if (!sc) return "tsp/missing-signing-certificate";
|
|
561
433
|
var decoded;
|
|
562
434
|
try { decoded = smime.decodeAttribute(sc); }
|
|
563
435
|
catch (_e) { return "tsp/bad-signing-certificate"; }
|
|
564
436
|
var essCert = decoded.certs && decoded.certs[0];
|
|
565
437
|
if (!essCert) return "tsp/bad-signing-certificate";
|
|
566
438
|
var nodeAlg = essCert.hashAlgorithm && _BIND_DIGEST[essCert.hashAlgorithm.name];
|
|
567
|
-
if (!nodeAlg) return "tsp/unsupported-algorithm";
|
|
439
|
+
if (!nodeAlg) return "tsp/unsupported-algorithm";
|
|
568
440
|
var actual = nodeCrypto.createHash(nodeAlg).update(tsaCertDer).digest();
|
|
569
441
|
if (Buffer.compare(actual, Buffer.from(essCert.certHash)) !== 0) return "tsp/cert-binding-mismatch";
|
|
570
|
-
// RFC 5035 sec. 5: if the ESSCertID(V2) carries the OPTIONAL issuerSerial, it MUST identify the
|
|
571
|
-
// signer certificate -- confirm its serialNumber. The certHash already pins the exact cert
|
|
572
|
-
// cryptographically; the serialNumber is that cert's unambiguous identity field.
|
|
573
442
|
if (essCert.issuerSerial) {
|
|
574
443
|
var signerCert;
|
|
575
444
|
try { signerCert = pkiX509.parse(tsaCertDer); }
|
|
576
445
|
catch (_e) { return "tsp/bad-signing-certificate"; }
|
|
577
446
|
if (essCert.issuerSerial.serialNumber !== signerCert.serialNumber) return "tsp/cert-binding-mismatch";
|
|
578
|
-
// the issuerSerial issuer is a single directoryName (RFC 5035 sec. 5) that MUST canonically
|
|
579
|
-
// equal the signer cert's issuer DN -- decode it and compare through the shared name guard.
|
|
580
447
|
var names = essCert.issuerSerial.issuer.names || [];
|
|
581
448
|
var gn = null;
|
|
582
449
|
try { if (names.length === 1) gn = schemaTsp.decodeGeneralName(names[0].bytes); }
|
|
@@ -588,33 +455,20 @@ function _checkCertBinding(signerInfo, tsaCertDer) {
|
|
|
588
455
|
return true;
|
|
589
456
|
}
|
|
590
457
|
|
|
591
|
-
// M11 -- RFC 3161 sec. 2.3: the TSA certificate MUST contain exactly ONE extendedKeyUsage instance
|
|
592
|
-
// whose sole KeyPurposeID is id-kp-timeStamping, and that extension MUST be CRITICAL. path.validate's
|
|
593
|
-
// requiredEku only checks the purpose is ASSERTED (an absent EKU is unrestricted), so this
|
|
594
|
-
// single-purpose + critical gate is layered on top -- the OCSP-delegate precedent for a format gate
|
|
595
|
-
// above the path checks. Additionally (RFC 5280 sec. 4.2.1.3) a keyUsage extension, if present, MUST
|
|
596
|
-
// permit signing for the cert to sign the token. Returns true or a tsp/* code.
|
|
597
458
|
function _checkTsaCertUsage(tsaCertDer) {
|
|
598
459
|
var cert;
|
|
599
460
|
try { cert = pkiX509.parse(tsaCertDer); }
|
|
600
461
|
catch (_e) { return "tsp/bad-tsa-certificate"; }
|
|
601
462
|
var exts = (cert.extensions || []).filter(function (e) { return e.oid === O("extKeyUsage"); });
|
|
602
|
-
if (exts.length !== 1) return "tsp/bad-eku";
|
|
463
|
+
if (exts.length !== 1) return "tsp/bad-eku";
|
|
603
464
|
if (!exts[0].critical) return "tsp/eku-not-critical";
|
|
604
465
|
var purposes, ekuNode;
|
|
605
466
|
try { ekuNode = asn1.decode(exts[0].value); }
|
|
606
467
|
catch (_e) { return "tsp/bad-eku"; }
|
|
607
|
-
// the extnValue MUST be a universal SEQUENCE OF KeyPurposeId -- a non-SEQUENCE constructed wrapper
|
|
608
|
-
// (e.g. a SET) carrying OID children would otherwise slip through the bare children walk.
|
|
609
468
|
if (ekuNode.tagClass !== "universal" || ekuNode.tagNumber !== asn1.TAGS.SEQUENCE || !ekuNode.children) return "tsp/bad-eku";
|
|
610
469
|
try { purposes = ekuNode.children.map(function (c) { return asn1.read.oid(c); }); }
|
|
611
470
|
catch (_e) { return "tsp/bad-eku"; }
|
|
612
471
|
if (purposes.length !== 1 || purposes[0] !== O("timeStamping")) return "tsp/eku-not-exclusive";
|
|
613
|
-
// A keyUsage that forbids signing cannot mint a token (RFC 5280 sec. 4.2.1.3); an absent keyUsage
|
|
614
|
-
// is unrestricted. Require digitalSignature (bit 0) or nonRepudiation/contentCommitment (bit 1) --
|
|
615
|
-
// the signing bits, the TSA analogue of the OCSP-responder keyUsage gate.
|
|
616
|
-
// Through the shared reader, which applies the NamedBitList rules (X.690 sec. 11.2.2 minimal
|
|
617
|
-
// encoding, sec. 4.2.1.3 at least one bit set) this boundary was applying only half of.
|
|
618
472
|
var ku;
|
|
619
473
|
try { ku = pkix.keyUsageOf(_NS, cert, _err, "tsp/bad-key-usage", "TSA certificate"); }
|
|
620
474
|
catch (_e) { return "tsp/bad-key-usage"; }
|
|
@@ -622,9 +476,6 @@ function _checkTsaCertUsage(tsaCertDer) {
|
|
|
622
476
|
return true;
|
|
623
477
|
}
|
|
624
478
|
|
|
625
|
-
// RFC 5816 sec. 2.2.2: if TSTInfo.tsa is present, it is a hint that MUST correspond to one of the
|
|
626
|
-
// signing certificate's subject names -- its subject DN (a directoryName, compared canonically) or a
|
|
627
|
-
// subjectAltName entry (a byte-exact GeneralName match). `tsaBytes` is the raw tsa GeneralName TLV.
|
|
628
479
|
function _tsaMatchesCert(tsaBytes, signerCert) {
|
|
629
480
|
var gn;
|
|
630
481
|
try { gn = schemaTsp.decodeGeneralName(tsaBytes); }
|
|
@@ -635,8 +486,6 @@ function _tsaMatchesCert(tsaBytes, signerCert) {
|
|
|
635
486
|
var san;
|
|
636
487
|
try { san = asn1.decode(sanExt.value); }
|
|
637
488
|
catch (_e) { return false; }
|
|
638
|
-
// subjectAltName MUST be a universal SEQUENCE OF GeneralName -- a malformed wrapper (e.g. a SET)
|
|
639
|
-
// carrying a byte-equal child must not contribute an identity (mirrors the EKU wrapper gate).
|
|
640
489
|
if (san.tagClass === "universal" && san.tagNumber === asn1.TAGS.SEQUENCE && san.children) {
|
|
641
490
|
for (var i = 0; i < san.children.length; i++) {
|
|
642
491
|
if (san.children[i].bytes.equals(tsaBytes)) return true;
|
|
@@ -646,28 +495,15 @@ function _tsaMatchesCert(tsaBytes, signerCert) {
|
|
|
646
495
|
return false;
|
|
647
496
|
}
|
|
648
497
|
|
|
649
|
-
// Bound on the candidate certification paths enumerated from a token's certificate pool -- a guard
|
|
650
|
-
// against a hostile embed of many same-subject certificates blowing up the search (CWE-834).
|
|
651
498
|
var MAX_TSA_CHAINS = 32;
|
|
652
499
|
|
|
653
|
-
// Enumerate candidate certification paths for the TSA leaf from a pool of embedded and caller-supplied
|
|
654
|
-
// certificates. path.validate validates a FIXED ordered path (it is not a path builder), so the
|
|
655
|
-
// ordering + backtracking lives here: at every step EVERY unused pool certificate whose subject DN
|
|
656
|
-
// equals the working issuer DN is a candidate issuer -- CA rollover / cross-certification embed
|
|
657
|
-
// several with the same subject, and a same-subject wrong-key certificate must not shadow the real
|
|
658
|
-
// issuer -- and each accumulated prefix is itself a candidate (the anchor may have issued the leaf
|
|
659
|
-
// directly or any intermediate). A certificate is never chained to itself (RFC 5280 identity is
|
|
660
|
-
// issuer + serialNumber, never the serial alone). Each chain is returned anchor-down (highest issuer
|
|
661
|
-
// first, the leaf last) as path.validate takes it; longer chains first so a fully-embedded path is
|
|
662
|
-
// preferred. Bounded by MAX_TSA_CHAINS. The caller (verify) tries each until one validates.
|
|
663
500
|
function _buildTsaChains(leaf, pool) {
|
|
664
501
|
var chains = [];
|
|
665
502
|
function dfs(current, acc, used) {
|
|
666
503
|
if (chains.length >= MAX_TSA_CHAINS) return;
|
|
667
|
-
chains.push(acc.slice());
|
|
504
|
+
chains.push(acc.slice());
|
|
668
505
|
for (var i = 0; i < pool.length && chains.length < MAX_TSA_CHAINS; i++) {
|
|
669
506
|
if (used[i]) continue;
|
|
670
|
-
// never chain a certificate to itself (issuer + serialNumber identity, not the serial alone).
|
|
671
507
|
if (pool[i].serialNumberHex === current.serialNumberHex && guard.name.dnEqual(pool[i].issuer.rdns, current.issuer.rdns, _err, "tsp/bad-tsa-certificate", "the TSA certificate issuer")) continue;
|
|
672
508
|
if (guard.name.dnEqual(pool[i].subject.rdns, current.issuer.rdns, _err, "tsp/bad-tsa-certificate", "the TSA certificate subject")) {
|
|
673
509
|
used[i] = true; acc.push(pool[i]);
|
|
@@ -742,11 +578,8 @@ function _buildTsaChains(leaf, pool) {
|
|
|
742
578
|
* res.valid; // boolean; pass opts.trustAnchors to also chain the TSA cert to a root
|
|
743
579
|
* res.genTime; // Date, read from the verified eContent
|
|
744
580
|
*/
|
|
745
|
-
// Every option pki.tsp.verify reads. Adding one here is the only way to make it accepted.
|
|
746
581
|
var _VERIFY_OPTS = { certs: 1, trustAnchors: 1, nonce: 1, reqPolicy: 1, revocationChecker: 1 };
|
|
747
582
|
|
|
748
|
-
// Does any extension carry the critical flag? The answer IS the unknown-critical-extension gate, so
|
|
749
|
-
// it is decided by comparison instead of by a `some`. See guard-list.
|
|
750
583
|
function _anyCritical(extensions) {
|
|
751
584
|
return guard.list.anyMatches(extensions, function (e) { return !!e && !!e.critical; });
|
|
752
585
|
}
|
|
@@ -754,136 +587,60 @@ function _anyCritical(extensions) {
|
|
|
754
587
|
async function verify(token, data, opts) {
|
|
755
588
|
opts = opts || {};
|
|
756
589
|
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw _err("tsp/bad-input", "pki.tsp.verify options must be an object");
|
|
757
|
-
// An unrecognized option is refused rather than ignored. The anchor option is `trustAnchors` (an
|
|
758
|
-
// anchor tuple, or a non-empty array of them), the same spelling pki.cms.verify / pki.cmp.verify use.
|
|
759
|
-
// A caller who mistyped it would otherwise get no anchoring and no error: the TSA certificate
|
|
760
|
-
// unchained, `valid: true`, and nothing to notice it by. Naming it at the boundary is cheap.
|
|
761
590
|
guard.identifier.assertKnownKeys(opts, _VERIFY_OPTS, _err, "tsp/bad-input",
|
|
762
591
|
"pki.tsp.verify has an unknown option (the anchor option is `trustAnchors`) ");
|
|
763
592
|
if (opts.certs != null && (!Array.isArray(opts.certs) || !guard.list.allMatch(opts.certs, function (c) { return guard.bytes.isByteSource(c); }))) {
|
|
764
|
-
throw _err("tsp/bad-input", "pki.tsp.verify opts.certs must be an array of DER certificate Buffers");
|
|
593
|
+
throw _err("tsp/bad-input", "pki.tsp.verify opts.certs must be an array of DER certificate Buffers");
|
|
765
594
|
}
|
|
766
|
-
var tokenDer = _tokenDer(token);
|
|
767
|
-
// M7 -- parseToken enforces the SignedData + id-ct-TSTInfo + single-signer + signing-cert-present
|
|
768
|
-
// structure and decodes the TSTInfo FROM the raw eContent; a structural defect throws.
|
|
595
|
+
var tokenDer = _tokenDer(token);
|
|
769
596
|
var parsed = schemaTsp.parseToken(tokenDer);
|
|
770
597
|
var tst = parsed.tstInfo;
|
|
771
|
-
// `trusted: false` on every refusal too, not only on the accepting return. A caller reading
|
|
772
|
-
// `res.trusted` must get an answer on both branches -- an undefined on the failure path is the
|
|
773
|
-
// same "cannot tell what was checked" the field was added to remove, and `!res.trusted` reading
|
|
774
|
-
// true by accident is not the same as its reading true because nothing anchored the TSA.
|
|
775
|
-
// What a path established travels with the REFUSAL as well. Hardcoding these to false/null here
|
|
776
|
-
// would misreport the one case the field matters most for: a path that ran, established the TSA
|
|
777
|
-
// REVOKED, and refused on that basis did check revocation, and saying it did not is the same
|
|
778
|
-
// "cannot tell what was checked" the field was added to remove. They stay false/null until a path
|
|
779
|
-
// actually produces them, which is the honest answer before one runs.
|
|
780
598
|
var revocationChecked = false;
|
|
781
599
|
var anchorConstraints = null;
|
|
782
600
|
function fail(code, reason) {
|
|
783
601
|
return { valid: false, trusted: false, revocationChecked: revocationChecked, anchorConstraints: anchorConstraints, code: code, reason: reason || null, genTime: tst.genTime, accuracy: tst.accuracy, serialNumber: tst.serialNumber, serialNumberHex: tst.serialNumberHex, policy: tst.policy, nonce: tst.nonce, tsa: tst.tsa, tstInfo: tst, signer: null };
|
|
784
602
|
}
|
|
785
|
-
// M12 -- the CMS signature over the exact RFC 5652 sec. 5.4 preimage (message-digest bound to the
|
|
786
|
-
// authenticated eContent). cms.verify re-parses the same bytes; a failure is a fail-closed verdict.
|
|
787
|
-
// Caller-supplied out-of-band certificates (opts.certs) join the signer-certificate candidates, so
|
|
788
|
-
// a cert-less token (RFC 3161 -- the TSA omits its certificate when certReq was false) can verify.
|
|
789
603
|
var cmsRes = await cmsVerify.verify(tokenDer, { certs: opts.certs });
|
|
790
604
|
var signer = cmsRes.signers[0];
|
|
791
605
|
if (!cmsRes.valid) return fail("tsp/bad-signature", signer && (signer.code || signer.message));
|
|
792
606
|
var tsaCertDer = signer && signer.cert;
|
|
793
607
|
if (!tsaCertDer) return fail("tsp/bad-signature", "the TSA signer certificate was not found");
|
|
794
|
-
// M10 -- what was timestamped MUST correspond to the data (imprint recompute + match).
|
|
795
608
|
var mi = _imprintMatches(tst.messageImprint, data);
|
|
796
609
|
if (mi !== true) return fail(mi);
|
|
797
|
-
// M14 -- if a request nonce is supplied, the token MUST echo it (BigInt-exact).
|
|
798
610
|
if (opts.nonce != null) {
|
|
799
611
|
var wantNonce = guard.range.authoredInteger(opts.nonce, _err, "tsp/bad-input", "opts.nonce");
|
|
800
612
|
if (tst.nonce == null || tst.nonce !== wantNonce) return fail("tsp/nonce-mismatch");
|
|
801
613
|
}
|
|
802
|
-
// M15 -- if the requested policy is supplied, the token's policy MUST equal it.
|
|
803
614
|
if (opts.reqPolicy != null) {
|
|
804
615
|
var wantPolicy = oid.isDottedDecimal(opts.reqPolicy) ? opts.reqPolicy : O(opts.reqPolicy);
|
|
805
616
|
if (tst.policy !== wantPolicy) return fail("tsp/policy-mismatch");
|
|
806
617
|
}
|
|
807
|
-
// A TSTInfo carrying a CRITICAL extension the verifier cannot process is unusable: RFC 3161 sec.
|
|
808
|
-
// 2.4.2 defers extension handling to RFC 5280 sec. 4.2, and defines no TSTInfo extension, so any
|
|
809
|
-
// critical one is unrecognized (the unknown-critical-extension gate the cert path also applies).
|
|
810
|
-
// The scan for a critical extension IS the gate, so it is a loop rather than a `some`: one
|
|
811
|
-
// answering false makes the unrecognized critical extension invisible and the token is accepted.
|
|
812
618
|
if (tst.extensions && _anyCritical(tst.extensions)) {
|
|
813
619
|
return fail("tsp/unknown-critical-extension");
|
|
814
620
|
}
|
|
815
|
-
// M13 -- the ESSCertID(V2) binding: the verifying cert MUST be the one the signer identified.
|
|
816
621
|
var bind = _checkCertBinding(parsed.signerInfo, tsaCertDer);
|
|
817
622
|
if (bind !== true) return fail(bind);
|
|
818
|
-
// M11 -- the RFC 3161 sec. 2.3 critical single-timeStamping EKU gate + the keyUsage-permits-signing check.
|
|
819
623
|
var usage = _checkTsaCertUsage(tsaCertDer);
|
|
820
624
|
if (usage !== true) return fail(usage);
|
|
821
|
-
// RFC 5816 sec. 2.2.2 -- the tsa hint, when present, MUST name the signer certificate.
|
|
822
625
|
if (tst.tsa) {
|
|
823
626
|
var sc;
|
|
824
627
|
try { sc = pkiX509.parse(tsaCertDer); }
|
|
825
628
|
catch (_e) { return fail("tsp/bad-tsa-certificate"); }
|
|
826
629
|
if (!_tsaMatchesCert(tst.tsa.bytes, sc)) return fail("tsp/tsa-mismatch");
|
|
827
630
|
}
|
|
828
|
-
// M18 -- full out-of-path TSA-cert validation (issuer-sig, validity at genTime, unknown-critical,
|
|
829
|
-
// key-param inheritance, requiredEku, optional revocation), only when trustAnchors is supplied.
|
|
830
|
-
// The path is ordered from the token's embedded certificates (leaf + any intermediates), so a TSA
|
|
831
|
-
// issued under an intermediate CA -- not just directly under the anchor -- validates.
|
|
832
|
-
// `trusted` is the SECOND claim, kept apart from `valid`. `valid` says the token's signature and
|
|
833
|
-
// its structural bindings hold; whether the timestamp authority is one this caller accepts is
|
|
834
|
-
// answered only by the chain below, and only when an anchor was supplied. Collapsing the two into
|
|
835
|
-
// one boolean meant an archived verdict could not be re-read to tell whether the TSA was ever
|
|
836
|
-
// trusted -- which is exactly what a timestamp is archived to answer. Without an anchor there is
|
|
837
|
-
// nothing to chain to and `trusted` is false: a definite answer rather than a missing one, the
|
|
838
|
-
// same shape pki.cms.verify and pki.cmp.verify return.
|
|
839
631
|
var trusted = false;
|
|
840
632
|
if (opts.trustAnchors) {
|
|
841
633
|
var pathRes = null;
|
|
842
|
-
// tst.genTime floors to millisecond precision. When genTime carries sub-millisecond digits the true
|
|
843
|
-
// instant lies in (floor, floor+1ms], so fail closed on BOTH validity boundaries: require each
|
|
844
|
-
// candidate path to validate at the floor AND the ceil. A token minted just after the certificate
|
|
845
|
-
// expired is rejected at the ceil (floor <= notAfter but ceil > notAfter); one minted just before
|
|
846
|
-
// notBefore is rejected at the floor (ceil >= notBefore but floor < notBefore). Certificate
|
|
847
|
-
// validity is seconds-precise (RFC 5280 sec. 4.1.2.5), so this never over-rejects a valid token.
|
|
848
634
|
var floorT = tst.genTime;
|
|
849
635
|
var ceilT = (tst.genTimeFraction != null && _hasNonZeroDigit(tst.genTimeFraction.slice(3))) ? new Date(guard.time.instantOf(floorT) + 1) : floorT;
|
|
850
636
|
try {
|
|
851
|
-
// parseToken surfaces each embedded cert as { bytes, tagClass, ... }; take the X.509 certs
|
|
852
|
-
// (universal SEQUENCE), skipping any attribute-certificate choices, and parse their raw bytes.
|
|
853
|
-
// Caller out-of-band certificates (opts.certs) join the pool so an intermediate absent from the
|
|
854
|
-
// token can still complete the path.
|
|
855
637
|
var pool = (parsed.certificates || []).filter(function (c) { return c.tagClass === "universal"; }).map(function (c) { return pkiX509.parse(c.bytes); });
|
|
856
638
|
(opts.certs || []).forEach(function (c) { pool.push(pkiX509.parse(c)); });
|
|
857
|
-
// path.validate validates a FIXED path, so backtracking over same-subject issuer candidates
|
|
858
|
-
// happens here -- accept the TSA certificate if ANY enumerated chain validates to the anchor at
|
|
859
|
-
// both window endpoints.
|
|
860
|
-
//
|
|
861
|
-
// checkPurpose names timeStamping alongside requiredEku, and the pairing is the point. The EKU
|
|
862
|
-
// constrains the TSA CERTIFICATE; checkPurpose selects the ANCHOR's own trust metadata, which
|
|
863
|
-
// pki.path consults only when a purpose is named -- so asking one without the other checks one
|
|
864
|
-
// end of the chain and not the other, and a root explicitly distrusted for timestamping would
|
|
865
|
-
// still answer trusted. The purpose is not a caller choice here: this verb validates timestamp
|
|
866
|
-
// tokens and nothing else, so there is exactly one purpose its anchors can be judged under.
|
|
867
|
-
// EVERY path result goes through this one function, so none can be produced without being
|
|
868
|
-
// accumulated. The alternative -- validating in several places and accumulating afterwards --
|
|
869
|
-
// is what let a result be overwritten before it was counted, first across candidate chains and
|
|
870
|
-
// then across the two endpoints of one candidate. There is now one door and it always counts.
|
|
871
|
-
//
|
|
872
|
-
// The two fields accumulate INDEPENDENTLY because they are independent facts. Whether
|
|
873
|
-
// revocation was established and which anchor constraints were consulted do not imply each
|
|
874
|
-
// other: an anchor that rejects the TSA on its own purposes or distrustAfter metadata, with no
|
|
875
|
-
// revocationChecker configured, establishes constraints while establishing nothing about
|
|
876
|
-
// revocation.
|
|
877
639
|
async function validateAt(chain, when) {
|
|
878
640
|
var res = await pathValidate.validate(chain, {
|
|
879
641
|
time: when, trustAnchors: opts.trustAnchors, requiredEku: ["timeStamping"], checkPurpose: "timeStamping", revocationChecker: opts.revocationChecker,
|
|
880
642
|
});
|
|
881
|
-
// The most any attempt ESTABLISHED, kept across every attempt: backtracking tries several
|
|
882
|
-
// chains for one TSA certificate and a fractional genTime validates each at two endpoints,
|
|
883
|
-
// so an attempt that fails earlier than another must not erase what that other established.
|
|
884
643
|
if (_moreEstablished(res.revocationChecked, revocationChecked)) revocationChecked = res.revocationChecked;
|
|
885
|
-
// Every attempt validates against the SAME opts.trustAnchor under the same purpose, so any
|
|
886
|
-
// non-null value describes that one anchor; the first to report them is as good as the last.
|
|
887
644
|
if (anchorConstraints == null && res.anchorConstraints != null) anchorConstraints = res.anchorConstraints;
|
|
888
645
|
return res;
|
|
889
646
|
}
|
|
@@ -893,20 +650,9 @@ async function verify(token, data, opts) {
|
|
|
893
650
|
if (pathRes.valid && ceilT !== floorT) pathRes = await validateAt(chains[ci], ceilT);
|
|
894
651
|
}
|
|
895
652
|
} catch (e) { return fail("tsp/untrusted-tsa", (e && e.message) || String(e)); }
|
|
896
|
-
// On a REFUSAL the question is whether ANYTHING established the TSA's revocation status, so the
|
|
897
|
-
// accumulated best answers it: a candidate that reached the checker and was rejected as revoked
|
|
898
|
-
// did check revocation, and saying otherwise would misdescribe the reason for the refusal.
|
|
899
653
|
if (!pathRes || !pathRes.valid) return fail("tsp/untrusted-tsa", "the TSA certificate did not validate to the trust anchor at genTime");
|
|
900
|
-
// On ACCEPTANCE the question is narrower and the accumulated best would OVERCLAIM: what matters
|
|
901
|
-
// is what was checked on the path actually accepted, not what some rejected candidate managed.
|
|
902
|
-
// The loop stops at the first valid path, so `pathRes` is that path.
|
|
903
654
|
revocationChecked = pathRes.revocationChecked;
|
|
904
655
|
anchorConstraints = pathRes.anchorConstraints;
|
|
905
|
-
// Reduced to a bare `trusted: true`, this answered the same way whether the TSA was established
|
|
906
|
-
// un-revoked or revocation was never consulted at all -- and revocation only runs when a
|
|
907
|
-
// revocationChecker is supplied, so the second case is the DEFAULT. A caller archiving a
|
|
908
|
-
// timestamp verdict could not tell the two apart later, which is the distinction the
|
|
909
|
-
// valid/trusted split exists to keep.
|
|
910
656
|
trusted = true;
|
|
911
657
|
}
|
|
912
658
|
return {
|