@blamejs/pki 0.8.52 → 0.8.53
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 +20 -0
- package/README.md +1 -1
- package/lib/cms-decrypt.js +40 -3
- package/lib/cms-encrypt.js +64 -2
- package/lib/cms-sign.js +67 -2
- package/lib/cms-verify.js +148 -24
- package/lib/ct.js +3 -2
- package/lib/guard-der.js +26 -0
- package/lib/guard-identifier.js +27 -0
- package/lib/lint.js +69 -13
- package/lib/oid.js +11 -1
- package/lib/path-validate.js +82 -12
- package/lib/revocation-fetch.js +1 -3
- package/lib/scep.js +8 -3
- package/lib/schema-all.js +2 -1
- package/lib/schema-cms.js +377 -6
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/schema-cms.js
CHANGED
|
@@ -60,6 +60,11 @@ var NS = pkix.makeNS("cms", CmsError, oid);
|
|
|
60
60
|
|
|
61
61
|
var ALGORITHM_IDENTIFIER = pkix.algorithmIdentifier(NS);
|
|
62
62
|
var ATTRIBUTE = pkix.attribute(NS);
|
|
63
|
+
/** @internal The two arms of a CMSAlgorithmProtection, each an AlgorithmIdentifier under its own
|
|
64
|
+
* IMPLICIT tag. The Appendix A module is `DEFINITIONS IMPLICIT TAGS`, so the tag replaces the
|
|
65
|
+
* SEQUENCE tag rather than wrapping it, which is what the shared factory asserts. */
|
|
66
|
+
var ALGORITHM_IDENTIFIER_1 = pkix.algorithmIdentifier(NS, { implicitTag: 1 });
|
|
67
|
+
var ALGORITHM_IDENTIFIER_2 = pkix.algorithmIdentifier(NS, { implicitTag: 2 });
|
|
63
68
|
|
|
64
69
|
var SIGNED_DATA_VERSION = pkix.versionReader(NS, { "1": 1, "3": 3, "4": 4, "5": 5 });
|
|
65
70
|
var SIGNER_VERSION = pkix.versionReader(NS, { "1": 1, "3": 3 });
|
|
@@ -106,30 +111,116 @@ var OID_MESSAGE_DIGEST = oid.byName("messageDigest");
|
|
|
106
111
|
var OID_SIGNING_TIME = oid.byName("signingTime");
|
|
107
112
|
var OID_COUNTERSIGNATURE = oid.byName("countersignature");
|
|
108
113
|
|
|
114
|
+
/** @internal The two identifiers RFC 6211's own documents give the algorithm protection attribute.
|
|
115
|
+
* Sec. 2 and Appendix A state the PKCS 9 leaf; sec. 4's "All identifiers are assigned out of the
|
|
116
|
+
* S/MIME OID arc", the IANA registry, errata 9144 and 9145, and
|
|
117
|
+
* draft-ietf-lamps-rfc6211-update-03 state the S/MIME attribute arc. Both are read, because an
|
|
118
|
+
* implementation that recognizes one spelling and not the other checks nothing on a message using
|
|
119
|
+
* the other, which is the interop failure the update draft sec. 1 records between two shipped
|
|
120
|
+
* implementations of this attribute. */
|
|
121
|
+
var OID_ALG_PROTECTION = oid.byName("cmsAlgorithmProtection");
|
|
122
|
+
var OID_ALG_PROTECTION_AA = oid.byName("cmsAlgorithmProtect");
|
|
123
|
+
function _isAlgProtection(type) {
|
|
124
|
+
return type === OID_ALG_PROTECTION || type === OID_ALG_PROTECTION_AA;
|
|
125
|
+
}
|
|
126
|
+
/** @internal Whether a dotted OID is one of the two the algorithm protection attribute is assigned,
|
|
127
|
+
* exported so a verb policing its own emission asks the module that owns the attribute rather than
|
|
128
|
+
* keeping a second copy of the pair.
|
|
129
|
+
*
|
|
130
|
+
* @internal
|
|
131
|
+
*/
|
|
132
|
+
function isAlgorithmProtectionType(type) { return _isAlgProtection(type); }
|
|
133
|
+
|
|
134
|
+
/** @internal The identifier an emitting verb writes, resolved from the option that chose it, or
|
|
135
|
+
* `null` when the option asks for no attribute. Only the four values are accepted. Any other
|
|
136
|
+
* truthy value read as the default would emit the PKCS 9 identifier to a caller who asked for the
|
|
137
|
+
* registry one, and a mismatched identifier is equivalent to no protection at all
|
|
138
|
+
* (draft-ietf-lamps-rfc6211-update sec. 5), so a misspelling would silently produce a message the
|
|
139
|
+
* reader it was meant for does not check. Any other falsy value read as absence would emit nothing
|
|
140
|
+
* for a caller who asked for the attribute, which is the same silence from the other direction.
|
|
141
|
+
*
|
|
142
|
+
* @internal
|
|
143
|
+
*/
|
|
144
|
+
function algorithmProtectionOid(choice, E) {
|
|
145
|
+
if (choice === true) return OID_ALG_PROTECTION;
|
|
146
|
+
if (choice === "registry") return OID_ALG_PROTECTION_AA;
|
|
147
|
+
if (choice === undefined || choice === null || choice === false) return null;
|
|
148
|
+
throw E("cms/bad-input", "algorithmProtection is true for the identifier RFC 6211 sec. 2 states, " +
|
|
149
|
+
"or \"registry\" for the one the IANA registry gives it; got " + guard.text.showValue(choice));
|
|
150
|
+
}
|
|
151
|
+
|
|
109
152
|
var ATTR_FORBIDDEN_IN = Object.create(null);
|
|
110
153
|
ATTR_FORBIDDEN_IN[OID_CONTENT_TYPE] = { unsigned: true, unauth: true, unprotected: true };
|
|
111
154
|
ATTR_FORBIDDEN_IN[OID_MESSAGE_DIGEST] = { unsigned: true, unauth: true, unprotected: true };
|
|
112
155
|
ATTR_FORBIDDEN_IN[OID_SIGNING_TIME] = { unsigned: true, unauth: true, unprotected: true };
|
|
113
156
|
ATTR_FORBIDDEN_IN[OID_COUNTERSIGNATURE] = { signed: true, auth: true, unauth: true, unprotected: true };
|
|
157
|
+
/** @internal RFC 6211 sec. 2: the attribute "MUST be a signed attribute or an authenticated
|
|
158
|
+
* attribute; it MUST NOT be an unsigned attribute, an unauthenticated attribute, or an unprotected
|
|
159
|
+
* attribute", and sec. 5 adds that an instance in such a place "does not provide any additional
|
|
160
|
+
* security". Refused at parse under both spellings. */
|
|
161
|
+
ATTR_FORBIDDEN_IN[OID_ALG_PROTECTION] = { unsigned: true, unauth: true, unprotected: true, authEnveloped: true };
|
|
162
|
+
ATTR_FORBIDDEN_IN[OID_ALG_PROTECTION_AA] = { unsigned: true, unauth: true, unprotected: true, authEnveloped: true };
|
|
163
|
+
/** @internal An `AuthEnvelopedData`'s authenticated attributes are their own place, distinct from an
|
|
164
|
+
* `AuthenticatedData`'s, because one attribute belongs in the second and in no other: RFC 6211
|
|
165
|
+
* sec. 2 populates the `macAlgorithm` arm "only if the attribute is placed in an
|
|
166
|
+
* AuthenticatedData.authAttrs sequence", and RFC 8933 sec. 6 says the attribute protects nothing in
|
|
167
|
+
* an `AuthEnvelopedData`, which carries no digest or MAC algorithm field for a copy to name. Every
|
|
168
|
+
* other attribute's row names neither place, so only this one is told apart. */
|
|
114
169
|
var ATTR_PLACE_LABELS = Object.assign(Object.create(null), {
|
|
115
170
|
signed: "a signed attribute", unsigned: "an unsigned attribute", auth: "an authenticated attribute",
|
|
116
171
|
unauth: "an unauthenticated attribute", unprotected: "an unprotected attribute",
|
|
172
|
+
authEnveloped: "an authenticated attribute of an AuthEnvelopedData",
|
|
117
173
|
});
|
|
118
174
|
|
|
119
|
-
|
|
175
|
+
/** @internal `places` is every name this set answers to, because a set can be more than one thing:
|
|
176
|
+
* an `AuthEnvelopedData`'s is an authenticated-attribute set AND that content type's own, so it
|
|
177
|
+
* carries every restriction the first names plus the one the second adds. Naming only the narrower
|
|
178
|
+
* place would drop the rest, which is how a set that refused a countersignature stopped refusing
|
|
179
|
+
* it. A name a row does not mention restricts nothing, so adding one can only add refusals. */
|
|
180
|
+
function _checkAttrPlacement(attrs, places) {
|
|
181
|
+
var names = typeof places === "string" ? [places] : places;
|
|
120
182
|
for (var i = 0; i < attrs.length; i++) {
|
|
121
183
|
var row = ATTR_FORBIDDEN_IN[attrs[i].type];
|
|
122
|
-
if (row
|
|
184
|
+
if (!row) continue;
|
|
185
|
+
for (var p = 0; p < names.length; p++) {
|
|
186
|
+
if (!row[names[p]]) continue;
|
|
123
187
|
throw NS.E("cms/misplaced-attr", "the " + (oid.name(attrs[i].type) || attrs[i].type) +
|
|
124
|
-
" attribute must not be " + ATTR_PLACE_LABELS[
|
|
188
|
+
" attribute must not be " + ATTR_PLACE_LABELS[names[p]] + " (RFC 5652 sec. 11)");
|
|
125
189
|
}
|
|
126
190
|
}
|
|
127
191
|
}
|
|
128
192
|
|
|
129
193
|
function _checkContentBindingAttrs(attrs, mode, ctx) {
|
|
130
|
-
var ct = 0, md = 0, seen = Object.create(null);
|
|
194
|
+
var ct = 0, md = 0, ap = 0, seen = Object.create(null);
|
|
131
195
|
for (var i = 0; i < attrs.length; i++) {
|
|
132
196
|
var a = attrs[i];
|
|
197
|
+
/** @internal Counted across BOTH identifiers rather than by type, because the two spell one
|
|
198
|
+
* attribute: "The SignedAttributes in a signerInfo MUST include only one instance of the
|
|
199
|
+
* algorithm protection attribute" (RFC 6211 sec. 2), and a set carrying one under each
|
|
200
|
+
* spelling carries two instances of it. The per-type `seen` map below cannot see that. */
|
|
201
|
+
if (_isAlgProtection(a.type)) {
|
|
202
|
+
ap += 1;
|
|
203
|
+
if (ap > 1) {
|
|
204
|
+
throw NS.E("cms/duplicate-signed-attr", "the attribute set must include only one instance " +
|
|
205
|
+
"of the algorithm protection attribute (RFC 6211 sec. 2), counting both identifiers it " +
|
|
206
|
+
"is assigned");
|
|
207
|
+
}
|
|
208
|
+
if (a.values.length !== 1) {
|
|
209
|
+
throw NS.E("cms/bad-algorithm-protection-attr", "the algorithm protection attribute MUST " +
|
|
210
|
+
"have a single attribute value, and there MUST NOT be zero or multiple instances of " +
|
|
211
|
+
"AttributeValue present (RFC 6211 sec. 2), got " + a.values.length);
|
|
212
|
+
}
|
|
213
|
+
/** @internal Decoded during the walk, so a malformed value is a parse refusal rather than
|
|
214
|
+
* something a verify path meets later: the attribute is a statement about the structure it
|
|
215
|
+
* rides in, and a structure carrying one that does not decode is malformed. */
|
|
216
|
+
/** @internal WHICH arm the value carries is not decided here. RFC 6211 sec. 2 says which arm
|
|
217
|
+
* a producer fills, and sec. 3.1 and 3.2 give the consequence to the validator: an attribute
|
|
218
|
+
* holding the other arm makes a claim about a structure this is not, and the comparison in
|
|
219
|
+
* `algorithmProtectionReport` fails the signer or the message for it. Refusing the whole
|
|
220
|
+
* message here instead would leave an operator unable to parse one in order to see that. */
|
|
221
|
+
schema.walk(ALG_PROTECTION,
|
|
222
|
+
pkix.decodeNested(a.values[0], ctx, "an algorithm protection attribute value"), ctx);
|
|
223
|
+
}
|
|
133
224
|
if (seen[a.type]) {
|
|
134
225
|
if (a.type === OID_CONTENT_TYPE) throw NS.E("cms/duplicate-content-type", "the attribute set must not repeat the content-type attribute");
|
|
135
226
|
if (a.type === OID_MESSAGE_DIGEST) throw NS.E("cms/duplicate-message-digest", "the attribute set must not repeat the message-digest attribute");
|
|
@@ -336,6 +427,153 @@ var SIGNER_IDENTIFIER = schema.choice([
|
|
|
336
427
|
{ when: { tagClass: "context", tagNumber: 0 }, schema: schema.implicitOctetString(0) },
|
|
337
428
|
], { code: "cms/bad-signer-identifier", what: "SignerIdentifier" });
|
|
338
429
|
|
|
430
|
+
/** @internal `CMSAlgorithmProtection ::= SEQUENCE { digestAlgorithm DigestAlgorithmIdentifier,
|
|
431
|
+
* signatureAlgorithm [1] SignatureAlgorithmIdentifier OPTIONAL, macAlgorithm [2]
|
|
432
|
+
* MessageAuthenticationCodeAlgorithm OPTIONAL }` with the RFC 6211 sec. 2 constraint "Exactly one
|
|
433
|
+
* of signatureAlgorithm or macAlgorithm SHALL be present", which the ASN.1 states as a
|
|
434
|
+
* `WITH COMPONENTS` pair and which no decoder generator enforces.
|
|
435
|
+
*
|
|
436
|
+
* The exclusive-or is decided in the build, where both fields are in hand, and each failure gets
|
|
437
|
+
* its own message: a value carrying neither arm protects one of the two identifiers it exists to
|
|
438
|
+
* protect, and one carrying both claims to be in two places at once. A trailing third element is
|
|
439
|
+
* refused by the engine, which consumes no element a field did not claim, so the SEQUENCE is the
|
|
440
|
+
* two elements Bouncy Castle's own size check requires. */
|
|
441
|
+
var ALG_PROTECTION = schema.seq([
|
|
442
|
+
schema.field("digestAlgorithm", ALGORITHM_IDENTIFIER),
|
|
443
|
+
schema.optional("signatureAlgorithm", ALGORITHM_IDENTIFIER_1, { tag: 1 }),
|
|
444
|
+
schema.optional("macAlgorithm", ALGORITHM_IDENTIFIER_2, { tag: 2 }),
|
|
445
|
+
], {
|
|
446
|
+
assert: "sequence", code: "cms/bad-algorithm-protection-attr", what: "CMSAlgorithmProtection",
|
|
447
|
+
build: function (m, ctx) {
|
|
448
|
+
/** @internal The two arms hold ALGORITHM IDENTIFIERS, not a signature and not a MAC, and the
|
|
449
|
+
* tests below are presence tests on a decoded record. The names say which, since `sig` and
|
|
450
|
+
* `mac` read as the values those words usually name and a comparison against one of those
|
|
451
|
+
* would owe a constant-time one. */
|
|
452
|
+
var sigAlgId = m.fields.signatureAlgorithm.present ? m.fields.signatureAlgorithm.value.result : null;
|
|
453
|
+
var macAlgId = m.fields.macAlgorithm.present ? m.fields.macAlgorithm.value.result : null;
|
|
454
|
+
if (sigAlgId !== null && macAlgId !== null) {
|
|
455
|
+
throw ctx.E("cms/bad-algorithm-protection-attr", "a CMSAlgorithmProtection carries exactly " +
|
|
456
|
+
"one of signatureAlgorithm [1] or macAlgorithm [2] (RFC 6211 sec. 2), and this one carries both");
|
|
457
|
+
}
|
|
458
|
+
if (sigAlgId === null && macAlgId === null) {
|
|
459
|
+
throw ctx.E("cms/bad-algorithm-protection-attr", "a CMSAlgorithmProtection carries exactly " +
|
|
460
|
+
"one of signatureAlgorithm [1] or macAlgorithm [2] (RFC 6211 sec. 2), and this one carries neither");
|
|
461
|
+
}
|
|
462
|
+
return {
|
|
463
|
+
digestAlgorithm: m.fields.digestAlgorithm.value.result,
|
|
464
|
+
signatureAlgorithm: sigAlgId,
|
|
465
|
+
macAlgorithm: macAlgId,
|
|
466
|
+
};
|
|
467
|
+
},
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
/** @internal What the RFC 6211 attribute in `attrs` says about the structure carrying it, as a
|
|
471
|
+
* report rather than a verdict: `{ present, oid, compared, mismatch }`. Read ONCE per structure by
|
|
472
|
+
* each consumer, so the row an operator reads and the decision a verdict rests on cannot disagree.
|
|
473
|
+
*
|
|
474
|
+
* Sec. 3.1 and sec. 3.2 make both comparisons mandatory for a validator that supports the
|
|
475
|
+
* attribute, and support is claimed the moment this runs: "If the fields are not the same (modulo
|
|
476
|
+
* encoding), then signature validation MUST fail."
|
|
477
|
+
*
|
|
478
|
+
* Both arms compare modulo encoding, in the two ways sec. 3 names. A field with a default compares
|
|
479
|
+
* the same written out as omitted, which is the clause's own MUST and which it says a binary
|
|
480
|
+
* compare cannot satisfy; RSASSA-PSS is where that arises, since RFC 4055 clause 3.1 defaults all
|
|
481
|
+
* four of its parameter fields. Absent and DER NULL parameters read as one algorithm on the digest
|
|
482
|
+
* arm, because RFC 3370 clause 2.1 and RFC 5754 clause 2 require both encodings to be ACCEPTED, so
|
|
483
|
+
* both legitimately appear on either side; sec. 3 leaves that case to the implementer. Anything
|
|
484
|
+
* else in a parameter field is read as written: that is where RSASSA-PSS puts its hash, mask
|
|
485
|
+
* generator, salt length and trailer field, which the signature itself does not cover, so
|
|
486
|
+
* collapsing two values there would admit the substitution the attribute exists to refuse.
|
|
487
|
+
*
|
|
488
|
+
* An arm belonging to the other placement is a mismatch rather than an absent one: sec. 2 populates
|
|
489
|
+
* `signatureAlgorithm` "only if the attribute is placed in a SignerInfo.signedAttrs sequence" and
|
|
490
|
+
* `macAlgorithm` only in an `AuthenticatedData.authAttrs`, so an attribute carrying the wrong arm is
|
|
491
|
+
* making a claim about a structure this is not.
|
|
492
|
+
*
|
|
493
|
+
* @internal
|
|
494
|
+
*/
|
|
495
|
+
function algorithmProtectionReport(attrs, digestAlgorithm, armName, armIdentifier) {
|
|
496
|
+
var out = { present: false, oid: null, compared: [], mismatch: null };
|
|
497
|
+
var found = null;
|
|
498
|
+
for (var i = 0; i < attrs.length; i++) {
|
|
499
|
+
if (found === null && _isAlgProtection(attrs[i].type)) found = attrs[i];
|
|
500
|
+
}
|
|
501
|
+
if (found === null) return out;
|
|
502
|
+
out.present = true;
|
|
503
|
+
out.oid = found.type;
|
|
504
|
+
var value;
|
|
505
|
+
try { value = parseAlgorithmProtection(found.values[0]); }
|
|
506
|
+
catch (e) {
|
|
507
|
+
out.mismatch = { code: (e && e.code) || "cms/bad-algorithm-protection-attr", message: e && e.message };
|
|
508
|
+
return out;
|
|
509
|
+
}
|
|
510
|
+
if (!sameDigestAlgorithm(digestAlgorithm, value.digestAlgorithm)) {
|
|
511
|
+
out.mismatch = { code: "cms/algorithm-protection-mismatch",
|
|
512
|
+
message: "the algorithm protection attribute names the digest algorithm " +
|
|
513
|
+
(value.digestAlgorithm.name || value.digestAlgorithm.oid) + " and the structure it protects " +
|
|
514
|
+
"names " + ((digestAlgorithm && (digestAlgorithm.name || digestAlgorithm.oid)) || "none") +
|
|
515
|
+
" (RFC 6211 sec. 3)" };
|
|
516
|
+
return out;
|
|
517
|
+
}
|
|
518
|
+
out.compared.push("digestAlgorithm");
|
|
519
|
+
var wantsMacArm = armName === "macAlgorithm";
|
|
520
|
+
var arm = wantsMacArm ? value.macAlgorithm : value.signatureAlgorithm;
|
|
521
|
+
var other = wantsMacArm ? value.signatureAlgorithm : value.macAlgorithm;
|
|
522
|
+
if (arm === null || other !== null) {
|
|
523
|
+
out.mismatch = { code: "cms/algorithm-protection-mismatch",
|
|
524
|
+
message: "the algorithm protection attribute carries " + (arm === null
|
|
525
|
+
? "no " + armName + " to compare against the one this structure names"
|
|
526
|
+
: "the arm belonging to the other placement") + ", and RFC 6211 sec. 2 populates each arm " +
|
|
527
|
+
"only in the placement it belongs to" };
|
|
528
|
+
return out;
|
|
529
|
+
}
|
|
530
|
+
if (!sameAlgorithmModuloDefaults(armIdentifier, arm)) {
|
|
531
|
+
out.mismatch = { code: "cms/algorithm-protection-mismatch",
|
|
532
|
+
message: "the algorithm protection attribute names the " + armName + " " +
|
|
533
|
+
(arm.name || arm.oid) + " and the structure it protects names " +
|
|
534
|
+
((armIdentifier && (armIdentifier.name || armIdentifier.oid)) || "none") +
|
|
535
|
+
", or their parameters differ (RFC 6211 sec. 3)" };
|
|
536
|
+
return out;
|
|
537
|
+
}
|
|
538
|
+
out.compared.push(armName);
|
|
539
|
+
return out;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/** @internal The producer door for the RFC 6211 attribute, shared by every verb that emits one. A
|
|
543
|
+
* caller-supplied copy cannot contradict the identifiers being emitted, and cannot be a second
|
|
544
|
+
* instance beside the one a verb emitted: caller attributes reach the wire without being read, so
|
|
545
|
+
* without this a message could carry a protection attribute naming algorithms its own structure
|
|
546
|
+
* does not use, which every verifier that reads the attribute then refuses. The comparison is
|
|
547
|
+
* against the very value the emitter would build, so it is the emitter's own answer rather than a
|
|
548
|
+
* second derivation of it.
|
|
549
|
+
*
|
|
550
|
+
* `attrs` are `{ type, values }` records. The signing verbs hold their attributes in that shape
|
|
551
|
+
* already; the authenticate verb holds DER TLVs and adapts them, so the POLICY lives here once
|
|
552
|
+
* rather than in a loop per verb.
|
|
553
|
+
*
|
|
554
|
+
* @internal
|
|
555
|
+
*/
|
|
556
|
+
function assertAlgorithmProtectionEmission(attrs, wantValue, E) {
|
|
557
|
+
var found = 0;
|
|
558
|
+
for (var i = 0; i < attrs.length; i++) {
|
|
559
|
+
if (!_isAlgProtection(attrs[i].type)) continue;
|
|
560
|
+
found += 1;
|
|
561
|
+
if (found > 1) {
|
|
562
|
+
throw E("cms/bad-input", "only one algorithm protection attribute may be emitted (RFC 6211 " +
|
|
563
|
+
"sec. 2), counting both identifiers it is assigned and any copy supplied by the caller");
|
|
564
|
+
}
|
|
565
|
+
if (attrs[i].values.length !== 1) {
|
|
566
|
+
throw E("cms/bad-input", "an algorithm protection attribute carries exactly one value " +
|
|
567
|
+
"(RFC 6211 sec. 2), got " + attrs[i].values.length);
|
|
568
|
+
}
|
|
569
|
+
if (guard.bytes.compare(attrs[i].values[0], wantValue) !== 0) {
|
|
570
|
+
throw E("cms/bad-input", "the algorithm protection attribute supplied does not copy the " +
|
|
571
|
+
"algorithm identifiers this structure uses (RFC 6211 sec. 2), so every verifier that reads " +
|
|
572
|
+
"it would refuse the result; omit it and pass algorithmProtection: true to have it built");
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
339
577
|
function makeSignerInfo(mode) {
|
|
340
578
|
return schema.seq([
|
|
341
579
|
schema.field("version", SIGNER_VERSION),
|
|
@@ -780,7 +1018,7 @@ var AUTHENTICATED_DATA = schema.seq([
|
|
|
780
1018
|
_assertDerEncodedAttrs(m.fields.authAttrs.node, "cms/bad-auth-attrs", ctx);
|
|
781
1019
|
authAttrs = m.fields.authAttrs.value.items.map(function (it) { return it.value.result; });
|
|
782
1020
|
_checkAttrPlacement(authAttrs, "auth");
|
|
783
|
-
_checkContentBindingAttrs(authAttrs, "
|
|
1021
|
+
_checkContentBindingAttrs(authAttrs, "auth", ctx);
|
|
784
1022
|
_assertContentTypeMatchesAttrs(authAttrs, encapContentInfo.eContentType, ctx);
|
|
785
1023
|
authAttrsBytes = m.fields.authAttrs.node.bytes;
|
|
786
1024
|
}
|
|
@@ -827,7 +1065,12 @@ var AUTH_ENVELOPED_DATA = schema.seq([
|
|
|
827
1065
|
if (hasAuthAttrs) {
|
|
828
1066
|
_assertDerEncodedAttrs(m.fields.authAttrs.node, "cms/bad-auth-attrs", ctx);
|
|
829
1067
|
authAttrs = m.fields.authAttrs.value.items.map(function (it) { return it.value.result; });
|
|
830
|
-
|
|
1068
|
+
/** @internal Both names: this is an authenticated-attribute set, and it is also this content
|
|
1069
|
+
* type's own set, which refuses one attribute more. The algorithm protection attribute
|
|
1070
|
+
* belongs in an `AuthenticatedData` and in no other, and the verbs that emit attributes
|
|
1071
|
+
* refuse it here, so the parser refuses it here too rather than reading a message this
|
|
1072
|
+
* package would not write. */
|
|
1073
|
+
_checkAttrPlacement(authAttrs, ["auth", "authEnveloped"]);
|
|
831
1074
|
_checkNoDuplicateAttrs(authAttrs);
|
|
832
1075
|
_assertContentTypeMatchesAttrs(authAttrs, encryptedContentInfo.contentType, ctx);
|
|
833
1076
|
authAttrsBytes = m.fields.authAttrs.node.bytes;
|
|
@@ -968,6 +1211,40 @@ var CONTENT_INFO = schema.seq([
|
|
|
968
1211
|
*/
|
|
969
1212
|
var parse = pkix.makeRecordingParser({ pemLabel: "CMS", PemError: PemError, ErrorClass: CmsError, prefix: "cms", what: "CMS ContentInfo", topSchema: CONTENT_INFO, ns: NS }, "cms");
|
|
970
1213
|
|
|
1214
|
+
/**
|
|
1215
|
+
* @primitive pki.schema.cms.parseAlgorithmProtection
|
|
1216
|
+
* @signature pki.schema.cms.parseAlgorithmProtection(der) -> { digestAlgorithm, signatureAlgorithm, macAlgorithm }
|
|
1217
|
+
* @since 0.8.53
|
|
1218
|
+
* @status stable
|
|
1219
|
+
* @spec RFC 6211
|
|
1220
|
+
* @related pki.cms.verify, pki.cms.sign, pki.schema.cms.parse
|
|
1221
|
+
*
|
|
1222
|
+
* Decode a `CMSAlgorithmProtection` attribute value (RFC 6211 sec. 2): a copy of the digest
|
|
1223
|
+
* algorithm identifier the structure names, plus exactly one of the signature algorithm identifier
|
|
1224
|
+
* (the `[1]` arm, for a `SignerInfo`) or the message authentication code algorithm identifier (the
|
|
1225
|
+
* `[2]` arm, for an `AuthenticatedData`). The arm that is absent reads as `null`. Each identifier is
|
|
1226
|
+
* surfaced whole, `{ oid, name, parameters }`, because the parameters are what the attribute exists
|
|
1227
|
+
* to pin: a signature algorithm's parameters are not covered by the signature.
|
|
1228
|
+
*
|
|
1229
|
+
* A value carrying both arms or neither is refused `cms/bad-algorithm-protection-attr`, which is the
|
|
1230
|
+
* sec. 2 rule "Exactly one of signatureAlgorithm or macAlgorithm SHALL be present". `pki.cms.parse`
|
|
1231
|
+
* decodes the attribute during its own walk, so a message carrying a malformed one is refused there;
|
|
1232
|
+
* this verb is for reading a value held on its own.
|
|
1233
|
+
*
|
|
1234
|
+
* @example
|
|
1235
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
1236
|
+
* var key = await pki.key.export(pair.privateKey);
|
|
1237
|
+
* var cert = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
1238
|
+
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
1239
|
+
* var signedDer = await pki.cms.sign(Buffer.from("hello"), { cert: cert, key: key }, { algorithmProtection: true });
|
|
1240
|
+
* var si = pki.schema.cms.parse(signedDer).signerInfos[0];
|
|
1241
|
+
* var attr = si.signedAttrs.filter(function (a) { return a.name === "cmsAlgorithmProtection"; })[0];
|
|
1242
|
+
* var protection = pki.schema.cms.parseAlgorithmProtection(attr.values[0]);
|
|
1243
|
+
* protection.digestAlgorithm.name; // "sha512", the digest Ed25519 signs under here
|
|
1244
|
+
* protection.macAlgorithm; // null, this being a SignerInfo
|
|
1245
|
+
*/
|
|
1246
|
+
var parseAlgorithmProtection = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: CmsError, prefix: "cms", what: "CMSAlgorithmProtection", topSchema: ALG_PROTECTION, ns: NS });
|
|
1247
|
+
|
|
971
1248
|
/**
|
|
972
1249
|
* @primitive pki.schema.cms.pemDecode
|
|
973
1250
|
* @signature pki.schema.cms.pemDecode(text, label?) -> Buffer
|
|
@@ -1101,9 +1378,103 @@ function sameDigestAlgorithm(a, c) {
|
|
|
1101
1378
|
a.parameters.equals(c.parameters);
|
|
1102
1379
|
}
|
|
1103
1380
|
|
|
1381
|
+
/** @internal The parameter fields that carry an ASN.1 DEFAULT, keyed by the algorithm that owns
|
|
1382
|
+
* them, because RFC 6211 sec. 3 says a binary compare is not enough: "A field with a default value
|
|
1383
|
+
* MUST compare as identical, independently of whether the value is defaulted or is explicitly
|
|
1384
|
+
* provided. This implies that a binary compare of the encoded bytes is insufficient." RSASSA-PSS
|
|
1385
|
+
* is the only identifier this attribute carries whose parameters have any, and RFC 4055 sec. 3.1
|
|
1386
|
+
* gives each of its four a default: `hashAlgorithm` sha1Identifier, `maskGenAlgorithm`
|
|
1387
|
+
* mgf1SHA1Identifier, `saltLength` 20 and `trailerField` 1. A row rather than a branch, so an
|
|
1388
|
+
* algorithm that arrives later with defaulted parameters is data. */
|
|
1389
|
+
var SHA1_IDENTIFIER = asn1.build.sequence([asn1.build.oid(oid.byName("sha1")), asn1.build.nullValue()]);
|
|
1390
|
+
var PARAM_MAX_TAG = 3;
|
|
1391
|
+
var PARAM_DEFAULTS = Object.create(null);
|
|
1392
|
+
PARAM_DEFAULTS[oid.byName("rsassaPss")] = [
|
|
1393
|
+
{ tag: 0, value: SHA1_IDENTIFIER },
|
|
1394
|
+
{ tag: 1, value: asn1.build.sequence([asn1.build.oid(oid.byName("mgf1")), SHA1_IDENTIFIER]) },
|
|
1395
|
+
{ tag: 2, value: asn1.build.integer(20) },
|
|
1396
|
+
{ tag: 3, value: asn1.build.integer(1) },
|
|
1397
|
+
];
|
|
1398
|
+
|
|
1399
|
+
/** @internal The tagged fields of a parameters SEQUENCE, as `[{ tag, node, bytes }]` in the order
|
|
1400
|
+
* they appear, or `null` when the bytes are not that shape. The DER rule for a SEQUENCE whose
|
|
1401
|
+
* fields are OPTIONAL or DEFAULT is one context-tagged element each, in ascending tag order with
|
|
1402
|
+
* no repeats, holding exactly one value; a producer that repeats a tag or writes them out of order
|
|
1403
|
+
* has not encoded this structure. One home for the walk, so the verifier that resolves RSASSA-PSS
|
|
1404
|
+
* parameters and the comparison that reads their defaults agree on what a field is.
|
|
1405
|
+
*
|
|
1406
|
+
* `maxTag` bounds the tags a caller will act on, so a field it has no meaning for is refused here
|
|
1407
|
+
* rather than ignored below.
|
|
1408
|
+
*
|
|
1409
|
+
* @internal
|
|
1410
|
+
*/
|
|
1411
|
+
function taggedParameterFields(params, maxTag) {
|
|
1412
|
+
if (!Buffer.isBuffer(params) || params.length === 0) return null;
|
|
1413
|
+
var node;
|
|
1414
|
+
// allow:bare-decode parameters of an identifier a parse door already accepted, read for a comparison outside any walk, so no parse context carries caps here.
|
|
1415
|
+
try { node = asn1.decode(params); } catch (_e) { return null; } // allow:swallow-unverified measured unreachable from the verbs: an attribute whose parameters do not decode is refused as cms/bad-der by the parse door, so no message carrying one reaches a comparison; bytes that do not decode are not the defaulted shape and fall back to the exact comparison
|
|
1416
|
+
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE ||
|
|
1417
|
+
!Array.isArray(node.children)) return null;
|
|
1418
|
+
var out = [], last = -1;
|
|
1419
|
+
for (var i = 0; i < node.children.length; i++) {
|
|
1420
|
+
var f = node.children[i];
|
|
1421
|
+
if (f.tagClass !== "context" || f.tagNumber <= last || f.tagNumber > maxTag ||
|
|
1422
|
+
!Array.isArray(f.children) || f.children.length !== 1) return null;
|
|
1423
|
+
last = f.tagNumber;
|
|
1424
|
+
if (!Buffer.isBuffer(f.children[0].bytes)) return null;
|
|
1425
|
+
out.push({ tag: f.tagNumber, node: f.children[0], bytes: f.children[0].bytes });
|
|
1426
|
+
}
|
|
1427
|
+
return out;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1430
|
+
/** @internal The parameters as a comparison key with every defaulted field dropped, or `null` when
|
|
1431
|
+
* they are not the shape the defaults belong to. A field whose encoding equals its default reads
|
|
1432
|
+
* the same as one the producer omitted, which is what the clause above requires; a field holding
|
|
1433
|
+
* anything else is kept and compared, since that is where RSASSA-PSS states the hash, the mask
|
|
1434
|
+
* generator, the salt length and the trailer field. */
|
|
1435
|
+
function _paramKeyWithoutDefaults(params, rows) {
|
|
1436
|
+
var fields = taggedParameterFields(params, PARAM_MAX_TAG);
|
|
1437
|
+
if (fields === null) return null;
|
|
1438
|
+
var parts = [];
|
|
1439
|
+
for (var i = 0; i < fields.length; i++) {
|
|
1440
|
+
var defaulted = false;
|
|
1441
|
+
for (var r = 0; r < rows.length; r++) {
|
|
1442
|
+
if (rows[r].tag === fields[i].tag) { defaulted = rows[r].value.equals(fields[i].bytes); break; }
|
|
1443
|
+
}
|
|
1444
|
+
if (defaulted) continue;
|
|
1445
|
+
parts.push(String(fields[i].tag) + ":" + fields[i].bytes.toString("hex"));
|
|
1446
|
+
}
|
|
1447
|
+
return parts.join(",");
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
/** @internal Whether two AlgorithmIdentifiers name one algorithm under RFC 6211 sec. 3, which is
|
|
1451
|
+
* byte equality for every identifier whose parameters hold no defaulted field and field equality
|
|
1452
|
+
* for one that does. Parameters that do not decode as the defaulted shape fall back to the exact
|
|
1453
|
+
* comparison, so an unreadable value is equal only to the identical bytes. */
|
|
1454
|
+
function sameAlgorithmModuloDefaults(a, c) {
|
|
1455
|
+
if (!a || !c || a.oid !== c.oid) return false;
|
|
1456
|
+
var rows = PARAM_DEFAULTS[a.oid];
|
|
1457
|
+
if (rows === undefined) return guard.der.sameAlgorithmExactly(a, c);
|
|
1458
|
+
/** @internal Two copies of one identifier normally carry the same bytes, and that answer needs no
|
|
1459
|
+
* decode: the fields are read only when the copies disagree, which is the case the clause is
|
|
1460
|
+
* about and the only one worth the walk. */
|
|
1461
|
+
if (guard.der.sameAlgorithmExactly(a, c)) return true;
|
|
1462
|
+
var ak = _paramKeyWithoutDefaults(a.parameters, rows);
|
|
1463
|
+
var ck = _paramKeyWithoutDefaults(c.parameters, rows);
|
|
1464
|
+
if (ak === null || ck === null) return guard.der.sameAlgorithmExactly(a, c);
|
|
1465
|
+
return ak === ck;
|
|
1466
|
+
}
|
|
1467
|
+
|
|
1104
1468
|
module.exports = {
|
|
1105
1469
|
parse: parse,
|
|
1470
|
+
parseAlgorithmProtection: parseAlgorithmProtection,
|
|
1471
|
+
algorithmProtectionReport: algorithmProtectionReport, // @internal
|
|
1472
|
+
assertAlgorithmProtectionEmission: assertAlgorithmProtectionEmission, // @internal
|
|
1473
|
+
isAlgorithmProtectionType: isAlgorithmProtectionType, // @internal
|
|
1474
|
+
algorithmProtectionOid: algorithmProtectionOid, // @internal
|
|
1106
1475
|
sameDigestAlgorithm: sameDigestAlgorithm, // @internal
|
|
1476
|
+
sameAlgorithmModuloDefaults: sameAlgorithmModuloDefaults, // @internal
|
|
1477
|
+
taggedParameterFields: taggedParameterFields, // @internal
|
|
1107
1478
|
pemDecode: pemDecode,
|
|
1108
1479
|
pemEncode: pemEncode,
|
|
1109
1480
|
matches: matches,
|
package/package.json
CHANGED
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:f287baca-f745-4ecd-8265-d6953251f0b6",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-10-
|
|
8
|
+
"timestamp": "2026-10-06T00:56:34.786Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.8.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.8.53",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.8.
|
|
25
|
+
"version": "0.8.53",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.8.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.8.53",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.8.
|
|
57
|
+
"ref": "@blamejs/pki@0.8.53",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|