@blamejs/pki 0.1.14 → 0.1.16
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 +33 -1
- package/README.md +3 -2
- package/index.js +17 -9
- package/lib/asn1-der.js +3 -2
- package/lib/constants.js +4 -0
- package/lib/framework-error.js +8 -0
- package/lib/oid.js +8 -3
- package/lib/path-validate.js +1393 -0
- package/lib/schema-cms.js +332 -15
- package/lib/schema-crl.js +5 -0
- package/lib/schema-engine.js +6 -5
- package/lib/schema-pkix.js +324 -13
- package/lib/webcrypto.js +2 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/schema-cms.js
CHANGED
|
@@ -9,14 +9,16 @@
|
|
|
9
9
|
* @slug cms
|
|
10
10
|
*
|
|
11
11
|
* @intro
|
|
12
|
-
* CMS
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
12
|
+
* CMS handling per RFC 5652 (§3 ContentInfo envelope). `parse` turns a DER or PEM
|
|
13
|
+
* (`CMS`) message into a structured object and is an OID-dispatch envelope —
|
|
14
|
+
* ContentInfo reads its `contentType` and structurally decodes SignedData (§5),
|
|
15
|
+
* EnvelopedData (§6, with all five RecipientInfo kinds — key-transport,
|
|
16
|
+
* key-agreement per RFC 5753, KEK, password, and other), and EncryptedData (§8);
|
|
17
|
+
* the remaining PKCS#7 content types are recognized and rejected with a precise
|
|
18
|
+
* `cms/unsupported-content-type` rather than a generic unknown-format error. A
|
|
19
|
+
* SignedData surfaces its version, digest algorithms, encapsulated content,
|
|
20
|
+
* certificate / CRL sets, and signer infos; an EnvelopedData its recipient infos
|
|
21
|
+
* and encrypted content info; an EncryptedData its encrypted content info.
|
|
20
22
|
*
|
|
21
23
|
* CMS is a signed container: the bytes an external verifier must hash are
|
|
22
24
|
* surfaced RAW and never re-serialized. `encapContentInfo.eContent` is the raw
|
|
@@ -53,15 +55,26 @@ var NAME = pkix.name(NS);
|
|
|
53
55
|
// version is {1,3} (RFC 5652 §5.1, §5.3). Wider accept maps than any other format.
|
|
54
56
|
var SIGNED_DATA_VERSION = pkix.versionReader(NS, { "1": 1, "3": 3, "4": 4, "5": 5 });
|
|
55
57
|
var SIGNER_VERSION = pkix.versionReader(NS, { "1": 1, "3": 3 });
|
|
58
|
+
// EnvelopedData §6.1 (0/2/3/4), EncryptedData §8 (0/2), and the per-RecipientInfo
|
|
59
|
+
// versions (RFC 5652 §6.2.1-§6.2.4): ktri {0,2}, kari {3}, kekri {4}, pwri {0}.
|
|
60
|
+
var ENVELOPED_DATA_VERSION = pkix.versionReader(NS, { "0": 0, "2": 2, "3": 3, "4": 4 });
|
|
61
|
+
var ENCRYPTED_DATA_VERSION = pkix.versionReader(NS, { "0": 0, "2": 2 });
|
|
62
|
+
var KTRI_VERSION = pkix.versionReader(NS, { "0": 0, "2": 2 });
|
|
63
|
+
var KARI_VERSION = pkix.versionReader(NS, { "3": 3 });
|
|
64
|
+
var KEKRI_VERSION = pkix.versionReader(NS, { "4": 4 });
|
|
65
|
+
var PWRI_VERSION = pkix.versionReader(NS, { "0": 0 });
|
|
56
66
|
|
|
57
|
-
// id-signedData
|
|
58
|
-
// are recognized-and-deferred (a precise
|
|
59
|
-
// OIDs resolve from the registry (pkcs7 /
|
|
67
|
+
// id-signedData / id-envelopedData / id-encryptedData are the content types this
|
|
68
|
+
// build structurally decodes; the rest are recognized-and-deferred (a precise
|
|
69
|
+
// diagnostic, not a silent unknown-format). OIDs resolve from the registry (pkcs7 /
|
|
70
|
+
// smimeCt families), never dotted literals.
|
|
60
71
|
var OID_SIGNED_DATA = oid.byName("signedData");
|
|
72
|
+
var OID_ENVELOPED_DATA = oid.byName("envelopedData");
|
|
73
|
+
var OID_ENCRYPTED_DATA = oid.byName("encryptedData");
|
|
61
74
|
var OID_DATA = oid.byName("data");
|
|
62
75
|
var DEFERRED = new Set([
|
|
63
|
-
oid.byName("data"), oid.byName("
|
|
64
|
-
oid.byName("digestedData"), oid.byName("
|
|
76
|
+
oid.byName("data"), oid.byName("signedAndEnvelopedData"),
|
|
77
|
+
oid.byName("digestedData"), oid.byName("authData"),
|
|
65
78
|
]);
|
|
66
79
|
// The two mandatory signed-attribute types (RFC 5652 §5.3, §11.1/§11.2).
|
|
67
80
|
var OID_CONTENT_TYPE = oid.byName("contentType");
|
|
@@ -277,11 +290,313 @@ var SIGNED_DATA = schema.seq([
|
|
|
277
290
|
},
|
|
278
291
|
});
|
|
279
292
|
|
|
293
|
+
// ==== EnvelopedData / EncryptedData (RFC 5652 §6/§8, RFC 5753) ========
|
|
294
|
+
var T = asn1.TAGS;
|
|
295
|
+
|
|
296
|
+
// EncryptedContentInfo ::= SEQUENCE { contentType OID, contentEncryptionAlgorithm
|
|
297
|
+
// AlgorithmIdentifier, encryptedContent [0] IMPLICIT OCTET STRING OPTIONAL } (RFC
|
|
298
|
+
// 5652 §6.1). encryptedContent is [0] IMPLICIT (context PRIMITIVE) — its content
|
|
299
|
+
// octets ARE the ciphertext directly, so it reads through implicitOctetString(0),
|
|
300
|
+
// NOT the [0] EXPLICIT shape ENCAP_CONTENT_INFO uses (which would double-strip a
|
|
301
|
+
// length header). The ciphertext + algorithm parameters are surfaced RAW.
|
|
302
|
+
var ENCRYPTED_CONTENT_INFO = schema.seq([
|
|
303
|
+
schema.field("contentType", schema.oidLeaf()),
|
|
304
|
+
schema.field("contentEncryptionAlgorithm", ALGORITHM_IDENTIFIER),
|
|
305
|
+
schema.optional("encryptedContent", schema.implicitOctetString(0), { tag: 0 }),
|
|
306
|
+
], {
|
|
307
|
+
assert: "sequence", arity: { min: 2, max: 3 }, code: "cms/bad-encrypted-content-info", what: "EncryptedContentInfo",
|
|
308
|
+
build: function (m) {
|
|
309
|
+
return {
|
|
310
|
+
contentType: m.fields.contentType.value,
|
|
311
|
+
contentEncryptionAlgorithm: m.fields.contentEncryptionAlgorithm.value.result,
|
|
312
|
+
encryptedContent: m.fields.encryptedContent.present ? m.fields.encryptedContent.value : null,
|
|
313
|
+
};
|
|
314
|
+
},
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
// RecipientIdentifier ::= CHOICE { issuerAndSerialNumber, subjectKeyIdentifier [0]
|
|
318
|
+
// IMPLICIT OCTET STRING } (RFC 5652 §6.2.1) — structurally identical to
|
|
319
|
+
// SignerIdentifier; reuse ISSUER_AND_SERIAL + the implicitOctetString(0) leaf.
|
|
320
|
+
var RECIPIENT_IDENTIFIER = schema.choice([
|
|
321
|
+
{ when: { tagClass: "universal", tagNumber: T.SEQUENCE }, schema: ISSUER_AND_SERIAL },
|
|
322
|
+
{ when: { tagClass: "context", tagNumber: 0 }, schema: schema.implicitOctetString(0) },
|
|
323
|
+
], { code: "cms/bad-recipient-identifier", what: "RecipientIdentifier" });
|
|
324
|
+
|
|
325
|
+
// KeyTransRecipientInfo ::= SEQUENCE { version(0|2), rid RecipientIdentifier,
|
|
326
|
+
// keyEncryptionAlgorithm, encryptedKey OCTET STRING } (RFC 5652 §6.2.1).
|
|
327
|
+
var KEY_TRANS_RECIPIENT_INFO = schema.seq([
|
|
328
|
+
schema.field("version", KTRI_VERSION),
|
|
329
|
+
schema.field("rid", RECIPIENT_IDENTIFIER),
|
|
330
|
+
schema.field("keyEncryptionAlgorithm", ALGORITHM_IDENTIFIER),
|
|
331
|
+
schema.field("encryptedKey", schema.octetString()),
|
|
332
|
+
], {
|
|
333
|
+
assert: "sequence", code: "cms/bad-ktri", what: "KeyTransRecipientInfo",
|
|
334
|
+
build: function (m) {
|
|
335
|
+
var version = m.fields.version.value;
|
|
336
|
+
var ridNode = m.fields.rid.node;
|
|
337
|
+
var isSkid = ridNode.tagClass === "context" && ridNode.tagNumber === 0;
|
|
338
|
+
// RFC 5652 §6.2.1 — rid ⇔ version: issuerAndSerialNumber ⇒ 0, subjectKeyIdentifier ⇒ 2.
|
|
339
|
+
if (isSkid && version !== 2) throw NS.E("cms/bad-recipient-version", "a subjectKeyIdentifier recipient identifier requires KeyTransRecipientInfo version 2 (RFC 5652 §6.2.1)");
|
|
340
|
+
if (!isSkid && version !== 0) throw NS.E("cms/bad-recipient-version", "an issuerAndSerialNumber recipient identifier requires KeyTransRecipientInfo version 0 (RFC 5652 §6.2.1)");
|
|
341
|
+
return {
|
|
342
|
+
type: "ktri", version: version,
|
|
343
|
+
rid: isSkid ? { subjectKeyIdentifier: m.fields.rid.value } : m.fields.rid.value.result,
|
|
344
|
+
ridType: isSkid ? "subjectKeyIdentifier" : "issuerAndSerialNumber",
|
|
345
|
+
keyEncryptionAlgorithm: m.fields.keyEncryptionAlgorithm.value.result,
|
|
346
|
+
encryptedKey: m.fields.encryptedKey.value,
|
|
347
|
+
};
|
|
348
|
+
},
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
// OriginatorPublicKey ::= SEQUENCE { algorithm, publicKey BIT STRING } (RFC 5753
|
|
352
|
+
// §3.1.1), reached as originatorKey [1] IMPLICIT — SPKI-shaped but cannot reuse
|
|
353
|
+
// pkix.spki (which asserts a universal SEQUENCE), so assert:"constructed".
|
|
354
|
+
var ORIGINATOR_PUBLIC_KEY = schema.seq([
|
|
355
|
+
schema.field("algorithm", ALGORITHM_IDENTIFIER),
|
|
356
|
+
schema.field("publicKey", schema.bitString()),
|
|
357
|
+
], {
|
|
358
|
+
assert: "constructed", code: "cms/bad-originator-public-key", what: "OriginatorPublicKey",
|
|
359
|
+
build: function (m) { return { algorithm: m.fields.algorithm.value.result, publicKey: m.fields.publicKey.value }; },
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// OriginatorIdentifierOrKey ::= CHOICE { issuerAndSerialNumber, subjectKeyIdentifier
|
|
363
|
+
// [0] IMPLICIT OCTET STRING, originatorKey [1] IMPLICIT OriginatorPublicKey }.
|
|
364
|
+
var ORIGINATOR_IDENTIFIER_OR_KEY = schema.choice([
|
|
365
|
+
{ when: { tagClass: "universal", tagNumber: T.SEQUENCE }, schema: ISSUER_AND_SERIAL },
|
|
366
|
+
{ when: { tagClass: "context", tagNumber: 0 }, schema: schema.implicitOctetString(0) },
|
|
367
|
+
{ when: { tagClass: "context", tagNumber: 1 }, schema: ORIGINATOR_PUBLIC_KEY },
|
|
368
|
+
], { code: "cms/bad-originator-identifier", what: "OriginatorIdentifierOrKey" });
|
|
369
|
+
|
|
370
|
+
// RecipientKeyIdentifier (RFC 5652 §6.2.2) and KEKIdentifier (§6.2.3) are one
|
|
371
|
+
// shape — { <keyId> OCTET STRING, date GeneralizedTime OPTIONAL, other
|
|
372
|
+
// OtherKeyAttribute OPTIONAL } — differing only in the key-id field's name and
|
|
373
|
+
// the enclosing tag form. One factory defines both so the OPTIONAL handling
|
|
374
|
+
// (date and the raw-surfaced OtherKeyAttribute) cannot diverge between them.
|
|
375
|
+
function keyIdentifierSchema(keyIdName, assert, code, what) {
|
|
376
|
+
return schema.seq([
|
|
377
|
+
schema.field(keyIdName, schema.octetString()),
|
|
378
|
+
schema.optional("date", schema.time(NS), { whenUniversal: [T.GENERALIZED_TIME] }),
|
|
379
|
+
schema.optional("other", schema.any(), { whenUniversal: [T.SEQUENCE] }),
|
|
380
|
+
], {
|
|
381
|
+
assert: assert, code: code, what: what,
|
|
382
|
+
build: function (m) {
|
|
383
|
+
var out = {};
|
|
384
|
+
out[keyIdName] = m.fields[keyIdName].value;
|
|
385
|
+
out.date = m.fields.date.present ? m.fields.date.value : null;
|
|
386
|
+
out.other = m.fields.other.present ? m.fields.other.node.bytes : null;
|
|
387
|
+
return out;
|
|
388
|
+
},
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// Reached as rKeyId [0] IMPLICIT (a SEQUENCE — constructed, unlike ktri's [0] leaf).
|
|
393
|
+
var RECIPIENT_KEY_IDENTIFIER = keyIdentifierSchema("subjectKeyIdentifier",
|
|
394
|
+
"constructed", "cms/bad-recipient-key-identifier", "RecipientKeyIdentifier");
|
|
395
|
+
|
|
396
|
+
// KeyAgreeRecipientIdentifier ::= CHOICE { issuerAndSerialNumber, rKeyId [0] IMPLICIT
|
|
397
|
+
// RecipientKeyIdentifier } (RFC 5652 §6.2.2).
|
|
398
|
+
var KEY_AGREE_RECIPIENT_IDENTIFIER = schema.choice([
|
|
399
|
+
{ when: { tagClass: "universal", tagNumber: T.SEQUENCE }, schema: ISSUER_AND_SERIAL },
|
|
400
|
+
{ when: { tagClass: "context", tagNumber: 0 }, schema: RECIPIENT_KEY_IDENTIFIER },
|
|
401
|
+
], { code: "cms/bad-kari-identifier", what: "KeyAgreeRecipientIdentifier" });
|
|
402
|
+
|
|
403
|
+
// RecipientEncryptedKey ::= SEQUENCE { rid KeyAgreeRecipientIdentifier, encryptedKey
|
|
404
|
+
// OCTET STRING } (RFC 5652 §6.2.2).
|
|
405
|
+
var RECIPIENT_ENCRYPTED_KEY = schema.seq([
|
|
406
|
+
schema.field("rid", KEY_AGREE_RECIPIENT_IDENTIFIER),
|
|
407
|
+
schema.field("encryptedKey", schema.octetString()),
|
|
408
|
+
], {
|
|
409
|
+
assert: "sequence", code: "cms/bad-recipient-encrypted-key", what: "RecipientEncryptedKey",
|
|
410
|
+
build: function (m) {
|
|
411
|
+
var ridNode = m.fields.rid.node;
|
|
412
|
+
var isRkid = ridNode.tagClass === "context" && ridNode.tagNumber === 0;
|
|
413
|
+
return {
|
|
414
|
+
rid: isRkid ? m.fields.rid.value.result : m.fields.rid.value.result,
|
|
415
|
+
encryptedKey: m.fields.encryptedKey.value,
|
|
416
|
+
};
|
|
417
|
+
},
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
// KeyAgreeRecipientInfo ::= [1] IMPLICIT SEQUENCE { version(3), originator [0]
|
|
421
|
+
// EXPLICIT OriginatorIdentifierOrKey, ukm [1] EXPLICIT OPTIONAL,
|
|
422
|
+
// keyEncryptionAlgorithm, recipientEncryptedKeys SEQUENCE OF } (RFC 5652 §6.2.2 +
|
|
423
|
+
// RFC 5753 §3.1.1). originator [0] is EXPLICIT (wraps a CHOICE).
|
|
424
|
+
var KEY_AGREE_RECIPIENT_INFO = schema.seq([
|
|
425
|
+
schema.field("version", KARI_VERSION),
|
|
426
|
+
schema.field("originator", schema.explicit(0, ORIGINATOR_IDENTIFIER_OR_KEY, { code: "cms/bad-kari" })),
|
|
427
|
+
schema.optional("ukm", schema.octetString(), { tag: 1, explicit: true, emptyCode: "cms/bad-kari" }),
|
|
428
|
+
schema.field("keyEncryptionAlgorithm", ALGORITHM_IDENTIFIER),
|
|
429
|
+
schema.field("recipientEncryptedKeys", schema.seqOf(RECIPIENT_ENCRYPTED_KEY, { code: "cms/bad-recipient-encrypted-keys", what: "recipientEncryptedKeys" })),
|
|
430
|
+
], {
|
|
431
|
+
assert: "constructed", code: "cms/bad-kari", what: "KeyAgreeRecipientInfo",
|
|
432
|
+
build: function (m) {
|
|
433
|
+
var origNode = m.fields.originator.node.children[0];
|
|
434
|
+
var origForm = origNode.tagClass === "context" ? (origNode.tagNumber === 0 ? "subjectKeyIdentifier" : "originatorKey") : "issuerAndSerialNumber";
|
|
435
|
+
var origVal = m.fields.originator.value;
|
|
436
|
+
return {
|
|
437
|
+
type: "kari", version: m.fields.version.value,
|
|
438
|
+
originator: { form: origForm, value: origForm === "subjectKeyIdentifier" ? origVal : origVal.result },
|
|
439
|
+
ukm: m.fields.ukm.present ? m.fields.ukm.value : null,
|
|
440
|
+
keyEncryptionAlgorithm: m.fields.keyEncryptionAlgorithm.value.result,
|
|
441
|
+
recipientEncryptedKeys: m.fields.recipientEncryptedKeys.value.items.map(function (it) { return it.value.result; }),
|
|
442
|
+
};
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
// KEKRecipientInfo ::= [2] IMPLICIT SEQUENCE { version(4), kekid KEKIdentifier,
|
|
447
|
+
// keyEncryptionAlgorithm, encryptedKey } (RFC 5652 §6.2.3).
|
|
448
|
+
var KEK_IDENTIFIER = keyIdentifierSchema("keyIdentifier",
|
|
449
|
+
"sequence", "cms/bad-kek-identifier", "KEKIdentifier");
|
|
450
|
+
var KEK_RECIPIENT_INFO = schema.seq([
|
|
451
|
+
schema.field("version", KEKRI_VERSION),
|
|
452
|
+
schema.field("kekid", KEK_IDENTIFIER),
|
|
453
|
+
schema.field("keyEncryptionAlgorithm", ALGORITHM_IDENTIFIER),
|
|
454
|
+
schema.field("encryptedKey", schema.octetString()),
|
|
455
|
+
], {
|
|
456
|
+
assert: "constructed", code: "cms/bad-kekri", what: "KEKRecipientInfo",
|
|
457
|
+
build: function (m) {
|
|
458
|
+
return {
|
|
459
|
+
type: "kekri", version: m.fields.version.value,
|
|
460
|
+
kekid: m.fields.kekid.value.result,
|
|
461
|
+
keyEncryptionAlgorithm: m.fields.keyEncryptionAlgorithm.value.result,
|
|
462
|
+
encryptedKey: m.fields.encryptedKey.value,
|
|
463
|
+
};
|
|
464
|
+
},
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
// PasswordRecipientInfo ::= [3] IMPLICIT SEQUENCE { version(0), keyDerivationAlgorithm
|
|
468
|
+
// [0] IMPLICIT AlgorithmIdentifier OPTIONAL, keyEncryptionAlgorithm, encryptedKey }
|
|
469
|
+
// (RFC 5652 §6.2.4). keyDerivationAlgorithm [0] IMPLICIT is the one field needing
|
|
470
|
+
// the implicitTag AlgorithmIdentifier; present iff the first post-version node is [0].
|
|
471
|
+
var PASSWORD_RECIPIENT_INFO = schema.seq([
|
|
472
|
+
schema.field("version", PWRI_VERSION),
|
|
473
|
+
schema.optional("keyDerivationAlgorithm", pkix.algorithmIdentifier(NS, { implicitTag: 0 }), { tag: 0 }),
|
|
474
|
+
schema.field("keyEncryptionAlgorithm", ALGORITHM_IDENTIFIER),
|
|
475
|
+
schema.field("encryptedKey", schema.octetString()),
|
|
476
|
+
], {
|
|
477
|
+
assert: "constructed", code: "cms/bad-pwri", what: "PasswordRecipientInfo",
|
|
478
|
+
build: function (m) {
|
|
479
|
+
return {
|
|
480
|
+
type: "pwri", version: m.fields.version.value,
|
|
481
|
+
keyDerivationAlgorithm: m.fields.keyDerivationAlgorithm.present ? m.fields.keyDerivationAlgorithm.value.result : null,
|
|
482
|
+
keyEncryptionAlgorithm: m.fields.keyEncryptionAlgorithm.value.result,
|
|
483
|
+
encryptedKey: m.fields.encryptedKey.value,
|
|
484
|
+
};
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
// OtherRecipientInfo ::= [4] IMPLICIT SEQUENCE { oriType OID, oriValue ANY } (RFC
|
|
489
|
+
// 5652 §6.2.5) — oriValue opaque, surfaced RAW (the shipped encoder routes
|
|
490
|
+
// KEMRecipientInfo through here).
|
|
491
|
+
var OTHER_RECIPIENT_INFO = schema.seq([
|
|
492
|
+
schema.field("oriType", schema.oidLeaf()),
|
|
493
|
+
schema.field("oriValue", schema.any()),
|
|
494
|
+
], {
|
|
495
|
+
assert: "constructed", code: "cms/bad-ori", what: "OtherRecipientInfo",
|
|
496
|
+
build: function (m) { return { type: "ori", oriType: m.fields.oriType.value, oriValue: m.fields.oriValue.node.bytes }; },
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
// RecipientInfo ::= CHOICE { ktri KeyTransRecipientInfo, kari [1], kekri [2], pwri
|
|
500
|
+
// [3], ori [4] } (RFC 5652 §6.2). A bare universal SEQUENCE is ktri (the untagged
|
|
501
|
+
// alternative). An unknown context tag → no arm → cms/bad-recipient-info.
|
|
502
|
+
var RECIPIENT_INFO = schema.choice([
|
|
503
|
+
{ when: { tagClass: "universal", tagNumber: T.SEQUENCE }, schema: KEY_TRANS_RECIPIENT_INFO },
|
|
504
|
+
{ when: { tagClass: "context", tagNumber: 1 }, schema: KEY_AGREE_RECIPIENT_INFO },
|
|
505
|
+
{ when: { tagClass: "context", tagNumber: 2 }, schema: KEK_RECIPIENT_INFO },
|
|
506
|
+
{ when: { tagClass: "context", tagNumber: 3 }, schema: PASSWORD_RECIPIENT_INFO },
|
|
507
|
+
{ when: { tagClass: "context", tagNumber: 4 }, schema: OTHER_RECIPIENT_INFO },
|
|
508
|
+
], { code: "cms/bad-recipient-info", what: "RecipientInfo" });
|
|
509
|
+
|
|
510
|
+
// OriginatorInfo ::= [0] IMPLICIT SEQUENCE { certs [0] IMPLICIT OPTIONAL, crls [1]
|
|
511
|
+
// IMPLICIT OPTIONAL } (RFC 5652 §6.1). Members surfaced RAW (their outer tag feeds
|
|
512
|
+
// the version rule).
|
|
513
|
+
var ORIGINATOR_INFO = schema.seq([
|
|
514
|
+
schema.optional("certs", schema.implicitSetOf(0, schema.any(), { min: 1, code: "cms/bad-originator-certs", what: "certs" }), { tag: 0 }),
|
|
515
|
+
schema.optional("crls", schema.implicitSetOf(1, schema.any(), { min: 1, code: "cms/bad-originator-crls", what: "crls" }), { tag: 1 }),
|
|
516
|
+
], {
|
|
517
|
+
assert: "constructed", code: "cms/bad-originator-info", what: "OriginatorInfo",
|
|
518
|
+
build: function (m) {
|
|
519
|
+
return {
|
|
520
|
+
certs: m.fields.certs.present ? m.fields.certs.value.items.map(rawElement) : [],
|
|
521
|
+
crls: m.fields.crls.present ? m.fields.crls.value.items.map(rawElement) : [],
|
|
522
|
+
};
|
|
523
|
+
},
|
|
524
|
+
});
|
|
525
|
+
|
|
526
|
+
// RFC 5652 §6.1 — the exact EnvelopedData CMSVersion, from originatorInfo's raw
|
|
527
|
+
// cert/crl outer tags and the recipient arms (a cert `other` is [3], a v2AttrCert
|
|
528
|
+
// [2]; a crl `other` is [1]; a pwri or ori forces v3; all-ktri-IAS with no
|
|
529
|
+
// originatorInfo/unprotectedAttrs is v0; everything else v2).
|
|
530
|
+
function _expectedEnvelopedDataVersion(originatorInfo, recipientInfos, hasUnprotectedAttrs) {
|
|
531
|
+
function ctx(el, n) { return el.tagClass === "context" && el.tagNumber === n; }
|
|
532
|
+
var hasOrig = !!originatorInfo;
|
|
533
|
+
var certs = hasOrig ? originatorInfo.certs : [];
|
|
534
|
+
var crls = hasOrig ? originatorInfo.crls : [];
|
|
535
|
+
if (hasOrig && (certs.some(function (c) { return ctx(c, 3); }) || crls.some(function (c) { return ctx(c, 1); }))) return 4;
|
|
536
|
+
if ((hasOrig && certs.some(function (c) { return ctx(c, 2); })) ||
|
|
537
|
+
recipientInfos.some(function (r) { return r.type === "pwri" || r.type === "ori"; })) return 3;
|
|
538
|
+
if (!hasOrig && !hasUnprotectedAttrs &&
|
|
539
|
+
recipientInfos.every(function (r) { return r.type === "ktri" && r.ridType === "issuerAndSerialNumber"; })) return 0;
|
|
540
|
+
return 2;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// EnvelopedData ::= SEQUENCE { version, originatorInfo [0] IMPLICIT OPTIONAL,
|
|
544
|
+
// recipientInfos RecipientInfos (SET SIZE 1..MAX), encryptedContentInfo,
|
|
545
|
+
// unprotectedAttrs [1] IMPLICIT OPTIONAL } (RFC 5652 §6.1). recipientInfos is
|
|
546
|
+
// min:1 (an empty SET is non-conformant — the INVERSE of SignedData's degenerate
|
|
547
|
+
// signerInfos).
|
|
548
|
+
var ENVELOPED_DATA = schema.seq([
|
|
549
|
+
schema.field("version", ENVELOPED_DATA_VERSION),
|
|
550
|
+
schema.optional("originatorInfo", ORIGINATOR_INFO, { tag: 0 }),
|
|
551
|
+
schema.field("recipientInfos", schema.setOf(RECIPIENT_INFO, { min: 1, code: "cms/bad-recipient-infos", what: "recipientInfos" })),
|
|
552
|
+
schema.field("encryptedContentInfo", ENCRYPTED_CONTENT_INFO),
|
|
553
|
+
schema.optional("unprotectedAttrs", schema.implicitSetOf(1, ATTRIBUTE, { min: 1, code: "cms/bad-unprotected-attrs", what: "unprotectedAttrs" }), { tag: 1 }),
|
|
554
|
+
], {
|
|
555
|
+
assert: "sequence", code: "cms/bad-enveloped-data", what: "EnvelopedData",
|
|
556
|
+
build: function (m) {
|
|
557
|
+
var version = m.fields.version.value;
|
|
558
|
+
var originatorInfo = m.fields.originatorInfo.present ? m.fields.originatorInfo.value.result : null;
|
|
559
|
+
var recipientInfos = m.fields.recipientInfos.value.items.map(function (it) { return it.value.result; });
|
|
560
|
+
var hasUnprotectedAttrs = m.fields.unprotectedAttrs.present;
|
|
561
|
+
var expected = _expectedEnvelopedDataVersion(originatorInfo, recipientInfos, hasUnprotectedAttrs);
|
|
562
|
+
if (version !== expected) throw NS.E("cms/bad-version", "EnvelopedData version " + version + " does not match its contents (RFC 5652 §6.1 requires v" + expected + ")");
|
|
563
|
+
return {
|
|
564
|
+
version: version,
|
|
565
|
+
originatorInfo: originatorInfo,
|
|
566
|
+
recipientInfos: recipientInfos,
|
|
567
|
+
encryptedContentInfo: m.fields.encryptedContentInfo.value.result,
|
|
568
|
+
unprotectedAttrs: hasUnprotectedAttrs ? m.fields.unprotectedAttrs.value.items.map(function (it) { return it.value.result; }) : null,
|
|
569
|
+
};
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
// EncryptedData ::= SEQUENCE { version, encryptedContentInfo, unprotectedAttrs [1]
|
|
574
|
+
// IMPLICIT OPTIONAL } (RFC 5652 §8) — no recipients, no originatorInfo; the CEK is
|
|
575
|
+
// distributed out of band. version is 0, or 2 iff unprotectedAttrs are present.
|
|
576
|
+
var ENCRYPTED_DATA = schema.seq([
|
|
577
|
+
schema.field("version", ENCRYPTED_DATA_VERSION),
|
|
578
|
+
schema.field("encryptedContentInfo", ENCRYPTED_CONTENT_INFO),
|
|
579
|
+
schema.optional("unprotectedAttrs", schema.implicitSetOf(1, ATTRIBUTE, { min: 1, code: "cms/bad-unprotected-attrs", what: "unprotectedAttrs" }), { tag: 1 }),
|
|
580
|
+
], {
|
|
581
|
+
assert: "sequence", code: "cms/bad-encrypted-data", what: "EncryptedData",
|
|
582
|
+
build: function (m) {
|
|
583
|
+
var version = m.fields.version.value;
|
|
584
|
+
var hasUnprotectedAttrs = m.fields.unprotectedAttrs.present;
|
|
585
|
+
var expected = hasUnprotectedAttrs ? 2 : 0;
|
|
586
|
+
if (version !== expected) throw NS.E("cms/bad-version", "EncryptedData version " + version + " does not match its contents (RFC 5652 §8 requires v" + expected + ")");
|
|
587
|
+
return {
|
|
588
|
+
version: version,
|
|
589
|
+
encryptedContentInfo: m.fields.encryptedContentInfo.value.result,
|
|
590
|
+
unprotectedAttrs: hasUnprotectedAttrs ? m.fields.unprotectedAttrs.value.items.map(function (it) { return it.value.result; }) : null,
|
|
591
|
+
};
|
|
592
|
+
},
|
|
593
|
+
});
|
|
594
|
+
|
|
280
595
|
// ContentInfo ::= SEQUENCE { contentType OID, content [0] EXPLICIT ANY DEFINED BY
|
|
281
596
|
// contentType } (RFC 5652 §3). The content is captured raw (explicit(0, any()))
|
|
282
597
|
// and re-dispatched by contentType inside the build: id-signedData walks
|
|
283
|
-
// SIGNED_DATA,
|
|
284
|
-
// are rejected.
|
|
598
|
+
// SIGNED_DATA, id-envelopedData / id-encryptedData walk their schemas, the other
|
|
599
|
+
// PKCS#7 types are recognized-and-deferred, unknown OIDs are rejected.
|
|
285
600
|
var CONTENT_INFO = schema.seq([
|
|
286
601
|
schema.field("contentType", schema.oidLeaf()),
|
|
287
602
|
schema.field("content", schema.explicit(0, schema.any(), { code: "cms/not-a-content-info" })),
|
|
@@ -290,6 +605,8 @@ var CONTENT_INFO = schema.seq([
|
|
|
290
605
|
build: function (m, ctx) {
|
|
291
606
|
var ct = m.fields.contentType.value;
|
|
292
607
|
if (ct === OID_SIGNED_DATA) return schema.walk(SIGNED_DATA, m.fields.content.value, ctx).result;
|
|
608
|
+
if (ct === OID_ENVELOPED_DATA) return schema.walk(ENVELOPED_DATA, m.fields.content.value, ctx).result;
|
|
609
|
+
if (ct === OID_ENCRYPTED_DATA) return schema.walk(ENCRYPTED_DATA, m.fields.content.value, ctx).result;
|
|
293
610
|
if (DEFERRED.has(ct)) {
|
|
294
611
|
throw NS.E("cms/unsupported-content-type", (ctx.oid.name(ct) || ct) + " is recognized but not parsed by this build");
|
|
295
612
|
}
|
package/lib/schema-crl.js
CHANGED
|
@@ -121,6 +121,11 @@ var REVOKED_LIST = schema.seqOf(REVOKED_ENTRY, {
|
|
|
121
121
|
// revokedCertificates=SEQUENCE) are disambiguated by their universal tag;
|
|
122
122
|
// crlExtensions is modeled as a trailing [0]..[0] so a stray non-[0] trailing
|
|
123
123
|
// context tag is REJECTED (crl/bad-tbs), not silently ignored.
|
|
124
|
+
// `signature` is consumed by the CERTIFICATE_LIST build (the §5.1.1.2
|
|
125
|
+
// outer==inner agreement check reads tbsMatch.fields.signature.node.bytes);
|
|
126
|
+
// the operator reads the surfaced outer signatureAlgorithm, which that
|
|
127
|
+
// check proves byte-identical.
|
|
128
|
+
// allow:schema-build-drops-parsed-field
|
|
124
129
|
var TBS_CERTLIST = schema.seq([
|
|
125
130
|
schema.optional("version", CRL_VERSION, { whenUniversal: [TAGS.INTEGER] }),
|
|
126
131
|
schema.field("signature", ALGORITHM_IDENTIFIER),
|
package/lib/schema-engine.js
CHANGED
|
@@ -63,11 +63,12 @@ function _assertShape(schema, node, ctx) {
|
|
|
63
63
|
_fail(ctx, schema.code, (schema.what || "value") + " must be a constructed value");
|
|
64
64
|
}
|
|
65
65
|
} else if (mode === "implicit") {
|
|
66
|
-
// IMPLICIT [tag] SET/SEQUENCE OF
|
|
67
|
-
// tag, so the node is a context-class
|
|
68
|
-
// children are the items
|
|
66
|
+
// IMPLICIT [tag] SET/SEQUENCE OF or a fixed-field IMPLICIT [tag] SEQUENCE body:
|
|
67
|
+
// the context tag replaces the universal tag, so the node is a context-class
|
|
68
|
+
// constructed [tag] and its direct children are the items / positional fields
|
|
69
|
+
// (no inner universal tag, no EXPLICIT unwrap).
|
|
69
70
|
if (node.tagClass !== "context" || node.tagNumber !== schema.implicitTag || !node.children) {
|
|
70
|
-
_fail(ctx, schema.code, (schema.what || "value") + " must be an IMPLICIT [" + schema.implicitTag + "]
|
|
71
|
+
_fail(ctx, schema.code, (schema.what || "value") + " must be an IMPLICIT [" + schema.implicitTag + "] constructed value");
|
|
71
72
|
}
|
|
72
73
|
} else {
|
|
73
74
|
_fail(ctx, schema.code, "unknown assert mode " + JSON.stringify(mode));
|
|
@@ -163,7 +164,7 @@ function trailing(members, opts) {
|
|
|
163
164
|
|
|
164
165
|
function seq(fields, opts) {
|
|
165
166
|
opts = opts || {};
|
|
166
|
-
return { kind: "seq", fields: fields, assert: opts.assert || "sequence", arity: opts.arity,
|
|
167
|
+
return { kind: "seq", fields: fields, assert: opts.assert || "sequence", implicitTag: opts.implicitTag, arity: opts.arity,
|
|
167
168
|
code: opts.code, what: opts.what, build: opts.build, checks: opts.checks || [] };
|
|
168
169
|
}
|
|
169
170
|
|