@blamejs/pki 0.5.6 → 0.5.8
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 +419 -378
- package/MIGRATING.md +65 -0
- package/README.md +12 -12
- package/lib/acme.js +31 -31
- package/lib/asn1-der.js +10 -10
- package/lib/attrcert-sign.js +24 -20
- package/lib/byte-reader.js +6 -6
- package/lib/byte-writer.js +5 -5
- package/lib/cbor-det.js +27 -24
- package/lib/cmc-build.js +104 -104
- package/lib/cmc-verify.js +108 -32
- package/lib/cmp-build.js +31 -26
- package/lib/cmp-session.js +74 -72
- package/lib/cmp-verify.js +72 -58
- package/lib/cms-compress.js +8 -9
- package/lib/cms-decrypt.js +92 -76
- package/lib/cms-encrypt.js +33 -34
- package/lib/cms-sign.js +100 -54
- package/lib/cms-verify.js +141 -82
- package/lib/composite-sig.js +13 -13
- package/lib/constants.js +4 -4
- package/lib/crl-sign.js +31 -25
- package/lib/crl-verify.js +7 -6
- package/lib/crmf-sign.js +19 -15
- package/lib/csr-sign.js +13 -9
- package/lib/ct.js +37 -37
- package/lib/edwards-point.js +7 -7
- package/lib/est.js +103 -59
- package/lib/framework-error.js +5 -5
- package/lib/guard-all.js +3 -3
- package/lib/guard-async.js +4 -4
- package/lib/guard-bytes.js +378 -15
- package/lib/guard-compress.js +17 -17
- package/lib/guard-crypto.js +1 -1
- package/lib/guard-encoding.js +15 -15
- package/lib/guard-header.js +3 -3
- package/lib/guard-identifier.js +16 -16
- package/lib/guard-json.js +15 -15
- package/lib/guard-limits.js +7 -7
- package/lib/guard-name.js +81 -16
- package/lib/guard-parsed.js +144 -75
- package/lib/guard-range.js +19 -19
- package/lib/guard-secret.js +11 -10
- package/lib/guard-text.js +6 -6
- package/lib/guard-time.js +10 -10
- package/lib/hpke.js +18 -17
- package/lib/http-digest.js +35 -35
- package/lib/http-retry-after.js +13 -13
- package/lib/http-transport.js +20 -19
- package/lib/inspect.js +53 -53
- package/lib/ip-utils.js +2 -2
- package/lib/jose.js +13 -13
- package/lib/key.js +16 -16
- package/lib/lint.js +51 -51
- package/lib/merkle.js +51 -36
- package/lib/mime.js +18 -18
- package/lib/ocsp-verify.js +10 -10
- package/lib/ocsp.js +32 -20
- package/lib/oid.js +29 -29
- package/lib/path-validate.js +114 -113
- package/lib/pbes2.js +16 -16
- package/lib/pkcs12-build.js +71 -56
- package/lib/pki-build.js +23 -22
- package/lib/rc2.js +1 -1
- package/lib/rfc3339.js +5 -5
- package/lib/schema-all.js +31 -31
- package/lib/schema-attrcert.js +12 -12
- package/lib/schema-c509.js +144 -142
- package/lib/schema-cmc.js +58 -58
- package/lib/schema-cmp.js +43 -43
- package/lib/schema-cms.js +169 -36
- package/lib/schema-crl.js +7 -7
- package/lib/schema-crmf.js +28 -28
- package/lib/schema-csr.js +12 -12
- package/lib/schema-csrattrs.js +16 -16
- package/lib/schema-engine.js +18 -18
- package/lib/schema-ocsp.js +15 -15
- package/lib/schema-pkcs12.js +20 -20
- package/lib/schema-pkcs8.js +2 -2
- package/lib/schema-pkix.js +131 -126
- package/lib/schema-smime.js +19 -19
- package/lib/schema-tsp.js +12 -12
- package/lib/schema-x509.js +3 -3
- package/lib/shbs.js +18 -18
- package/lib/sign-scheme.js +19 -16
- package/lib/sigstore.js +10 -11
- package/lib/sleep.js +1 -1
- package/lib/smime.js +308 -96
- package/lib/tls-cert-compress.js +18 -18
- package/lib/trust.js +27 -27
- package/lib/tsp-sign.js +22 -18
- package/lib/validator-all.js +1 -1
- package/lib/validator-attcert.js +1 -1
- package/lib/validator-cose.js +43 -44
- package/lib/validator-keydesc.js +3 -3
- package/lib/validator-sig.js +13 -13
- package/lib/validator-tls.js +11 -11
- package/lib/validator-tpm.js +21 -20
- package/lib/webauthn-mds.js +67 -67
- package/lib/webauthn.js +34 -34
- package/lib/webcrypto.js +15 -15
- package/lib/x509-sign.js +24 -15
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/schema-cms.js
CHANGED
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
* CMS handling per RFC 5652 (sec. 3 ContentInfo envelope). `parse` turns a DER or PEM
|
|
13
13
|
* (`CMS`) message into a structured object and is an OID-dispatch envelope --
|
|
14
14
|
* ContentInfo reads its `contentType` and structurally decodes SignedData (sec. 5),
|
|
15
|
-
* EnvelopedData (sec. 6, with all five RecipientInfo kinds
|
|
15
|
+
* EnvelopedData (sec. 6, with all five RecipientInfo kinds: key-transport,
|
|
16
16
|
* key-agreement per RFC 5753, KEK, password, and other, including the RFC 9629
|
|
17
17
|
* KEMRecipientInfo carried under `id-ori-kem` with ML-KEM per RFC 9936),
|
|
18
18
|
* EncryptedData (sec. 8), AuthenticatedData (sec. 9), and AuthEnvelopedData (RFC 5083,
|
|
19
19
|
* with RFC 5084 AES-GCM/CCM parameter validation); the remaining PKCS#7 content
|
|
20
20
|
* types are recognized and rejected with a precise `cms/unsupported-content-type`
|
|
21
|
-
*
|
|
21
|
+
* in place of a generic unknown-format error. A SignedData surfaces its version,
|
|
22
22
|
* digest algorithms, encapsulated content, certificate / CRL sets, and signer
|
|
23
23
|
* infos; an EnvelopedData its recipient infos and encrypted content info; an
|
|
24
24
|
* EncryptedData its encrypted content info; an AuthenticatedData its MAC
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
* content, validated AEAD parameters, and raw `mac`.
|
|
28
28
|
*
|
|
29
29
|
* CMS is a signed container: the bytes an external verifier must hash are
|
|
30
|
-
* surfaced
|
|
30
|
+
* surfaced raw and never re-serialized. `encapContentInfo.eContent` is the raw
|
|
31
31
|
* content (or `null` for a detached signature); each SignerInfo's `signature` is
|
|
32
32
|
* raw, and `signedAttrsBytes` is the on-wire `[0]` SignedAttributes TLV so a
|
|
33
33
|
* verifier can re-tag it to the universal SET the signature is computed over
|
|
34
|
-
* (sec. 5.4)
|
|
34
|
+
* (sec. 5.4); `authAttrsBytes` plays the same role for the sec. 9.2 MAC input and the
|
|
35
35
|
* RFC 5083 sec. 2.2 AAD. Embedded certificates and CRLs are surfaced as raw DER +
|
|
36
36
|
* their outer tag, validated against the closed CertificateChoices /
|
|
37
37
|
* RevocationInfoChoice tag sets, so an obsolete alternative never fails the
|
|
@@ -63,7 +63,7 @@ var ALGORITHM_IDENTIFIER = pkix.algorithmIdentifier(NS);
|
|
|
63
63
|
var ATTRIBUTE = pkix.attribute(NS);
|
|
64
64
|
var NAME = pkix.name(NS);
|
|
65
65
|
|
|
66
|
-
// CMSVersion ::= INTEGER
|
|
66
|
+
// CMSVersion ::= INTEGER, where the SignedData version is {1,3,4,5} and the SignerInfo
|
|
67
67
|
// version is {1,3} (RFC 5652 sec. 5.1, sec. 5.3). Wider accept maps than any other format.
|
|
68
68
|
var SIGNED_DATA_VERSION = pkix.versionReader(NS, { "1": 1, "3": 3, "4": 4, "5": 5 });
|
|
69
69
|
var SIGNER_VERSION = pkix.versionReader(NS, { "1": 1, "3": 3 });
|
|
@@ -188,7 +188,7 @@ function _checkContentBindingAttrs(attrs, mode) {
|
|
|
188
188
|
// content-type attribute (what it signs has no content type).
|
|
189
189
|
if (mode === "countersig") throw NS.E("cms/misplaced-attr", "a countersignature's signedAttrs must not carry a content-type attribute (RFC 5652 sec. 11.4)");
|
|
190
190
|
if (a.values.length !== 1) throw NS.E("cms/bad-content-type-attr", "the content-type attribute must be single-valued");
|
|
191
|
-
// ContentType ::= OBJECT IDENTIFIER (RFC 5652 sec. 11.1)
|
|
191
|
+
// ContentType ::= OBJECT IDENTIFIER (RFC 5652 sec. 11.1): validate the value's
|
|
192
192
|
// full syntax (tag AND minimal base-128 OID content), not just the tag, so a
|
|
193
193
|
// truncated / non-minimal subidentifier is rejected here, not at verify time.
|
|
194
194
|
try { asn1.read.oid(asn1.decode(a.values[0])); }
|
|
@@ -196,7 +196,7 @@ function _checkContentBindingAttrs(attrs, mode) {
|
|
|
196
196
|
} else if (a.type === OID_MESSAGE_DIGEST) {
|
|
197
197
|
md += 1;
|
|
198
198
|
if (a.values.length !== 1) throw NS.E("cms/bad-message-digest-attr", "the message-digest attribute must be single-valued");
|
|
199
|
-
// MessageDigest ::= OCTET STRING (RFC 5652 sec. 11.2)
|
|
199
|
+
// MessageDigest ::= OCTET STRING (RFC 5652 sec. 11.2): validate the full syntax.
|
|
200
200
|
try { asn1.read.octetString(asn1.decode(a.values[0])); }
|
|
201
201
|
catch (e) { throw NS.E("cms/bad-message-digest-attr", "the message-digest attribute value must be an OCTET STRING", e); }
|
|
202
202
|
} else if (a.type === OID_SIGNING_TIME) {
|
|
@@ -212,17 +212,149 @@ function _checkContentBindingAttrs(attrs, mode) {
|
|
|
212
212
|
if (md === 0) throw NS.E("cms/missing-message-digest", "the attribute set must contain a message-digest attribute (RFC 5652 sec. 11.2)");
|
|
213
213
|
}
|
|
214
214
|
|
|
215
|
-
//
|
|
215
|
+
// looksLikeSignedAttributes(bytes) -> boolean.
|
|
216
|
+
//
|
|
217
|
+
// Does `bytes` parse as a DER SignedAttributes block, a SET OF Attribute carrying both the
|
|
218
|
+
// content-type and message-digest attributes RFC 5652 sec. 5.3 makes mandatory whenever signed
|
|
219
|
+
// attributes are present?
|
|
220
|
+
//
|
|
221
|
+
// This is the detector for the signed-attribute stripping forgery
|
|
222
|
+
// (draft-vangeest-lamps-cms-euf-cma-signeddata, Attack Type 1). A CMS signature does not commit to
|
|
223
|
+
// WHETHER signed attributes were present, so a signature made over a SignedAttributes block can be
|
|
224
|
+
// re-presented as one made over content: drop the signedAttrs field and set the encapsulated
|
|
225
|
+
// content to the DER of those same attributes. Sec. 5.4 then says the signature is over the content
|
|
226
|
+
// itself, which is exactly what it covers, and with no attributes there is no message-digest or
|
|
227
|
+
// content-type attribute left to disagree. The proposed standards fixes are protocol changes (a
|
|
228
|
+
// context string naming which mode was signed) that no verifier can apply on its own.
|
|
229
|
+
//
|
|
230
|
+
// What a verifier CAN do is refuse the shape. Every message produced by the attack has, as its
|
|
231
|
+
// content, the encoded SignedAttributes of a real message, and sec. 5.3 requires those to carry
|
|
232
|
+
// both attributes named above. That makes their presence a NECESSARY condition of the attack rather
|
|
233
|
+
// than a guess, and the shape is one ordinary content does not have: a certificate, a JSON payload,
|
|
234
|
+
// arbitrary bytes, and even a SET OF other attributes all fail it. The cost is a message whose
|
|
235
|
+
// legitimate content really is an encoded SignedAttributes block signed without attributes, which
|
|
236
|
+
// is refused as genuinely ambiguous -- sign it with attributes and it is unambiguous again.
|
|
237
|
+
//
|
|
238
|
+
// One mandatory attribute value, held to all three conditions a real SignedAttributes meets:
|
|
239
|
+
// exactly one value (RFC 5652 sec. 11.1 / sec. 11.2 make both single-valued), the right tag, and a
|
|
240
|
+
// body that actually reads as that type. All three together, for each attribute; checking the
|
|
241
|
+
// cardinality and the tag while letting an undecodable body through would refuse content the real
|
|
242
|
+
// SignedAttributes parser could never have produced, which is the false positive this whole
|
|
243
|
+
// detector is shaped to avoid.
|
|
244
|
+
function _readsAs(vals, tagNumber, reader) {
|
|
245
|
+
if (vals.length !== 1 || !schema.isUniversal(vals[0], tagNumber)) return false;
|
|
246
|
+
var reads = true;
|
|
247
|
+
try { reader(vals[0]); }
|
|
248
|
+
catch (_e) {
|
|
249
|
+
reads = false; // right tag, unreadable body -- not a preimage anything signed
|
|
250
|
+
}
|
|
251
|
+
return reads;
|
|
252
|
+
}
|
|
253
|
+
// SigningTime ::= Time is a CHOICE of UTCTime and GeneralizedTime, so the tag is one of two and the
|
|
254
|
+
// reader settles which -- _readsAs pins a single tag and cannot express it.
|
|
255
|
+
function _readsAsTime(vals) {
|
|
256
|
+
if (vals.length !== 1) return false;
|
|
257
|
+
var reads = true;
|
|
258
|
+
try { asn1.read.time(vals[0]); }
|
|
259
|
+
catch (_e) {
|
|
260
|
+
reads = false;
|
|
261
|
+
}
|
|
262
|
+
return reads;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Deliberately a total function: it answers about arbitrary attacker bytes and must never throw.
|
|
266
|
+
// The X.690 sec. 11.6 SET OF ordering rule, as the schema engine applies it: each component's
|
|
267
|
+
// encoding is greater than or equal to the one before it.
|
|
268
|
+
function _ascendingDer(nodes) {
|
|
269
|
+
for (var i = 1; i < nodes.length; i++) {
|
|
270
|
+
if (Buffer.compare(nodes[i - 1].bytes, nodes[i].bytes) > 0) return false;
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function looksLikeSignedAttributes(bytes) {
|
|
276
|
+
if (!bytes || !bytes.length) return false;
|
|
277
|
+
var node;
|
|
278
|
+
try { node = asn1.decode(bytes); }
|
|
279
|
+
catch (_e) { return false; } // not DER at all -- not this shape
|
|
280
|
+
if (!schema.isUniversal(node, asn1.TAGS.SET) || !node.constructed) return false;
|
|
281
|
+
var kids = node.children || [];
|
|
282
|
+
if (!kids.length) return false;
|
|
283
|
+
// Every rule _checkContentBindingAttrs applies to a real SignedAttributes is applied here, and
|
|
284
|
+
// for one reason: the detector must match only what a conforming block can be. A set the real
|
|
285
|
+
// parser would have rejected cannot be the preimage of any signature, so matching it would refuse
|
|
286
|
+
// content no attack could have produced. Enumerated against that function, never discovered
|
|
287
|
+
// one rule at a time -- no duplicate attribute types (sec. 5.3), content-type single-valued and a
|
|
288
|
+
// readable OID (sec. 11.1), message-digest single-valued and a readable OCTET STRING (sec. 11.2),
|
|
289
|
+
// signing-time when present single-valued and a readable Time (sec. 11.3).
|
|
290
|
+
// X.690 sec. 11.6: the components of a SET OF appear in ascending DER order, and the schema
|
|
291
|
+
// engine enforces exactly that on the real SignedAttributes. An out-of-order set is one the
|
|
292
|
+
// walker refuses, so it is not the preimage of any signature either.
|
|
293
|
+
if (!_ascendingDer(kids)) return false;
|
|
294
|
+
var sawContentType = false, sawMessageDigest = false, seenTypes = Object.create(null);
|
|
295
|
+
for (var i = 0; i < kids.length; i++) {
|
|
296
|
+
var a = kids[i];
|
|
297
|
+
// Attribute ::= SEQUENCE { attrType OBJECT IDENTIFIER, attrValues SET OF ANY }
|
|
298
|
+
if (!schema.isUniversal(a, asn1.TAGS.SEQUENCE)) return false;
|
|
299
|
+
if (!a.children || a.children.length !== 2) return false;
|
|
300
|
+
var t = a.children[0], vs = a.children[1];
|
|
301
|
+
if (!schema.isUniversal(t, asn1.TAGS.OBJECT_IDENTIFIER)) return false;
|
|
302
|
+
if (!schema.isUniversal(vs, asn1.TAGS.SET)) return false;
|
|
303
|
+
var attrOid;
|
|
304
|
+
try { attrOid = asn1.read.oid(t); }
|
|
305
|
+
catch (_e2) { return false; }
|
|
306
|
+
if (seenTypes[attrOid]) return false; // a repeated attribute type -- sec. 5.3 forbids it
|
|
307
|
+
seenTypes[attrOid] = true;
|
|
308
|
+
// The sec. 11 PLACEMENT rows, from the same table the real parser reads. An attribute the
|
|
309
|
+
// parser refuses to see in signedAttrs -- id-countersignature is the one sec. 11.4 names --
|
|
310
|
+
// cannot appear in a conforming SignedAttributes, so a set containing it is not a preimage any
|
|
311
|
+
// signature covers. Missing this row was the difference between a necessary condition and a
|
|
312
|
+
// guess: it would have refused ordinary content that merely carried that attribute encoding.
|
|
313
|
+
var placement = ATTR_FORBIDDEN_IN[attrOid];
|
|
314
|
+
if (placement && placement.signed) return false;
|
|
315
|
+
// RFC 5652 gives every AttributeValue set SIZE (1..MAX), so an attribute with an EMPTY value
|
|
316
|
+
// set is one no conforming signer produced and no signature covers. The upper bound is
|
|
317
|
+
// deliberately not applied: this decoder caps values per attribute as a resource limit of its
|
|
318
|
+
// own, and a limit this implementation chose is not a fact about what a signature can cover.
|
|
319
|
+
// An external signer may sign a conforming set larger than that cap, and the stripped message
|
|
320
|
+
// presents those bytes as opaque content where the cap never applies, so refusing to recognize
|
|
321
|
+
// it because of a local limit would miss exactly the preimage the attack reuses.
|
|
322
|
+
var n = (vs.children || []).length;
|
|
323
|
+
if (n < 1) return false;
|
|
324
|
+
if (!_ascendingDer(vs.children || [])) return false; // the inner SET OF is ordered too
|
|
325
|
+
// The two mandatory attributes are checked down to their VALUES, not just their type OIDs.
|
|
326
|
+
// RFC 5652 sec. 11.1 makes content-type a single OBJECT IDENTIFIER and sec. 11.2 makes
|
|
327
|
+
// message-digest a single OCTET STRING, so a set carrying those OIDs over an empty or
|
|
328
|
+
// wrongly-typed value cannot be the preimage of a real signature, and refusing it would be a
|
|
329
|
+
// false positive on content that merely resembles the shape. The detector has to stay a
|
|
330
|
+
// necessary condition of the attack; anything broader costs a legitimate caller.
|
|
331
|
+
var vals = vs.children || [];
|
|
332
|
+
if (attrOid === OID_CONTENT_TYPE) {
|
|
333
|
+
if (!_readsAs(vals, asn1.TAGS.OBJECT_IDENTIFIER, asn1.read.oid)) return false;
|
|
334
|
+
sawContentType = true;
|
|
335
|
+
} else if (attrOid === OID_MESSAGE_DIGEST) {
|
|
336
|
+
if (!_readsAs(vals, asn1.TAGS.OCTET_STRING, asn1.read.octetString)) return false;
|
|
337
|
+
sawMessageDigest = true;
|
|
338
|
+
} else if (attrOid === OID_SIGNING_TIME) {
|
|
339
|
+
// Not mandatory, but when present sec. 11.3 constrains it the same way, so a set carrying an
|
|
340
|
+
// unreadable signing-time is one the real parser would have refused.
|
|
341
|
+
if (!_readsAsTime(vals)) return false;
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return sawContentType && sawMessageDigest;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// RFC 5652 sec. 5.3 / sec. 9.3: when a content-type attribute is present, it MUST
|
|
216
348
|
// be single-valued (sec. 11.1) and its value MUST equal the eContentType (a
|
|
217
349
|
// cross-field consistency both parsed here). Shared by SignedData signedAttrs,
|
|
218
|
-
// AuthenticatedData authAttrs, and AuthEnvelopedData authAttrs
|
|
350
|
+
// AuthenticatedData authAttrs, and AuthEnvelopedData authAttrs, where the single-value
|
|
219
351
|
// rule holds even where content-type is not REQUIRED (RFC 5083), so an
|
|
220
352
|
// expected-first-value-plus-extra set can never surface as ambiguous.
|
|
221
353
|
function _assertContentTypeMatchesAttrs(attrs, eContentType) {
|
|
222
354
|
for (var i = 0; i < attrs.length; i++) {
|
|
223
355
|
if (attrs[i].type !== OID_CONTENT_TYPE) continue;
|
|
224
356
|
if (attrs[i].values.length !== 1) throw NS.E("cms/bad-content-type-attr", "the content-type attribute must be single-valued (RFC 5652 sec. 11.1)");
|
|
225
|
-
// ContentType ::= OBJECT IDENTIFIER
|
|
357
|
+
// ContentType ::= OBJECT IDENTIFIER: validate the value's full syntax (tag and
|
|
226
358
|
// minimal base-128 OID content) with the CMS typed verdict, so a malformed value
|
|
227
359
|
// on a path that does not run _checkContentBindingAttrs (AuthEnvelopedData,
|
|
228
360
|
// RFC 5083 sec. 2.1) surfaces cms/bad-content-type-attr, not the raw asn1/* error.
|
|
@@ -234,8 +366,8 @@ function _assertContentTypeMatchesAttrs(attrs, eContentType) {
|
|
|
234
366
|
}
|
|
235
367
|
|
|
236
368
|
// RFC 5652 sec. 5.3 -- an attribute set MUST NOT include multiple instances of the
|
|
237
|
-
// same attribute type. AuthEnvelopedData authAttrs (RFC 5083 sec. 2.1) gets
|
|
238
|
-
// duplicate check
|
|
369
|
+
// same attribute type. AuthEnvelopedData authAttrs (RFC 5083 sec. 2.1) gets only this
|
|
370
|
+
// duplicate check, and not the content-type/message-digest presence rules (RFC 5652
|
|
239
371
|
// sec. 11.1/sec. 11.2 bind only signed-data and authenticated-data), so it must not reuse
|
|
240
372
|
// _checkContentBindingAttrs, which would over-enforce.
|
|
241
373
|
function _checkNoDuplicateAttrs(attrs) {
|
|
@@ -286,7 +418,7 @@ function _validateAeadParams(alg, macLen) {
|
|
|
286
418
|
}
|
|
287
419
|
var legal = kind === "gcm" ? AEAD_GCM_ICVLENS : AEAD_CCM_ICVLENS;
|
|
288
420
|
if (!legal.has(icvLen)) throw NS.E("cms/bad-aead-params", "the AES-" + K + " aes-ICVlen " + icvLen + " is not an allowed value (RFC 5084)");
|
|
289
|
-
// X.690 sec. 11.5: a DEFAULT value MUST be omitted in DER
|
|
421
|
+
// X.690 sec. 11.5: a DEFAULT value MUST be omitted in DER, so an encoded ICVlen equal
|
|
290
422
|
// to the default 12 is non-canonical.
|
|
291
423
|
if (icvEncoded && icvLen === 12) throw NS.E("cms/non-canonical-default", "the AEAD aes-ICVlen equal to the DEFAULT 12 MUST be omitted (X.690 sec. 11.5)");
|
|
292
424
|
// RFC 5084 sec. 3.1/sec. 3.2: aes-ICVlen MUST match the AuthEnvelopedData mac length.
|
|
@@ -294,7 +426,7 @@ function _validateAeadParams(alg, macLen) {
|
|
|
294
426
|
return { kind: kind, nonce: nonce, icvLen: icvLen };
|
|
295
427
|
}
|
|
296
428
|
|
|
297
|
-
// RFC 5652 sec. 5.3 / sec. 9.1 + RFC 5083 sec. 2.1
|
|
429
|
+
// RFC 5652 sec. 5.3 / sec. 9.1 + RFC 5083 sec. 2.1: a signed / authenticated attribute SET
|
|
298
430
|
// MUST be DER encoded even when the enclosing structure was decoded as BER (its
|
|
299
431
|
// bytes feed the sec. 5.4 / sec. 9.2 re-tagged hash / MAC input, so a non-DER TLV makes
|
|
300
432
|
// the surfaced raw bytes unusable for verification). Strictly re-decoding the
|
|
@@ -305,8 +437,8 @@ function _assertDerEncodedAttrs(node, code) {
|
|
|
305
437
|
catch (e) { throw NS.E(code, "the attribute set must be DER encoded even inside a BER envelope (RFC 5652 sec. 5.3)", e); }
|
|
306
438
|
}
|
|
307
439
|
|
|
308
|
-
// A CertificateChoices / RevocationInfoChoice element, surfaced
|
|
309
|
-
// outer tag)
|
|
440
|
+
// A CertificateChoices / RevocationInfoChoice element, surfaced raw (its DER +
|
|
441
|
+
// outer tag) and never recursively parsed: the obsolete CHOICE alternatives
|
|
310
442
|
// (extendedCertificate, attribute certs, otherRevocationInfo) never fail the
|
|
311
443
|
// parse, and a caller re-parses a `certificate`/`CertificateList` element itself.
|
|
312
444
|
function rawElement(item) {
|
|
@@ -383,7 +515,7 @@ var ISSUER_AND_SERIAL = schema.seq([
|
|
|
383
515
|
});
|
|
384
516
|
|
|
385
517
|
// SignerIdentifier ::= CHOICE { issuerAndSerialNumber IssuerAndSerialNumber,
|
|
386
|
-
// subjectKeyIdentifier [0] IMPLICIT OCTET STRING } (RFC 5652 sec. 5.3)
|
|
518
|
+
// subjectKeyIdentifier [0] IMPLICIT OCTET STRING } (RFC 5652 sec. 5.3), where the arm is
|
|
387
519
|
// disambiguated by tag (universal SEQUENCE vs context [0]).
|
|
388
520
|
var SIGNER_IDENTIFIER = schema.choice([
|
|
389
521
|
{ when: { tagClass: "universal", tagNumber: asn1.TAGS.SEQUENCE }, schema: ISSUER_AND_SERIAL },
|
|
@@ -394,10 +526,10 @@ var SIGNER_IDENTIFIER = schema.choice([
|
|
|
394
526
|
// signedAttrs [0] IMPLICIT OPTIONAL, signatureAlgorithm, signature OCTET STRING,
|
|
395
527
|
// unsignedAttrs [1] IMPLICIT OPTIONAL } (RFC 5652 sec. 5.3). signedAttrs/unsignedAttrs
|
|
396
528
|
// are positional optionals (a required signatureAlgorithm sits between them, so
|
|
397
|
-
// they cannot be a trailing block).
|
|
529
|
+
// they cannot be a trailing block). One definition drives both consumers: the
|
|
398
530
|
// signerInfos of a SignedData (mode "content") and a countersignature attribute
|
|
399
531
|
// value (mode "countersig", RFC 5652 sec. 11.4: syntactically a SignerInfo whose
|
|
400
|
-
// signedAttrs MUST carry message-digest and MUST NOT carry content-type)
|
|
532
|
+
// signedAttrs MUST carry message-digest and MUST NOT carry content-type), so
|
|
401
533
|
// the two can never diverge structurally.
|
|
402
534
|
function makeSignerInfo(mode) {
|
|
403
535
|
return schema.seq([
|
|
@@ -442,7 +574,7 @@ function makeSignerInfo(mode) {
|
|
|
442
574
|
// sec. 11.4 -- every countersignature value IS a SignerInfo (validated by
|
|
443
575
|
// content, never accepted on the attribute type alone). Multiple
|
|
444
576
|
// countersignature instances are explicitly permitted here, and a
|
|
445
|
-
// countersignature's own unsignedAttrs may nest further ones
|
|
577
|
+
// countersignature's own unsignedAttrs may nest further ones, so the
|
|
446
578
|
// recursion is bounded by the decoder's depth cap.
|
|
447
579
|
for (var u = 0; u < unsignedAttrs.length; u++) {
|
|
448
580
|
if (unsignedAttrs[u].type !== OID_COUNTERSIGNATURE) continue;
|
|
@@ -473,7 +605,7 @@ var COUNTERSIGNATURE_SIGNER_INFO = makeSignerInfo("countersig");
|
|
|
473
605
|
// SignedData ::= SEQUENCE { version CMSVersion, digestAlgorithms SET OF,
|
|
474
606
|
// encapContentInfo, certificates [0] IMPLICIT OPTIONAL, crls [1] IMPLICIT
|
|
475
607
|
// OPTIONAL, signerInfos SET OF } (RFC 5652 sec. 5.1). digestAlgorithms and
|
|
476
|
-
// signerInfos are min:0
|
|
608
|
+
// signerInfos are min:0, since a degenerate certs-only SignedData carries neither.
|
|
477
609
|
var SIGNED_DATA = schema.seq([
|
|
478
610
|
schema.field("version", SIGNED_DATA_VERSION),
|
|
479
611
|
schema.field("digestAlgorithms", schema.setOf(ALGORITHM_IDENTIFIER, { min: 0, code: "cms/bad-digest-algorithms", what: "digestAlgorithms" })),
|
|
@@ -526,10 +658,10 @@ var T = asn1.TAGS;
|
|
|
526
658
|
|
|
527
659
|
// EncryptedContentInfo ::= SEQUENCE { contentType OID, contentEncryptionAlgorithm
|
|
528
660
|
// AlgorithmIdentifier, encryptedContent [0] IMPLICIT OCTET STRING OPTIONAL } (RFC
|
|
529
|
-
// 5652 sec. 6.1). encryptedContent is [0] IMPLICIT (context
|
|
661
|
+
// 5652 sec. 6.1). encryptedContent is [0] IMPLICIT (context primitive), so its content
|
|
530
662
|
// octets ARE the ciphertext directly, so it reads through implicitOctetString(0),
|
|
531
|
-
//
|
|
532
|
-
// length header). The ciphertext + algorithm parameters are surfaced
|
|
663
|
+
// and not the [0] EXPLICIT shape ENCAP_CONTENT_INFO uses (which would double-strip a
|
|
664
|
+
// length header). The ciphertext + algorithm parameters are surfaced raw.
|
|
533
665
|
var ENCRYPTED_CONTENT_INFO = schema.seq([
|
|
534
666
|
schema.field("contentType", schema.oidLeaf()),
|
|
535
667
|
schema.field("contentEncryptionAlgorithm", ALGORITHM_IDENTIFIER),
|
|
@@ -546,7 +678,7 @@ var ENCRYPTED_CONTENT_INFO = schema.seq([
|
|
|
546
678
|
});
|
|
547
679
|
|
|
548
680
|
// RecipientIdentifier ::= CHOICE { issuerAndSerialNumber, subjectKeyIdentifier [0]
|
|
549
|
-
// IMPLICIT OCTET STRING } (RFC 5652 sec. 6.2.1)
|
|
681
|
+
// IMPLICIT OCTET STRING } (RFC 5652 sec. 6.2.1), structurally identical to
|
|
550
682
|
// SignerIdentifier; reuse ISSUER_AND_SERIAL + the implicitOctetString(0) leaf.
|
|
551
683
|
var RECIPIENT_IDENTIFIER = schema.choice([
|
|
552
684
|
{ when: { tagClass: "universal", tagNumber: T.SEQUENCE }, schema: ISSUER_AND_SERIAL },
|
|
@@ -580,7 +712,7 @@ var KEY_TRANS_RECIPIENT_INFO = schema.seq([
|
|
|
580
712
|
});
|
|
581
713
|
|
|
582
714
|
// OriginatorPublicKey ::= SEQUENCE { algorithm, publicKey BIT STRING } (RFC 5753
|
|
583
|
-
// sec. 3.1.1), reached as originatorKey [1] IMPLICIT
|
|
715
|
+
// sec. 3.1.1), reached as originatorKey [1] IMPLICIT: SPKI-shaped but cannot reuse
|
|
584
716
|
// pkix.spki (which asserts a universal SEQUENCE), so assert:"constructed".
|
|
585
717
|
var ORIGINATOR_PUBLIC_KEY = schema.seq([
|
|
586
718
|
schema.field("algorithm", ALGORITHM_IDENTIFIER),
|
|
@@ -600,7 +732,7 @@ var ORIGINATOR_IDENTIFIER_OR_KEY = schema.choice([
|
|
|
600
732
|
|
|
601
733
|
// RecipientKeyIdentifier (RFC 5652 sec. 6.2.2) and KEKIdentifier (sec. 6.2.3) are one
|
|
602
734
|
// shape -- { <keyId> OCTET STRING, date GeneralizedTime OPTIONAL, other
|
|
603
|
-
// OtherKeyAttribute OPTIONAL }
|
|
735
|
+
// OtherKeyAttribute OPTIONAL }, differing only in the key-id field's name and
|
|
604
736
|
// the enclosing tag form. One factory defines both so the OPTIONAL handling
|
|
605
737
|
// (date and the raw-surfaced OtherKeyAttribute) cannot diverge between them.
|
|
606
738
|
function keyIdentifierSchema(keyIdName, assert, code, what) {
|
|
@@ -620,7 +752,7 @@ function keyIdentifierSchema(keyIdName, assert, code, what) {
|
|
|
620
752
|
});
|
|
621
753
|
}
|
|
622
754
|
|
|
623
|
-
// Reached as rKeyId [0] IMPLICIT (a SEQUENCE
|
|
755
|
+
// Reached as rKeyId [0] IMPLICIT (a SEQUENCE, constructed, unlike ktri's [0] leaf).
|
|
624
756
|
var RECIPIENT_KEY_IDENTIFIER = keyIdentifierSchema("subjectKeyIdentifier",
|
|
625
757
|
"constructed", "cms/bad-recipient-key-identifier", "RecipientKeyIdentifier");
|
|
626
758
|
|
|
@@ -813,7 +945,7 @@ var RECIPIENT_INFO = schema.choice([
|
|
|
813
945
|
], { code: "cms/bad-recipient-info", what: "RecipientInfo" });
|
|
814
946
|
|
|
815
947
|
// OriginatorInfo ::= [0] IMPLICIT SEQUENCE { certs [0] IMPLICIT OPTIONAL, crls [1]
|
|
816
|
-
// IMPLICIT OPTIONAL } (RFC 5652 sec. 6.1). Members surfaced
|
|
948
|
+
// IMPLICIT OPTIONAL } (RFC 5652 sec. 6.1). Members surfaced raw (their outer tag feeds
|
|
817
949
|
// the version rule).
|
|
818
950
|
var ORIGINATOR_INFO = schema.seq([
|
|
819
951
|
schema.optional("certs", schema.implicitSetOf(0, schema.any(), { min: 1, code: "cms/bad-originator-certs", what: "certs" }), { tag: 0 }),
|
|
@@ -845,8 +977,8 @@ function _expectedEnvelopedDataVersion(originatorInfo, recipientInfos, hasUnprot
|
|
|
845
977
|
}
|
|
846
978
|
|
|
847
979
|
// RFC 5652 sec. 9.1 -- the exact AuthenticatedData CMSVersion, from originatorInfo's raw
|
|
848
|
-
// cert/crl outer tags
|
|
849
|
-
//
|
|
980
|
+
// cert/crl outer tags only. Unlike EnvelopedData sec. 6.1, the recipient-info kinds do
|
|
981
|
+
// not influence the version. If originatorInfo is present and (other-cert [3] or
|
|
850
982
|
// other-crl [1]) -> 3; ELSE IF originatorInfo present AND v2AttrCert [2] -> 1; ELSE 0.
|
|
851
983
|
function _expectedAuthDataVersion(originatorInfo) {
|
|
852
984
|
if (!originatorInfo) return 0;
|
|
@@ -859,7 +991,7 @@ function _expectedAuthDataVersion(originatorInfo) {
|
|
|
859
991
|
// EnvelopedData ::= SEQUENCE { version, originatorInfo [0] IMPLICIT OPTIONAL,
|
|
860
992
|
// recipientInfos RecipientInfos (SET SIZE 1..MAX), encryptedContentInfo,
|
|
861
993
|
// unprotectedAttrs [1] IMPLICIT OPTIONAL } (RFC 5652 sec. 6.1). recipientInfos is
|
|
862
|
-
// min:1 (an empty SET is non-conformant
|
|
994
|
+
// min:1 (an empty SET is non-conformant, the inverse of SignedData's degenerate
|
|
863
995
|
// signerInfos).
|
|
864
996
|
var ENVELOPED_DATA = schema.seq([
|
|
865
997
|
schema.field("version", ENVELOPED_DATA_VERSION),
|
|
@@ -889,7 +1021,7 @@ var ENVELOPED_DATA = schema.seq([
|
|
|
889
1021
|
});
|
|
890
1022
|
|
|
891
1023
|
// EncryptedData ::= SEQUENCE { version, encryptedContentInfo, unprotectedAttrs [1]
|
|
892
|
-
// IMPLICIT OPTIONAL } (RFC 5652 sec. 8)
|
|
1024
|
+
// IMPLICIT OPTIONAL } (RFC 5652 sec. 8): no recipients, no originatorInfo; the CEK is
|
|
893
1025
|
// distributed out of band. version is 0, or 2 iff unprotectedAttrs are present.
|
|
894
1026
|
var ENCRYPTED_DATA = schema.seq([
|
|
895
1027
|
schema.field("version", ENCRYPTED_DATA_VERSION),
|
|
@@ -1018,7 +1150,7 @@ var AUTH_ENVELOPED_DATA = schema.seq([
|
|
|
1018
1150
|
// sec. 2.2 AAD is the re-tagged DER SET). No content-type/message-digest
|
|
1019
1151
|
// presence rules bind here (sec. 11.1/sec. 11.2 name signed-data and
|
|
1020
1152
|
// authenticated-data; sec. 2.1 says message-digest SHOULD NOT appear), so only
|
|
1021
|
-
// the duplicate rule, the sec. 11 placement rows, and
|
|
1153
|
+
// the duplicate rule, the sec. 11 placement rows, and, when a content-type
|
|
1022
1154
|
// attribute IS present -- the value == contentType coherence apply.
|
|
1023
1155
|
_assertDerEncodedAttrs(m.fields.authAttrs.node, "cms/bad-auth-attrs");
|
|
1024
1156
|
authAttrs = m.fields.authAttrs.value.items.map(function (it) { return it.value.result; });
|
|
@@ -1133,8 +1265,8 @@ var CONTENT_INFO = schema.seq([
|
|
|
1133
1265
|
* `{ type: "ori", oriType, oriValue, kemri }` with the parsed KEMRecipientInfo in
|
|
1134
1266
|
* `kemri`. Every result additionally carries `contentType` (the dotted OID) and
|
|
1135
1267
|
* `contentTypeName` (its registry name) naming which of the five shapes was
|
|
1136
|
-
* dispatched.
|
|
1137
|
-
* `signature`, `signedAttrsBytes`, `authAttrsBytes`, `mac`
|
|
1268
|
+
* dispatched. The raw byte ranges an external verifier hashes (`eContent`,
|
|
1269
|
+
* `signature`, `signedAttrsBytes`, `authAttrsBytes`, `mac`) are surfaced
|
|
1138
1270
|
* exactly as on the wire. The remaining PKCS#7 types throw `cms/unsupported-content-type`; an
|
|
1139
1271
|
* unrecognized OID throws `cms/unknown-content-type`; a malformed structure
|
|
1140
1272
|
* throws a typed `CmsError` (`cms/*`) and a leaf-level codec fault surfaces as
|
|
@@ -1310,6 +1442,7 @@ module.exports = {
|
|
|
1310
1442
|
walkSignedData: walkSignedData,
|
|
1311
1443
|
walkEncryptedData: walkEncryptedData,
|
|
1312
1444
|
walkCountersignature: walkCountersignature,
|
|
1445
|
+
looksLikeSignedAttributes: looksLikeSignedAttributes,
|
|
1313
1446
|
assertAttachedCiphertext: assertAttachedCiphertext,
|
|
1314
1447
|
// The structure's own algorithm tables, exported (like the walk* helpers) as the single source
|
|
1315
1448
|
// of truth the crypto layer (cms-encrypt / cms-decrypt) shares -- so the wrap<->KEK-length, the
|
package/lib/schema-crl.js
CHANGED
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
* rules, and the raw `tbsCertList` bytes are returned for signature checking.
|
|
20
20
|
*
|
|
21
21
|
* @card
|
|
22
|
-
* Parse DER / PEM X.509 CRLs into structured, validated fields
|
|
22
|
+
* Parse DER / PEM X.509 CRLs into structured, validated fields: revoked
|
|
23
23
|
* serials with real-`Date` revocation times, named extensions, fail-closed.
|
|
24
24
|
*/
|
|
25
25
|
|
|
@@ -34,14 +34,14 @@ var PemError = frameworkError.PemError;
|
|
|
34
34
|
var TAGS = asn1.TAGS;
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
// CRLReason ::= ENUMERATED (RFC 5280 sec. 5.3.1)
|
|
37
|
+
// CRLReason ::= ENUMERATED (RFC 5280 sec. 5.3.1). Value 7 is unused/reserved.
|
|
38
38
|
// The legal set lives once in pkix.CRL_REASON_NAMES (shared with the OCSP
|
|
39
39
|
// RevokedInfo decoder, which surfaces the name; this decoder the numeric code).
|
|
40
40
|
var CRL_REASONS = pkix.CRL_REASON_NAMES;
|
|
41
41
|
|
|
42
|
-
// Extension-value decoding is keyed off the
|
|
43
|
-
// load from the canonical name)
|
|
44
|
-
// pki.oid.register() display override
|
|
42
|
+
// Extension-value decoding is keyed off the stable dotted OID (resolved once at
|
|
43
|
+
// load from the canonical name) and not the mutable display name, so a caller's
|
|
44
|
+
// pki.oid.register() display override cannot change parse behaviour.
|
|
45
45
|
var OID_CRL_NUMBER = oid.byName("cRLNumber");
|
|
46
46
|
var OID_REASON_CODE = oid.byName("reasonCode");
|
|
47
47
|
var OID_INVALIDITY_DATE = oid.byName("invalidityDate");
|
|
@@ -58,9 +58,9 @@ var EXTENSIONS = pkix.extensions(NS);
|
|
|
58
58
|
// UTCTime). invalidityDate stays GeneralizedTime-only (sec. 5.3.2, decodeExt).
|
|
59
59
|
var TIME = pkix.time(NS);
|
|
60
60
|
|
|
61
|
-
// CRL Version ::= INTEGER { v1(0), v2(1) }
|
|
61
|
+
// CRL Version ::= INTEGER { v1(0), v2(1) }, a bare INTEGER (not [0] EXPLICIT).
|
|
62
62
|
// A CRL is at most v2; reject an explicit v1 (DER forbids the default) and any
|
|
63
|
-
// value >= 2. Do
|
|
63
|
+
// value >= 2. Do not reuse the certificate readVersion (it maps 2 -> v3).
|
|
64
64
|
var CRL_VERSION = pkix.versionReader(NS, { "1": 2 });
|
|
65
65
|
|
|
66
66
|
// The three cheap, high-value CRL extension values are decoded from their raw
|
package/lib/schema-crmf.js
CHANGED
|
@@ -24,13 +24,13 @@
|
|
|
24
24
|
* when supplied, MUST be 2 (RFC 4211 sec. 5); certReqId is an unbounded signed
|
|
25
25
|
* INTEGER (the RFC 9483 `-1` sentinel is legal). The `CertRequest` byte range
|
|
26
26
|
* the proof-of-possession signature covers, and each `poposkInput`, are surfaced
|
|
27
|
-
*
|
|
28
|
-
* keyEncipherment / keyAgreement POP arms are surfaced
|
|
27
|
+
* raw for a downstream verifier; registration controls / info values and the
|
|
28
|
+
* keyEncipherment / keyAgreement POP arms are surfaced raw and never recursed.
|
|
29
29
|
* DER-only, fail-closed.
|
|
30
30
|
*
|
|
31
31
|
* @card
|
|
32
32
|
* Parse DER / PEM RFC 4211 CertReqMessages into requested-certificate templates,
|
|
33
|
-
* proof-of-possession, and registration controls
|
|
33
|
+
* proof-of-possession, and registration controls: dual-accepted names, raw
|
|
34
34
|
* verifier inputs, fail-closed.
|
|
35
35
|
*/
|
|
36
36
|
|
|
@@ -102,7 +102,7 @@ var OPTIONAL_VALIDITY = schema.seq([
|
|
|
102
102
|
});
|
|
103
103
|
|
|
104
104
|
// ---- ProofOfPossession (sec. 4) ------------------------------------------
|
|
105
|
-
// raVerified [0] NULL
|
|
105
|
+
// raVerified [0] NULL: the RA has already verified POP out of band.
|
|
106
106
|
var POPO_RAVERIFIED = schema.decode(function (n, ctx) {
|
|
107
107
|
try { asn1.read.nullImplicit(n, 0); }
|
|
108
108
|
catch (e) { throw ctx.E("crmf/bad-popo", "raVerified [0] must be an IMPLICIT NULL", e); }
|
|
@@ -110,9 +110,9 @@ var POPO_RAVERIFIED = schema.decode(function (n, ctx) {
|
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
// When poposkInput is present the POP signature is over the DER of the
|
|
113
|
-
// POPOSigningKeyInput
|
|
113
|
+
// POPOSigningKeyInput value (RFC 4211 sec. 4.1), which is a SEQUENCE. But on the wire
|
|
114
114
|
// poposkInput is an IMPLICIT [0] field, so its bytes lead with the context tag
|
|
115
|
-
// (0xA0)
|
|
115
|
+
// (0xA0) and not the SEQUENCE tag a verifier hashes. `asn1.sequenceTlv` recovers
|
|
116
116
|
// the SEQUENCE-tagged signed region from the field's content; `signedBytes` is that
|
|
117
117
|
// region and `bytes` keeps the raw wire [0] TLV, so a verifier can use either
|
|
118
118
|
// encoding its peer produced.
|
|
@@ -120,7 +120,7 @@ var POPO_RAVERIFIED = schema.decode(function (n, ctx) {
|
|
|
120
120
|
// POPOSigningKeyInput ::= SEQUENCE { authInfo CHOICE { sender [0] GeneralName,
|
|
121
121
|
// publicKeyMAC PKMACValue }, publicKey SubjectPublicKeyInfo } (sec. 4.1). Structurally
|
|
122
122
|
// validated (not deferred raw), so an empty or malformed poposkInput fails closed
|
|
123
|
-
//
|
|
123
|
+
// and never surfaces a signed region that was not a well-formed POP input.
|
|
124
124
|
// PKMACValue ::= SEQUENCE { algId AlgorithmIdentifier, value BIT STRING }. The
|
|
125
125
|
// MAC value is octet-string algorithm output handed to an external verifier;
|
|
126
126
|
// with no in-tree POP verify layer, octet alignment is enforced at parse.
|
|
@@ -130,7 +130,7 @@ var PKMAC_VALUE = schema.seq([
|
|
|
130
130
|
], {
|
|
131
131
|
assert: "sequence", arity: { exact: 2 }, code: "crmf/bad-popo", what: "PKMACValue",
|
|
132
132
|
build: function (m, ctx) {
|
|
133
|
-
// The parsed BIT STRING structure { unusedBits, bytes }
|
|
133
|
+
// The parsed BIT STRING structure { unusedBits, bytes }, a structural
|
|
134
134
|
// octet-alignment check on the unused-bit COUNT, not a comparison of the
|
|
135
135
|
// MAC secret (there is no in-tree POP verify), so no timing oracle exists.
|
|
136
136
|
var macBitString = m.fields.value.value;
|
|
@@ -142,7 +142,7 @@ var PKMAC_VALUE = schema.seq([
|
|
|
142
142
|
});
|
|
143
143
|
|
|
144
144
|
// authInfo CHOICE: sender [0] GeneralName is EXPLICIT (GeneralName is itself a
|
|
145
|
-
// CHOICE); publicKeyMAC is a bare PKMACValue SEQUENCE
|
|
145
|
+
// CHOICE); publicKeyMAC is a bare PKMACValue SEQUENCE, disjoint by tag class.
|
|
146
146
|
var POPOSK_AUTH_INFO = schema.choice([
|
|
147
147
|
{ when: { tagClass: "context", tagNumber: 0 }, schema: schema.explicit(0, pkix.generalName(NS, { code: "crmf/bad-popo" }), { code: "crmf/bad-popo" }) },
|
|
148
148
|
{ when: { tagClass: "universal", tagNumber: TAGS.SEQUENCE }, schema: PKMAC_VALUE },
|
|
@@ -154,7 +154,7 @@ var POPOSK_INPUT = schema.seq([
|
|
|
154
154
|
], { assert: "implicit", implicitTag: 0, arity: { exact: 2 }, code: "crmf/bad-popo", what: "POPOSigningKeyInput" });
|
|
155
155
|
|
|
156
156
|
// POPOSigningKey ::= SEQUENCE { poposkInput [0] POPOSigningKeyInput OPTIONAL,
|
|
157
|
-
// algorithmIdentifier AlgorithmIdentifier, signature BIT STRING }
|
|
157
|
+
// algorithmIdentifier AlgorithmIdentifier, signature BIT STRING }, IMPLICIT [1].
|
|
158
158
|
var POPO_SIGNING_KEY = schema.seq([
|
|
159
159
|
schema.optional("poposkInput", schema.decode(function (n, ctx) {
|
|
160
160
|
// Validate the full POPOSigningKeyInput structure fail-closed, then surface the
|
|
@@ -187,9 +187,9 @@ var POPO_SIGNING_KEY = schema.seq([
|
|
|
187
187
|
// keyEncipherment [2] / keyAgreement [3] POPOPrivKey (sec. 4). POPOPrivKey is a
|
|
188
188
|
// 5-arm CHOICE { thisMessage [0], subsequentMessage [1], dhMAC [2], agreeMAC [3],
|
|
189
189
|
// encryptedKey [4] }, so X.680 sec. 31.2.7 forces the outer [2]/[3] tag to be EXPLICIT
|
|
190
|
-
// (a CHOICE has no single tag to replace). Validate that shell
|
|
191
|
-
// wrapper around exactly one inner context [0]..[4] alternative
|
|
192
|
-
// the arm
|
|
190
|
+
// (a CHOICE has no single tag to replace). Validate that shell, a constructed
|
|
191
|
+
// wrapper around exactly one inner context [0]..[4] alternative, before surfacing
|
|
192
|
+
// the arm raw, so a primitive, empty or mis-tagged node is rejected and never
|
|
193
193
|
// reported to a verifier as a well-formed POP. The inner alternative's own value
|
|
194
194
|
// (incl. the encryptedKey [4] EnvelopedData) stays deferred to the verify layer.
|
|
195
195
|
// POPOPrivKey ::= CHOICE { thisMessage [0] BIT STRING (deprecated), subsequentMessage
|
|
@@ -240,7 +240,7 @@ function popoPrivKey(type) {
|
|
|
240
240
|
try { schema.embeddedDer(PKMAC_VALUE, asn1.sequenceTlv(inner), NS, { code: "crmf/bad-popo", what: "agreeMAC [3] PKMACValue" }); }
|
|
241
241
|
catch (e) { throw ctx.E("crmf/bad-popo", "agreeMAC [3] must be a PKMACValue SEQUENCE { algId, BIT STRING } (RFC 4211 sec. 4.2)", e); }
|
|
242
242
|
}
|
|
243
|
-
// encryptedKey [4] EnvelopedData
|
|
243
|
+
// encryptedKey [4] EnvelopedData: validate the CMS structure fail-closed by
|
|
244
244
|
// composing the shared EnvelopedData decoder (retag the IMPLICIT [4] to the
|
|
245
245
|
// universal SEQUENCE it decodes as). The EncKeyWithID identity check lives inside
|
|
246
246
|
// the ENCRYPTED content, so it stays a verify-layer concern.
|
|
@@ -248,12 +248,12 @@ function popoPrivKey(type) {
|
|
|
248
248
|
var env;
|
|
249
249
|
try { env = cms.walkEnvelopedData(asn1.decode(asn1.sequenceTlv(inner))); }
|
|
250
250
|
catch (e) { throw ctx.E("crmf/bad-popo", "encryptedKey [4] must be a well-formed EnvelopedData (RFC 4211 sec. 4.2, RFC 5652 sec. 6.1)", e); }
|
|
251
|
-
// RFC 4211 sec. 4.2
|
|
251
|
+
// RFC 4211 sec. 4.2: the enveloped content type MUST be id-ct-encKeyWithID (the
|
|
252
252
|
// ContentType OID is in the clear even though the content itself is encrypted).
|
|
253
253
|
if (env.encryptedContentInfo.contentType !== oid.byName("encKeyWithID")) {
|
|
254
254
|
throw ctx.E("crmf/bad-popo", "encryptedKey [4] EnvelopedData content type MUST be id-ct-encKeyWithID (RFC 4211 sec. 4.2)");
|
|
255
255
|
}
|
|
256
|
-
// The encrypted key material MUST be present
|
|
256
|
+
// The encrypted key material MUST be present. CMS allows a detached
|
|
257
257
|
// EnvelopedData (encryptedContent OPTIONAL), but a POP with no key to verify
|
|
258
258
|
// or archive is meaningless. The shared CMS assert rejects a null OR
|
|
259
259
|
// zero-length attached ciphertext (the rule the EST / PKCS#12 siblings hold).
|
|
@@ -275,7 +275,7 @@ var PROOF_OF_POSSESSION = schema.choice([
|
|
|
275
275
|
|
|
276
276
|
// ---- Controls / regInfo (sec. 5, sec. 6, sec. 7) ---------------------------------
|
|
277
277
|
// AttributeTypeAndValue ::= SEQUENCE { type OBJECT IDENTIFIER, value ANY DEFINED BY
|
|
278
|
-
// type }. The list is decoded; each value stays
|
|
278
|
+
// type }. The list is decoded; each value stays raw (the per-OID value semantics
|
|
279
279
|
// are deferred). Shared shape for both controls and regInfo.
|
|
280
280
|
var CONTROL = schema.seq([
|
|
281
281
|
schema.field("type", schema.oidLeaf()),
|
|
@@ -293,10 +293,10 @@ var CONTROLS = schema.seqOf(CONTROL, { assert: "sequence", min: 1, code: "crmf/b
|
|
|
293
293
|
// regInfo ::= SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue (sec. 7).
|
|
294
294
|
var REG_INFO = schema.seqOf(CONTROL, { assert: "sequence", min: 1, code: "crmf/bad-reg-info", what: "regInfo", build: mapControls });
|
|
295
295
|
|
|
296
|
-
// ---- CertTemplate (sec. 5)
|
|
296
|
+
// ---- CertTemplate (sec. 5), the IMPLICIT-TAGS core ------------------------
|
|
297
297
|
// CertTemplate ::= SEQUENCE { version [0], serialNumber [1], signingAlg [2],
|
|
298
298
|
// issuer [3] Name, validity [4] OptionalValidity, subject [5] Name, publicKey [6]
|
|
299
|
-
// SubjectPublicKeyInfo, issuerUID [7], subjectUID [8], extensions [9] }
|
|
299
|
+
// SubjectPublicKeyInfo, issuerUID [7], subjectUID [8], extensions [9] }, where every
|
|
300
300
|
// field IMPLICIT and OPTIONAL, so the whole body is one ascending [0..9] trailing
|
|
301
301
|
// run. issuer/subject Name and the [4] validity times are the CHOICE exceptions.
|
|
302
302
|
var CERT_TEMPLATE = schema.seq([
|
|
@@ -323,9 +323,9 @@ var CERT_TEMPLATE = schema.seq([
|
|
|
323
323
|
// existing certificate -- serialNumber and issuer present -- inside a CMP
|
|
324
324
|
// RevDetails (RFC 9810 sec. 5.3.9). Every field is surfaced so both the request
|
|
325
325
|
// omission check and the revocation consumer read them off one result.
|
|
326
|
-
// The
|
|
326
|
+
// The single profile override: RFC 9810 Appendix D.6 profiles the CMP
|
|
327
327
|
// cross-certification (ccr) template as "version v1 or v3", so the caller
|
|
328
|
-
// opts in to v1 (0) via ctx.allowV1Version
|
|
328
|
+
// opts in to v1 (0) via ctx.allowV1Version, the same base-section-vs-
|
|
329
329
|
// profile-appendix override mechanism as the signingAlg relaxation below.
|
|
330
330
|
if (f.version.present && f.version.value !== 2n &&
|
|
331
331
|
!(ctx.allowV1Version && f.version.value === 0n)) {
|
|
@@ -365,7 +365,7 @@ var CERT_REQUEST = schema.seq([
|
|
|
365
365
|
// hazard), so a request template that sets any of them is rejected
|
|
366
366
|
// fail-closed. The check lives HERE, not in CERT_TEMPLATE, because the same
|
|
367
367
|
// structure legitimately carries serialNumber/issuer in a CMP RevDetails.
|
|
368
|
-
// A cross-certification request (ccr) is the
|
|
368
|
+
// A cross-certification request (ccr) is the one exception: RFC 9810 Appendix
|
|
369
369
|
// D.6 profiles its template with signingAlg PRESENT (the requesting CA states
|
|
370
370
|
// the algorithm it wants the cross-certificate signed with), so the caller
|
|
371
371
|
// relaxes that single field via ctx.allowSigningAlg; serialNumber and the UID
|
|
@@ -406,7 +406,7 @@ var CERT_REQ_MSG = schema.seq([
|
|
|
406
406
|
// to sign / MAC over, and a verifier would be bound to the wrong identity/key.
|
|
407
407
|
var complete = certReq.certTemplate.subject !== null && certReq.certTemplate.publicKey !== null;
|
|
408
408
|
// RFC 4211 sec. 4.1 -- for a signature POP, poposkInput's presence is fixed by the
|
|
409
|
-
// CertTemplate: it MUST be omitted when the template carries
|
|
409
|
+
// CertTemplate: it MUST be omitted when the template carries both subject and
|
|
410
410
|
// publicKey (the signature is then over the DER of the CertRequest), and MUST be
|
|
411
411
|
// present otherwise.
|
|
412
412
|
if (popo && popo.type === "signature") {
|
|
@@ -438,7 +438,7 @@ var CERT_REQ_MSG = schema.seq([
|
|
|
438
438
|
},
|
|
439
439
|
});
|
|
440
440
|
|
|
441
|
-
// CertReqMessages ::= SEQUENCE SIZE(1..MAX) OF CertReqMsg (sec. 3)
|
|
441
|
+
// CertReqMessages ::= SEQUENCE SIZE(1..MAX) OF CertReqMsg (sec. 3), where an empty sequence
|
|
442
442
|
// is malformed. The top-level reject code predates the
|
|
443
443
|
// `<prefix>/not-a-<structure>` convention and stays frozen on the public
|
|
444
444
|
// error-code surface.
|
|
@@ -461,16 +461,16 @@ var CERT_REQ_MESSAGES = schema.seqOf(CERT_REQ_MSG, {
|
|
|
461
461
|
* `{ messages: [ { certReq, popo, regInfo } ] }`. Each `certReq` is
|
|
462
462
|
* `{ certReqId, certReqIdHex, certTemplate, controls, certReqBytes }`, and
|
|
463
463
|
* `certTemplate` carries the requestable certificate fields (`version`, `issuer`,
|
|
464
|
-
* `validity`, `subject`, `publicKey`, `extensions
|
|
464
|
+
* `validity`, `subject`, `publicKey`, `extensions`, each `null` when absent). The
|
|
465
465
|
* CA-assigned / deprecated fields RFC 4211 sec. 5 requires a request to omit
|
|
466
466
|
* (`serialNumber`, `signingAlg`, `issuerUID`, `subjectUID`) are rejected, not
|
|
467
467
|
* surfaced. `popo` is
|
|
468
468
|
* `null`, `{ type: "raVerified" }`, `{ type: "signature", poposkInput,
|
|
469
469
|
* algorithmIdentifier, signature }`, or `{ type: "keyEncipherment" |
|
|
470
470
|
* "keyAgreement", method, bytes }` (where `method` is the POPOPrivKey alternative --
|
|
471
|
-
* `thisMessage` / `subsequentMessage` / `dhMAC` / `agreeMAC` / `encryptedKey
|
|
471
|
+
* `thisMessage` / `subsequentMessage` / `dhMAC` / `agreeMAC` / `encryptedKey`, each
|
|
472
472
|
* structurally validated, the `encryptedKey` EnvelopedData included). When present, `poposkInput` is
|
|
473
|
-
* `{ bytes, signedBytes, publicKey }
|
|
473
|
+
* `{ bytes, signedBytes, publicKey }`, where `bytes` is the raw wire `[0]` TLV,
|
|
474
474
|
* `signedBytes` is the `POPOSigningKeyInput` re-tagged to the `SEQUENCE` DER the
|
|
475
475
|
* signature actually covers, and `publicKey` is the canonical
|
|
476
476
|
* `SubjectPublicKeyInfo` DER the RFC 4211 sec. 4.1 template-match check compares
|
|
@@ -503,7 +503,7 @@ var parse = pkix.makeParser({
|
|
|
503
503
|
* @related pki.schema.crmf.parse
|
|
504
504
|
*
|
|
505
505
|
* Extract the DER bytes from a PEM block (RFC 4211 registers no RFC 7468 label, so
|
|
506
|
-
* the first block is taken unless `label` is given
|
|
506
|
+
* the first block is taken unless `label` is given; CRMF rides inside CMP / EST as
|
|
507
507
|
* DER in practice). Throws `PemError` on a missing envelope or a non-base64 body.
|
|
508
508
|
*
|
|
509
509
|
* @example
|