@blamejs/pki 0.5.24 → 0.5.26
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 +54 -0
- package/README.md +2 -2
- package/lib/cmp-build.js +220 -6
- package/lib/cmp-session.js +753 -11
- package/lib/crmf-sign.js +304 -1
- package/lib/guard-identifier.js +36 -0
- package/lib/guard-name.js +90 -1
- package/lib/oid.js +55 -2
- package/lib/path-validate.js +15 -27
- package/lib/pki-build.js +29 -0
- package/lib/schema-cmp.js +100 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/crmf-sign.js
CHANGED
|
@@ -33,6 +33,14 @@ var pkix = require("./schema-pkix");
|
|
|
33
33
|
var pkiBuild = require("./pki-build");
|
|
34
34
|
var frameworkError = require("./framework-error");
|
|
35
35
|
var guard = require("./guard-all");
|
|
36
|
+
var intrinsic = require("./guard-intrinsic");
|
|
37
|
+
// Own-key membership through an operation taken at load: it decides which proof-of-possession
|
|
38
|
+
// alternative a request carries, and a name reached from the prototype is one the RFC never defined.
|
|
39
|
+
var _hasOwn = intrinsic.hasOwn;
|
|
40
|
+
var _stringify = intrinsic.stringify;
|
|
41
|
+
var cms = require("./schema-cms");
|
|
42
|
+
var cmsEncrypt = require("./cms-encrypt");
|
|
43
|
+
var key = require("./key");
|
|
36
44
|
require("./path-validate"); // side-effect: path-validate injects its signature engine into crmf-verify at load
|
|
37
45
|
var crmfVerify = require("./crmf-verify");
|
|
38
46
|
|
|
@@ -50,6 +58,28 @@ var _b = pkiBuild.makeBuilder({
|
|
|
50
58
|
});
|
|
51
59
|
|
|
52
60
|
var KNOWN_SPEC_KEYS = { certReqId: 1, certTemplate: 1, controls: 1, regInfo: 1, pop: 1 };
|
|
61
|
+
// spec.pop across all four ProofOfPossession arms: `type` picks the arm; `raVerified` and `sender`
|
|
62
|
+
// belong to the raVerified / signature arms; the rest to POPOPrivKey (RFC 4211 sec. 4.2, 4.3).
|
|
63
|
+
// spec.pop is a CHOICE, so the accepted key set is per ARM rather than a union of all of them. A
|
|
64
|
+
// single flat list would accept `{ type: 'keyEncipherment', method: 'subsequentMessage', privateKey,
|
|
65
|
+
// recipients, archive: true }`: a caller who believed they were sending the key that way gets a
|
|
66
|
+
// message that declares a later exchange instead, with the three fields they supplied read by
|
|
67
|
+
// nothing. `type` and `method` are what pick the arm, so they are read first and the rest is checked
|
|
68
|
+
// against the arm the caller chose.
|
|
69
|
+
var POP_KEYS_BY_ARM = {
|
|
70
|
+
raVerified: { type: 1, raVerified: 1 },
|
|
71
|
+
signature: { type: 1, sender: 1 },
|
|
72
|
+
subsequentMessage: { type: 1, method: 1, subsequentMessage: 1 },
|
|
73
|
+
encryptedKey: {
|
|
74
|
+
type: 1, method: 1, privateKey: 1, identifier: 1, recipients: 1, archive: 1,
|
|
75
|
+
contentEncryptionAlgorithm: 1,
|
|
76
|
+
},
|
|
77
|
+
// The arms that refuse before they read anything else still need a set, so the refusal a caller
|
|
78
|
+
// sees names the method rather than an unknown field alongside it.
|
|
79
|
+
agreeMAC: { type: 1, method: 1 },
|
|
80
|
+
thisMessage: { type: 1, method: 1 },
|
|
81
|
+
dhMAC: { type: 1, method: 1 },
|
|
82
|
+
};
|
|
53
83
|
var KNOWN_BATCH_KEYS = { messages: 1 };
|
|
54
84
|
var KNOWN_TEMPLATE_KEYS = { version: 1, subject: 1, publicKey: 1, validity: 1, extensions: 1, issuer: 1 };
|
|
55
85
|
var REVOCATION_TEMPLATE_KEYS = { version: 1, subject: 1, publicKey: 1, validity: 1, extensions: 1, issuer: 1, serialNumber: 1 };
|
|
@@ -161,19 +191,271 @@ function _encodeCertId(id) {
|
|
|
161
191
|
return b.sequence([_b.encodeGeneralName(id.issuer), _b.serialInteger(id.serialNumber)]);
|
|
162
192
|
}
|
|
163
193
|
|
|
194
|
+
// The two ProofOfPossession arms a key that cannot sign has to use: keyEncipherment [2] and
|
|
195
|
+
// keyAgreement [3], each carrying a POPOPrivKey. Both field tags are EXPLICIT, because POPOPrivKey is
|
|
196
|
+
// itself a CHOICE and X.680 sec. 31.2.7 forbids implicit tagging of one. The parser states and
|
|
197
|
+
// enforces the same rule (schema-crmf.js), so an implicit emission here would disagree with the
|
|
198
|
+
// module's own reader.
|
|
199
|
+
var POPO_PRIVKEY_TAGS = { keyEncipherment: 2, keyAgreement: 3 };
|
|
200
|
+
// SubsequentMessage ::= INTEGER { encrCert (0), challengeResp (1) } (RFC 4211 sec. 4.2).
|
|
201
|
+
var SUBSEQUENT_MESSAGE = { encrCert: 0n, challengeResp: 1n };
|
|
202
|
+
// The specification deprecates two of the five alternatives in the same breath as defining them:
|
|
203
|
+
// thisMessage "has been deprecated in favor of encryptedKey", dhMAC "is deprecated in favor of the
|
|
204
|
+
// agreeMAC field" (sec. 4.2), and RFC 9810 sec. 5.2.8.3 restates both as `-- deprecated`. A reader
|
|
205
|
+
// still has to accept them, since a peer may send one; a producer choosing to emit one today is
|
|
206
|
+
// choosing the superseded encoding, so the builder names the successor instead of emitting it.
|
|
207
|
+
var DEPRECATED_POPO_METHOD = { thisMessage: "encryptedKey", dhMAC: "agreeMAC" };
|
|
208
|
+
|
|
209
|
+
// EncKeyWithID ::= SEQUENCE { privateKey PrivateKeyInfo,
|
|
210
|
+
// identifier CHOICE { string UTF8String, generalName GeneralName } OPTIONAL }
|
|
211
|
+
// (RFC 4211 sec. 4.2.1). `identifier` is marked OPTIONAL in the module and then made mandatory by the
|
|
212
|
+
// prose for this use: "This field MUST be present if the purpose is to prove possession of the private
|
|
213
|
+
// key." The reason is stated there too. Without it an escrow agent decrypts a key without knowing
|
|
214
|
+
// whose it is, so an attacker can wrap someone else's intercepted key in a request of their own and
|
|
215
|
+
// then ask for it back. Building a POP is that first purpose, so the caller supplies one.
|
|
216
|
+
function _encKeyWithID(privateKeyDer, identifier) {
|
|
217
|
+
var idNode;
|
|
218
|
+
if (typeof identifier === "string") idNode = b.utf8(identifier);
|
|
219
|
+
else idNode = _b.encodeGeneralName(identifier); // the GeneralName alternative of the CHOICE
|
|
220
|
+
return b.sequence([b.raw(privateKeyDer), idNode]);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// POPOPrivKey, the proof an encryption or key-agreement key gives instead of a signature (RFC 4211
|
|
224
|
+
// sec. 4.2 / 4.3). A key that cannot sign has no POPOSigningKey available, so without these arms the
|
|
225
|
+
// only reachable alternative is raVerified, an RA's assertion and not a proof at all. For a toolkit
|
|
226
|
+
// whose signature story is PQC-first that gap lands exactly on ML-KEM enrollment.
|
|
227
|
+
//
|
|
228
|
+
// `subsequentMessage` carries no cryptography: it declares which follow-up exchange will complete the
|
|
229
|
+
// proof, and the completion happens in the enrolling protocol (RFC 9810 sec. 5.2.8.3.2 / .3), not here.
|
|
230
|
+
// `encryptedKey` carries the private key itself to the CA, which is why it is gated twice below.
|
|
231
|
+
function _buildPopoPrivKey(pop, mode, template) {
|
|
232
|
+
var outerTag = POPO_PRIVKEY_TAGS[mode];
|
|
233
|
+
var method = pop && pop.method;
|
|
234
|
+
if (typeof method !== "string") {
|
|
235
|
+
throw _err("crmf/bad-popo", "a " + mode + " proof of possession requires pop.method ('subsequentMessage' or 'encryptedKey')");
|
|
236
|
+
}
|
|
237
|
+
if (_hasOwn(DEPRECATED_POPO_METHOD, method)) {
|
|
238
|
+
throw _err("crmf/bad-popo", "the " + method + " proof of possession is deprecated by RFC 4211 sec. 4.2 in favor of " +
|
|
239
|
+
DEPRECATED_POPO_METHOD[method] + "; a reader still accepts it, a new message should not carry it");
|
|
240
|
+
}
|
|
241
|
+
// sec. 4.2 lists exactly three methods for an encipherment key (the private key provided, a
|
|
242
|
+
// decrypted challenge, an encrypted certificate) and introduces the MAC alternatives only in
|
|
243
|
+
// sec. 4.3, "for keyAgreement (only)". The parser refuses a MAC arm under keyEncipherment; the
|
|
244
|
+
// builder refuses to produce one, so the two directions agree about the same message.
|
|
245
|
+
if (method === "agreeMAC" && mode === "keyEncipherment") {
|
|
246
|
+
throw _err("crmf/bad-popo", "keyEncipherment proof of possession cannot use the agreeMAC alternative -- " +
|
|
247
|
+
"it is defined for keyAgreement only (RFC 4211 sec. 4.2, sec. 4.3)");
|
|
248
|
+
}
|
|
249
|
+
if (method === "agreeMAC") {
|
|
250
|
+
// Conforming, and not built here. It needs the static Diffie-Hellman shared secret of RFC 2875
|
|
251
|
+
// sec. 3, derived between the requester's private key and a CA certificate the requester already
|
|
252
|
+
// holds, a classical key-agreement primitive this toolkit does not otherwise carry. It is
|
|
253
|
+
// reported, not approximated: verifyPop already names an inbound agreeMAC as unverified, and an
|
|
254
|
+
// emitted one nothing here can check would be worse than none.
|
|
255
|
+
throw _err("crmf/unsupported-popo", "the agreeMAC proof of possession is not built by this toolkit: it requires " +
|
|
256
|
+
"the RFC 2875 static Diffie-Hellman shared secret between the requester's key and a CA certificate it " +
|
|
257
|
+
"already holds. Use 'subsequentMessage' (encrCert or challengeResp), which every key type can produce");
|
|
258
|
+
}
|
|
259
|
+
if (method === "subsequentMessage") {
|
|
260
|
+
var which = pop.subsequentMessage;
|
|
261
|
+
if (typeof which !== "string" || !_hasOwn(SUBSEQUENT_MESSAGE, which)) {
|
|
262
|
+
throw _err("crmf/bad-popo", "pop.subsequentMessage must be 'encrCert' or 'challengeResp' -- " +
|
|
263
|
+
"SubsequentMessage ::= INTEGER { encrCert (0), challengeResp (1) } has no other value (RFC 4211 sec. 4.2)");
|
|
264
|
+
}
|
|
265
|
+
// subsequentMessage [1] IMPLICIT SubsequentMessage, inside the EXPLICIT [2]/[3] wrapper.
|
|
266
|
+
return b.explicit(outerTag, b.implicit(1, b.integer(SUBSEQUENT_MESSAGE[which])));
|
|
267
|
+
}
|
|
268
|
+
if (method !== "encryptedKey") {
|
|
269
|
+
throw _err("crmf/bad-popo", "unsupported POPOPrivKey method " + JSON.stringify(method) +
|
|
270
|
+
" (supported: 'subsequentMessage', 'encryptedKey')");
|
|
271
|
+
}
|
|
272
|
+
// encryptedKey is legal under BOTH outer arms, and the asymmetry with agreeMAC above is the
|
|
273
|
+
// specification's own. RFC 4211 sec. 4.3: key-agreement POP "is accomplished by one of four
|
|
274
|
+
// different methods. The first three are identical to those presented above for key encryption
|
|
275
|
+
// keys" -- and the first of sec. 4.2's three is "The private key can be provided to the CA/RA",
|
|
276
|
+
// which is this alternative. Only the fourth, the MAC, is agreement-only. The parser reads
|
|
277
|
+
// encryptedKey under both arms for the same reason, so gating it to keyEncipherment here would put
|
|
278
|
+
// the producer and the reader back into disagreement about one message.
|
|
279
|
+
return _buildEncryptedKeyPop(pop, outerTag, template);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// encryptedKey [4] EnvelopedData. RFC 9810 sec. 5.2.8.3.1: "This method SHALL only be used if archival
|
|
283
|
+
// of the private key is desired." Nothing in a builder can confirm that intent, so it is an explicit
|
|
284
|
+
// opt-in on the raVerified precedent: handing your private key to the CA should take a sentence that
|
|
285
|
+
// says so, not a mistyped method name.
|
|
286
|
+
function _buildEncryptedKeyPop(pop, outerTag, template) {
|
|
287
|
+
if (pop.archive !== true) {
|
|
288
|
+
throw _err("crmf/bad-popo", "an encryptedKey proof of possession sends the requester's private key to the CA, " +
|
|
289
|
+
"and RFC 9810 sec. 5.2.8.3.1 permits it only where archival is intended -- opt in with pop.archive: true");
|
|
290
|
+
}
|
|
291
|
+
if (pop.identifier == null) {
|
|
292
|
+
throw _err("crmf/bad-popo", "an encryptedKey proof of possession requires pop.identifier (a string or a GeneralName): " +
|
|
293
|
+
"RFC 4211 sec. 4.2.1 makes EncKeyWithID.identifier mandatory when the purpose is proving possession, so a " +
|
|
294
|
+
"decrypting agent knows whose key it holds");
|
|
295
|
+
}
|
|
296
|
+
// From here the function owns a plaintext copy of the caller's private key, so every exit clears it.
|
|
297
|
+
// The checks below reject a malformed PKCS#8, an empty recipient list, an AEAD algorithm and a
|
|
298
|
+
// template with no key, and each of those throws synchronously: cleanup attached only to the
|
|
299
|
+
// promise would run on none of them, leaving the copy readable exactly on the paths a caller hits
|
|
300
|
+
// by getting the call wrong. `wipe` tolerates the second buffer not existing yet.
|
|
301
|
+
var privateKeyDer = guard.bytes.snapshot(pop.privateKey, CrmfError, "crmf/bad-input", "pop.privateKey");
|
|
302
|
+
var encKeyWithIdDer = null;
|
|
303
|
+
function wipe() { guard.secret.zeroizeAll([privateKeyDer, encKeyWithIdDer], CrmfError, "crmf/bad-input", "the enclosed private key"); }
|
|
304
|
+
try {
|
|
305
|
+
return _encryptedKeyPopBody();
|
|
306
|
+
} catch (e) { wipe(); throw e; }
|
|
307
|
+
|
|
308
|
+
function _encryptedKeyPopBody() {
|
|
309
|
+
if (!privateKeyDer.length) throw _err("crmf/bad-input", "pop.privateKey must be the requester's PrivateKeyInfo (PKCS#8) DER");
|
|
310
|
+
// PrivateKeyInfo.version MUST be 0 (sec. 4.2.1). These are caller bytes, so the value is read rather
|
|
311
|
+
// than assumed; a v1 OneAsymmetricKey carries a public key the CA did not ask to be told.
|
|
312
|
+
var pkiKids, pkiVersion;
|
|
313
|
+
try { pkiKids = pkix.rootSequenceChildren(asn1.decode(privateKeyDer), 3); }
|
|
314
|
+
catch (e) { throw _err("crmf/bad-input", "pop.privateKey must be a PrivateKeyInfo (PKCS#8) SEQUENCE", e); }
|
|
315
|
+
if (!pkiKids) throw _err("crmf/bad-input", "pop.privateKey must be a PrivateKeyInfo (PKCS#8) SEQUENCE of at least three fields");
|
|
316
|
+
try { pkiVersion = asn1.read.integer(pkiKids[0]); }
|
|
317
|
+
catch (e) { throw _err("crmf/bad-input", "PrivateKeyInfo.version must be an INTEGER (RFC 4211 sec. 4.2.1)", e); }
|
|
318
|
+
if (pkiVersion !== 0n) {
|
|
319
|
+
throw _err("crmf/bad-input", "EncKeyWithID.privateKey must be a PrivateKeyInfo with version 0, got " + pkiVersion + " (RFC 4211 sec. 4.2.1)");
|
|
320
|
+
}
|
|
321
|
+
var recipients = pop.recipients;
|
|
322
|
+
if (!intrinsic.isArray(recipients) || recipients.length === 0) {
|
|
323
|
+
throw _err("crmf/bad-input", "an encryptedKey proof of possession requires pop.recipients (the CA or archive agents to envelope the key to)");
|
|
324
|
+
}
|
|
325
|
+
// The field is typed EnvelopedData, so the content-encryption algorithm has to be one that produces
|
|
326
|
+
// one. An AEAD algorithm yields an AuthEnvelopedData (RFC 5083), a different structure this field
|
|
327
|
+
// cannot carry and the parser will not read. It is refused by name, not emitted for a peer to
|
|
328
|
+
// reject. CBC is the default here for that reason, not as a preference between the two.
|
|
329
|
+
var cea = pop.contentEncryptionAlgorithm || "aes-256-cbc";
|
|
330
|
+
if (typeof cea !== "string") throw _err("crmf/bad-input", "pop.contentEncryptionAlgorithm must be an algorithm name");
|
|
331
|
+
if (/gcm|ccm|chacha/i.test(cea)) {
|
|
332
|
+
throw _err("crmf/bad-popo", "encryptedKey is typed EnvelopedData, and the AEAD algorithm " + cea +
|
|
333
|
+
" produces an AuthEnvelopedData instead (RFC 5083) -- choose a CBC content-encryption algorithm");
|
|
334
|
+
}
|
|
335
|
+
// RFC 4211 sec. 4.2: encryptedKey "contains the encrypted private key MATCHING THE PUBLIC KEY for
|
|
336
|
+
// which the certificate is to be issued". Without this the arm proves nothing it claims to: a
|
|
337
|
+
// requester could ask for a certificate over one key while enclosing an unrelated private key, and
|
|
338
|
+
// the CA would issue on a proof of possession of something it never certified. The signature arm
|
|
339
|
+
// self-verifies before returning for the same reason; this is that check for this arm.
|
|
340
|
+
if (template.spki == null) {
|
|
341
|
+
throw _err("crmf/bad-input", "an encryptedKey proof of possession requires certTemplate.publicKey -- " +
|
|
342
|
+
"the proof is that the enclosed private key is the one being certified (RFC 4211 sec. 4.2)");
|
|
343
|
+
}
|
|
344
|
+
// The second plaintext copy: the EncKeyWithID that carries the key into the message. This arm is
|
|
345
|
+
// the one place in the toolkit that deliberately holds a caller's private key in the clear, so both
|
|
346
|
+
// copies are cleared on every exit. The caller's own buffer stays intact, since it was copied.
|
|
347
|
+
encKeyWithIdDer = _encKeyWithID(privateKeyDer, pop.identifier);
|
|
348
|
+
// Composed, never hand-rolled: the one CMS producer emits the RecipientInfos, and the content type is
|
|
349
|
+
// the id-ct-encKeyWithID sec. 4.2 requires -- the same OID the parser independently checks for.
|
|
350
|
+
return _assertEnclosedKeyMatches(privateKeyDer, template.spki).then(function () {
|
|
351
|
+
// The CMS producer raises its own cms/* faults for a malformed recipient or an algorithm it does
|
|
352
|
+
// not carry. Those are this module's inputs, reached through a composition the caller cannot see,
|
|
353
|
+
// so they are re-raised in this module's namespace with the original kept as the cause -- the
|
|
354
|
+
// same translation the CRMF parser performs where it composes the CMS reader.
|
|
355
|
+
return cmsEncrypt.encrypt(encKeyWithIdDer, recipients, { contentType: "encKeyWithID", contentEncryptionAlgorithm: cea })
|
|
356
|
+
.then(null, function (e) {
|
|
357
|
+
if (e instanceof CrmfError) throw e;
|
|
358
|
+
throw _err("crmf/bad-input", "the encryptedKey proof of possession could not envelope the private key to " +
|
|
359
|
+
"pop.recipients: " + ((e && e.message) || e), e);
|
|
360
|
+
});
|
|
361
|
+
})
|
|
362
|
+
.then(function (contentInfo) {
|
|
363
|
+
// cms.encrypt returns a ContentInfo wrapping the EnvelopedData; the field carries the bare
|
|
364
|
+
// structure. Read back through the shipped walker rather than indexing into our own output, so a
|
|
365
|
+
// change in what encrypt emits is a typed error here and not a malformed message on the wire.
|
|
366
|
+
var inner = _bareEnvelopedData(contentInfo);
|
|
367
|
+
return b.explicit(outerTag, b.implicit(4, inner));
|
|
368
|
+
})
|
|
369
|
+
.then(function (out) { wipe(); return out; }, function (e) { wipe(); throw e; });
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Is the private key about to be enclosed the one the template asks to have certified? Derived through
|
|
374
|
+
// the toolkit's own key engine, not a second re-implementation, so every algorithm it drives (RSA, EC,
|
|
375
|
+
// the Edwards curves, ML-KEM, ML-DSA) answers the same way. Both values are public, so this is an
|
|
376
|
+
// ordinary comparison and not a secret one.
|
|
377
|
+
// nosemgrep: pki-non-constant-time-secret-compare
|
|
378
|
+
function _assertEnclosedKeyMatches(privateKeyDer, templateSpki) {
|
|
379
|
+
return Promise.resolve()
|
|
380
|
+
.then(function () { return key.publicFromPrivate(privateKeyDer); })
|
|
381
|
+
.then(function (derivedSpki) {
|
|
382
|
+
var derived = guard.bytes.view(derivedSpki, CrmfError, "crmf/bad-input", "the derived public key");
|
|
383
|
+
var wanted = guard.bytes.view(templateSpki, CrmfError, "crmf/bad-input", "certTemplate.publicKey");
|
|
384
|
+
if (!_b.samePublicKey(derived, wanted)) {
|
|
385
|
+
throw _err("crmf/bad-popo", "the private key enclosed in the encryptedKey proof of possession is not the " +
|
|
386
|
+
"private half of certTemplate.publicKey, so it proves possession of a key this request does not ask to " +
|
|
387
|
+
"have certified (RFC 4211 sec. 4.2)");
|
|
388
|
+
}
|
|
389
|
+
}, function (e) {
|
|
390
|
+
if (e instanceof CrmfError) throw e;
|
|
391
|
+
throw _err("crmf/bad-input", "pop.privateKey could not be read as a private key to check against " +
|
|
392
|
+
"certTemplate.publicKey: " + ((e && e.message) || e), e);
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// The EnvelopedData SEQUENCE inside the ContentInfo cms.encrypt returns, validated on the way out.
|
|
397
|
+
// Every fault here comes from re-reading a composition the caller never sees. The codec's decode and
|
|
398
|
+
// the CMS walker each raise in their own namespace, so the whole body is translated, not the one call
|
|
399
|
+
// that happens to be likeliest. `pki.crmf.build` promises a typed CrmfError, and a promise with an
|
|
400
|
+
// exception for the paths nobody expected to take is the shape that makes it untrue exactly once.
|
|
401
|
+
function _bareEnvelopedData(contentInfoDer) {
|
|
402
|
+
try {
|
|
403
|
+
var kids = pkix.rootSequenceChildren(asn1.decode(contentInfoDer), 2, 2);
|
|
404
|
+
var wrapper = kids && kids[1];
|
|
405
|
+
if (!wrapper || wrapper.tagClass !== "context" || wrapper.tagNumber !== 0 || !wrapper.children || wrapper.children.length !== 1) {
|
|
406
|
+
throw _err("crmf/bad-popo", "the enveloped private key is not a ContentInfo carrying one [0] EXPLICIT content");
|
|
407
|
+
}
|
|
408
|
+
var env = wrapper.children[0];
|
|
409
|
+
cms.walkEnvelopedData(env); // typed cms/* on anything that is not a well-formed EnvelopedData
|
|
410
|
+
return b.raw(env.bytes);
|
|
411
|
+
} catch (e) {
|
|
412
|
+
if (e instanceof CrmfError) throw e;
|
|
413
|
+
throw _err("crmf/bad-popo", "the enveloped private key could not be read back as an EnvelopedData: " +
|
|
414
|
+
((e && e.message) || e), e);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
// Check spec.pop against the arm the caller selected, never against every arm's fields at once. Every
|
|
419
|
+
// opt-in below is a refusal a caller turns off by naming it, so a field that belongs to a different
|
|
420
|
+
// arm must not read as one this arm accepted and ignored: the caller believes they asked for
|
|
421
|
+
// something the message does not say. An unrecognized arm falls through with only the selector
|
|
422
|
+
// fields allowed, so the error names the arm rather than a field beside it.
|
|
423
|
+
function _assertPopArmKeys(pop, mode) {
|
|
424
|
+
var arm = _hasOwn(POPO_PRIVKEY_TAGS, mode) ? pop.method : mode;
|
|
425
|
+
var allowed = (typeof arm === "string" && _hasOwn(POP_KEYS_BY_ARM, arm))
|
|
426
|
+
? POP_KEYS_BY_ARM[arm]
|
|
427
|
+
: { type: 1, method: 1 };
|
|
428
|
+
guard.identifier.assertKnownKeys(pop, allowed, _err, "crmf/bad-input", function (k) {
|
|
429
|
+
return "spec.pop field " + _stringify(k) + " is not read by a " +
|
|
430
|
+
(typeof arm === "string" ? arm : "proof") + " proof of possession";
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
|
|
164
434
|
// ProofOfPossession. The signature arm (the default when a key is given): when the template carries both
|
|
165
435
|
// subject and publicKey (complete), sign the CertRequest DER and omit poposkInput; otherwise build a
|
|
166
436
|
// POPOSigningKeyInput (authInfo sender [0] GeneralName + the requested publicKey), sign its SEQUENCE, and
|
|
167
437
|
// carry it as poposkInput [0] (RFC 4211 sec. 4.1). raVerified is emitted only on an explicit opt-in.
|
|
168
438
|
function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
|
|
169
439
|
if (pop != null && (typeof pop !== "object" || Buffer.isBuffer(pop))) throw _err("crmf/bad-input", "spec.pop must be an object (e.g. { type: 'signature' } or { type: 'raVerified', raVerified: true })");
|
|
440
|
+
// Every opt-in below is a refusal a caller turns off by naming it, so a misspelled key must not
|
|
441
|
+
// read as an omitted one: `archve: true` silently withholding the archival consent, or `identifer`
|
|
442
|
+
// silently omitting the field RFC 4211 sec. 4.2.1 makes mandatory, would each be a message built
|
|
443
|
+
// on the opposite of what the caller asked for.
|
|
444
|
+
// The arm is resolved BEFORE its fields are checked, because `type` is not the only thing that
|
|
445
|
+
// picks one: a spec that supplies a key and omits `type` selects the signature arm, and checking
|
|
446
|
+
// against an unresolved arm would refuse the `pop.sender` that arm reads.
|
|
170
447
|
var mode = (pop && pop.type) || (signingKey != null ? "signature" : null);
|
|
448
|
+
if (pop != null) _assertPopArmKeys(pop, mode);
|
|
171
449
|
if (mode == null) return null; // no POP requested and no key -> omit popo (an RA supplies it out of band)
|
|
172
450
|
if (mode === "raVerified") {
|
|
173
451
|
if (!(pop && pop.raVerified === true)) throw _err("crmf/bad-popo", "raVerified must be explicitly opted into (pop: { type: 'raVerified', raVerified: true }) -- a requester does not normally assert it (RFC 4211 sec. 4)");
|
|
174
452
|
return b.implicit(0, b.nullValue()); // raVerified [0] IMPLICIT NULL
|
|
175
453
|
}
|
|
176
|
-
if (
|
|
454
|
+
if (_hasOwn(POPO_PRIVKEY_TAGS, mode)) return _buildPopoPrivKey(pop, mode, template);
|
|
455
|
+
if (mode !== "signature") {
|
|
456
|
+
throw _err("crmf/bad-popo", "unsupported proof-of-possession type " + JSON.stringify(mode) +
|
|
457
|
+
" (supported: 'signature', 'raVerified', 'keyEncipherment', 'keyAgreement')");
|
|
458
|
+
}
|
|
177
459
|
if (signingKey == null) throw _err("crmf/bad-input", "a signature proof of possession requires the requester's private key");
|
|
178
460
|
if (template.spki == null) throw _err("crmf/bad-input", "a signature proof of possession requires certTemplate.publicKey");
|
|
179
461
|
var scheme = signScheme.resolveSignScheme(_b.certLikeFromSpki(template.spki), { combinedRsaSig: true, pss: opts.pss, digestAlgorithm: opts.digestAlgorithm }, true, _signE);
|
|
@@ -227,6 +509,27 @@ function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
|
|
|
227
509
|
* PEM block with `opts.pem` (the label is required). Malformed input throws a typed `CrmfError`.
|
|
228
510
|
* Certificate-request-message parsing is `pki.schema.crmf.parse`.
|
|
229
511
|
*
|
|
512
|
+
* A key that cannot sign proves possession another way (RFC 4211 sec. 4.2, 4.3), which is the case an
|
|
513
|
+
* ML-KEM enrollment is in. Set `pop.type` to `keyEncipherment` or `keyAgreement` and pick a `pop.method`:
|
|
514
|
+
*
|
|
515
|
+
* - `subsequentMessage` with `pop.subsequentMessage` of `encrCert` or `challengeResp`. The proof completes
|
|
516
|
+
* over the enrolling protocol's next exchange (RFC 9810 sec. 5.2.8.3.2 / .3), so this message only
|
|
517
|
+
* declares which one. No key material leaves the requester, and every key type can produce it.
|
|
518
|
+
* - `encryptedKey`, which sends the requester's private key to the CA inside a CMS `EnvelopedData` whose
|
|
519
|
+
* content type is `id-ct-encKeyWithID`. RFC 9810 sec. 5.2.8.3.1 permits it only where archival is
|
|
520
|
+
* intended, so it takes `pop.archive: true`; `pop.privateKey` is the PKCS#8 DER, `pop.recipients` the CMS
|
|
521
|
+
* recipients to envelope it to, and `pop.identifier` (a string or GeneralName) is required, because
|
|
522
|
+
* sec. 4.2.1 makes it mandatory whenever the purpose is proving possession -- without it a decrypting
|
|
523
|
+
* agent cannot tell whose key it holds. `pop.contentEncryptionAlgorithm` defaults to `aes-256-cbc`; an
|
|
524
|
+
* AEAD algorithm is refused, since it would produce an `AuthEnvelopedData` this field cannot carry.
|
|
525
|
+
*
|
|
526
|
+
* The two alternatives the specification deprecates in the same breath as defining them, `thisMessage` and
|
|
527
|
+
* `dhMAC`, are refused with their successors named; `pki.schema.crmf.parse` still reads both, since a peer
|
|
528
|
+
* may send one. `agreeMAC` is not built: it needs the RFC 2875 static Diffie-Hellman shared secret between
|
|
529
|
+
* the requester's key and a CA certificate it already holds. Building an `encryptedKey` or `agreeMAC` proof
|
|
530
|
+
* inside `pki.cmp.build` raises the announced protocol version to cmp2021(3), which RFC 9810 sec. 5.2.8.3
|
|
531
|
+
* requires.
|
|
532
|
+
*
|
|
230
533
|
* @opts
|
|
231
534
|
* - `pem` (string) -- return a PEM block with this label instead of DER (e.g. "CERTIFICATE REQUEST MESSAGE").
|
|
232
535
|
* - `pss` (boolean) -- sign an RSA key with RSASSA-PSS instead of PKCS#1 v1.5.
|
package/lib/guard-identifier.js
CHANGED
|
@@ -738,6 +738,41 @@ function _notAnOptionsBag(v) {
|
|
|
738
738
|
// error and says so. A Buffer is excluded because it is an object and is never an options bag --
|
|
739
739
|
// passing one is an argument-order mistake, which is worth naming rather than treating as empty.
|
|
740
740
|
//
|
|
741
|
+
// A built-in exotic whose data lives in internal slots rather than own enumerable keys, recognized by
|
|
742
|
+
// identity so a cross-realm instance is caught too. `util.types` covers each such kind; an array is not
|
|
743
|
+
// among them and is checked separately by the caller.
|
|
744
|
+
function _isBuiltinExotic(v) {
|
|
745
|
+
var t = util.types;
|
|
746
|
+
return t.isDate(v) || t.isMap(v) || t.isSet(v) || t.isWeakMap(v) || t.isWeakSet(v) || t.isRegExp(v) ||
|
|
747
|
+
t.isPromise(v) || t.isArrayBuffer(v) || t.isSharedArrayBuffer(v) || t.isArrayBufferView(v) ||
|
|
748
|
+
t.isBoxedPrimitive(v) || t.isNativeError(v) || t.isArgumentsObject(v) || t.isGeneratorObject(v);
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
// assertPlainRecord(v, E, code, label): v MUST be a plain data record -- a non-null object that is not an
|
|
752
|
+
// array, a Buffer, a Proxy, or a built-in exotic (a Date, Map, Set, boxed primitive, ...). A caller's
|
|
753
|
+
// `typeof v === "object"` gate admits an array or a Date, which then passes assertKnownKeys with no own
|
|
754
|
+
// key to reject and reads as a keyless record -- every field `undefined` and silently defaulted. This
|
|
755
|
+
// door refuses that shape, so a malformed container is a refusal rather than a request assembled from
|
|
756
|
+
// defaults. Composes BEFORE assertKnownKeys, which only
|
|
757
|
+
// judges the keys that are present, not whether the value is a record at all.
|
|
758
|
+
//
|
|
759
|
+
// The prototype is NOT consulted, so a record built by a caller's own class, or one from another realm
|
|
760
|
+
// (a vm context, a worker) whose prototype is a different Object.prototype, is accepted -- it holds its
|
|
761
|
+
// fields as own enumerable properties like any record. Only a built-in exotic that keeps its data in
|
|
762
|
+
// internal slots (a Date, Map, Set, boxed primitive, ...), an array (index-keyed), a Buffer, or a Proxy
|
|
763
|
+
// is refused, each recognized by identity through `util.types` / `Array.isArray` so a cross-realm
|
|
764
|
+
// instance is caught where an `instanceof` or a prototype check would miss it.
|
|
765
|
+
//
|
|
766
|
+
// @enforced-by behavioral -- a plain-record refusal has no single rename-proof code shape worth a
|
|
767
|
+
// tree-wide detector; the RED vectors (an array, a Date, and a Proxy at each routed entry point, and a
|
|
768
|
+
// cross-realm/subclass record that must still be ACCEPTED) are the guard.
|
|
769
|
+
function assertPlainRecord(v, E, code, label) {
|
|
770
|
+
_requireFactory(E);
|
|
771
|
+
if (v === null || typeof v !== "object" || _isNodeBuffer(v) || _isArray(v)) throw E(code, label + " must be a plain object");
|
|
772
|
+
_refuseUnenumerable(v, E, code, label); // a Proxy's reported keys need not match what it answers
|
|
773
|
+
if (_isBuiltinExotic(v)) throw E(code, label + " must be a plain object (a built-in exotic such as a Date or Map is refused)");
|
|
774
|
+
}
|
|
775
|
+
|
|
741
776
|
// @enforced-by behavioral -- the defect is an ordering, which has no rename-proof code shape; the
|
|
742
777
|
// RED vector (a verb handed `false` accepting it as `{}`) is the guard.
|
|
743
778
|
function optionsObject(opts, E, code, label) {
|
|
@@ -856,6 +891,7 @@ function _settle(opts, E, code, label) {
|
|
|
856
891
|
module.exports = intrinsic.freeze({
|
|
857
892
|
assertCanonicalOid: assertCanonicalOid,
|
|
858
893
|
assertKnownKeys: assertKnownKeys,
|
|
894
|
+
assertPlainRecord: assertPlainRecord,
|
|
859
895
|
optionsObject: optionsObject,
|
|
860
896
|
refuseAccessorFields: refuseAccessorFields,
|
|
861
897
|
// Exported so a caller that forwards an options bag copies the same surface this module
|
package/lib/guard-name.js
CHANGED
|
@@ -53,6 +53,12 @@ var _String = String;
|
|
|
53
53
|
// matches, and it is captured for the same reason as the rest: what a guard does is settled before
|
|
54
54
|
// a caller runs, or it is settled by the caller.
|
|
55
55
|
var _isArray = Array.isArray;
|
|
56
|
+
// Byte identity and the shape test that gates it, both captured for the reason above: a
|
|
57
|
+
// distribution-point comparison asks whether two encodings are the same bytes, and an object
|
|
58
|
+
// supplying its own `equals` would be answering that question about itself.
|
|
59
|
+
var _isBuffer = intrinsic.isBuffer;
|
|
60
|
+
var _bufferEquals = intrinsic.bufferEquals;
|
|
61
|
+
var _getOwnPropertyDescriptor = intrinsic.getOwnPropertyDescriptor;
|
|
56
62
|
|
|
57
63
|
// assertNoControlBytes(str, E, code, label) -> str | throws E(code, ...)
|
|
58
64
|
// DirectoryString policy (a DN attribute value): reject NUL and C0 control bytes
|
|
@@ -185,6 +191,89 @@ function dnEqual(rdnsA, rdnsB, E, code, label) {
|
|
|
185
191
|
return true;
|
|
186
192
|
}
|
|
187
193
|
|
|
194
|
+
// dpnCorresponds(a, b, E, code, label) -> boolean. Do two DistributionPointNames name the
|
|
195
|
+
// same CRL distribution point? RFC 5280 sec. 6.3.3(b)(2)(i) asks it of a certificate's
|
|
196
|
+
// DistributionPoint against a shard CRL's issuingDistributionPoint, and RFC 9483 sec. 4.3.4
|
|
197
|
+
// asks it of the dpn a CMP crlUpdate requested against the IDP of the CRL that came back.
|
|
198
|
+
// The answer is "at least one name in common", compared by BYTE-IDENTICAL DER.
|
|
199
|
+
//
|
|
200
|
+
// The comparison key is pinned by sec. 5.2.5: "The identical encoding MUST be used in the
|
|
201
|
+
// distributionPoint fields of the certificate and the CRL". This is therefore the one name
|
|
202
|
+
// comparison in the toolkit that must NOT canonicalize. dnEqual's sec. 7.1 rule applied here
|
|
203
|
+
// would equate two encodings the spec keeps distinct, and a CRL would be accepted as covering
|
|
204
|
+
// a point it was never published for. Whole-set equality is the opposite error: a point
|
|
205
|
+
// published under several names (a URI beside a directoryName) shares one of them with a
|
|
206
|
+
// reference that lists only that one, and demanding all would reject its own CRL.
|
|
207
|
+
//
|
|
208
|
+
// Mixed forms never correspond, with fullName on one side and nameRelativeToCRLIssuer on the
|
|
209
|
+
// other. Deciding that would mean resolving an RDN fragment against a CRL issuer DN this
|
|
210
|
+
// comparison is not given, so the answer is no.
|
|
211
|
+
//
|
|
212
|
+
// @enforced-by behavioral -- the rule's identity is carried by the kind strings, which the
|
|
213
|
+
// pattern walk strips before matching, so it has no rename-proof code shape. The RED vectors
|
|
214
|
+
// in guard-name.test.js and the crlUpdate vectors in cmp-session.test.js are the guard.
|
|
215
|
+
function dpnCorresponds(a, b, E, code, label) {
|
|
216
|
+
var x = _reduceDpn(a, E, code, label), y = _reduceDpn(b, E, code, label);
|
|
217
|
+
if (x.kind !== y.kind) return false;
|
|
218
|
+
if (x.kind === "rdn") return _bufferEquals(x.bytes, y.bytes);
|
|
219
|
+
for (var i = 0; i < x.names.length; i++) {
|
|
220
|
+
for (var j = 0; j < y.names.length; j++) {
|
|
221
|
+
if (_bufferEquals(x.names[i], y.names[j])) return true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// The comparand, checked and REDUCED to the values that were checked. The reason _assertSequence
|
|
228
|
+
// gives applies here in full: a comparison handed the wrong shape must refuse at the call site
|
|
229
|
+
// instead of answering. Reading `names` off a value that has none would run a loop over
|
|
230
|
+
// `i < undefined` zero times and report "no correspondence", which reads as a clean refusal of a
|
|
231
|
+
// CRL and hides that nothing was compared; reading it off a sparse array would walk to
|
|
232
|
+
// Array.prototype for the missing index and compare a name the reference never carried.
|
|
233
|
+
//
|
|
234
|
+
// Every field must be the comparand's OWN DATA property, and its value is taken FROM the property
|
|
235
|
+
// descriptor. That is one reflective read per field, so the value the shape check validated is the
|
|
236
|
+
// value the comparison is answered from: no second read is left for an accessor, or a Proxy trap,
|
|
237
|
+
// to answer differently, and a field the decoder never produces (inherited, or reached through a
|
|
238
|
+
// getter) is refused. Buffers, not caller byte-likes, for the same reason: the comparison is byte
|
|
239
|
+
// identity, and an object answering `equals` or `length` for itself would be deciding the question
|
|
240
|
+
// asked of it.
|
|
241
|
+
function _reduceDpn(d, E, code, label) {
|
|
242
|
+
function refuse(why) { return E(code, "cannot compare " + label + ": " + why); }
|
|
243
|
+
function ownValue(o, k) {
|
|
244
|
+
// FromPropertyDescriptor hands back a fresh ordinary object, so `value` here is the value the
|
|
245
|
+
// object itself reported for that key, including through a Proxy's own-descriptor trap.
|
|
246
|
+
var desc = _getOwnPropertyDescriptor(o, k);
|
|
247
|
+
if (!desc || !_hasOwn(desc, "value")) throw refuse("a distribution-point comparison requires " + k + " to be the comparand's own value (RFC 5280 sec. 6.3.3 compares decoded names)");
|
|
248
|
+
return desc.value;
|
|
249
|
+
}
|
|
250
|
+
if (!d || typeof d !== "object") {
|
|
251
|
+
throw refuse("a distribution-point comparison requires the decoded DistributionPointName on both sides (kind fullName or rdn)");
|
|
252
|
+
}
|
|
253
|
+
var kind = ownValue(d, "kind");
|
|
254
|
+
if (kind !== "fullName" && kind !== "rdn") throw refuse("a DistributionPointName is fullName or nameRelativeToCRLIssuer, and this is neither");
|
|
255
|
+
if (kind === "rdn") {
|
|
256
|
+
var bytes = ownValue(d, "bytes");
|
|
257
|
+
if (!_isBuffer(bytes)) throw refuse("a nameRelativeToCRLIssuer distribution point must carry its encoded bytes");
|
|
258
|
+
return { kind: kind, bytes: bytes };
|
|
259
|
+
}
|
|
260
|
+
var names = ownValue(d, "names");
|
|
261
|
+
if (!_isArray(names)) throw refuse("a fullName distribution point must carry a sequence of encoded GeneralNames");
|
|
262
|
+
// The length is read once, into the bound the copy runs to. A hole and an inherited element are
|
|
263
|
+
// the same refusal as any other missing own value: indexing past a hole walks to Array.prototype,
|
|
264
|
+
// where a planted numeric property supplies a name the reference never carried.
|
|
265
|
+
var len = names.length;
|
|
266
|
+
// Indexed assignment, never a push: the accumulator must not be built by a method a caller can
|
|
267
|
+
// replace, since what it collects is what the comparison is answered from.
|
|
268
|
+
var taken = [];
|
|
269
|
+
for (var i = 0; i < len; i++) {
|
|
270
|
+
var n = ownValue(names, i);
|
|
271
|
+
if (!_isBuffer(n)) throw refuse("every fullName GeneralName must be its encoded bytes");
|
|
272
|
+
taken[i] = n;
|
|
273
|
+
}
|
|
274
|
+
return { kind: kind, names: taken };
|
|
275
|
+
}
|
|
276
|
+
|
|
188
277
|
// escapeControlBytes(str) -> str. The render-side sibling of assertNoControlBytes:
|
|
189
278
|
// where a name string must still be DISPLAYED best-effort (a human-readable report,
|
|
190
279
|
// a log line) instead of rejected, neutralize every C0 control byte and DEL by
|
|
@@ -316,7 +405,7 @@ function lowerAscii(s) {
|
|
|
316
405
|
// call, so a writable export would let a caller replace the check rather than pass it.
|
|
317
406
|
module.exports = intrinsic.freeze({
|
|
318
407
|
assertNoControlBytes: assertNoControlBytes, assertPrintableIa5: assertPrintableIa5,
|
|
319
|
-
dnEqual: dnEqual, rdnEqual: rdnEqual, emailEqual: emailEqual,
|
|
408
|
+
dnEqual: dnEqual, rdnEqual: rdnEqual, emailEqual: emailEqual, dpnCorresponds: dpnCorresponds,
|
|
320
409
|
escapeControlBytes: escapeControlBytes, escapeDnValue: escapeDnValue,
|
|
321
410
|
lowerAscii: lowerAscii,
|
|
322
411
|
});
|
package/lib/oid.js
CHANGED
|
@@ -234,8 +234,11 @@ var FAMILIES = {
|
|
|
234
234
|
// id-pkip arc (id-pkix 5). id-regCtrl (id-pkip 1) names the control types a
|
|
235
235
|
// CertRequest carries; id-regInfo (id-pkip 2) names the registration-info types.
|
|
236
236
|
// The parser surfaces each control/info value raw, keyed by these names.
|
|
237
|
+
// altCertTemplate (7) and the two keySpec controls algId (11) / rsaKeyLen (12) are the RFC 9480
|
|
238
|
+
// sec. 2.16 additions; a CertReqTemplate response states its key requirements with the latter two.
|
|
237
239
|
regCtrl: { base: [1, 3, 6, 1, 5, 5, 7, 5, 1], of: {
|
|
238
|
-
regToken: 1, authenticator: 2, pkiPublicationInfo: 3, pkiArchiveOptions: 4, oldCertID: 5, protocolEncrKey: 6
|
|
240
|
+
regToken: 1, authenticator: 2, pkiPublicationInfo: 3, pkiArchiveOptions: 4, oldCertID: 5, protocolEncrKey: 6,
|
|
241
|
+
altCertTemplate: 7, algId: 11, rsaKeyLen: 12 } },
|
|
239
242
|
regInfo: { base: [1, 3, 6, 1, 5, 5, 7, 5, 2], of: { utf8Pairs: 1, certReq: 2 } },
|
|
240
243
|
|
|
241
244
|
// PKIX extended key purposes (id-kp, RFC 5280 sec. 4.2.1.12). timeStamping is required,
|
|
@@ -376,6 +379,9 @@ var FAMILIES = {
|
|
|
376
379
|
// SignerInfo carries a MAC-based proof instead. Registered so a decoder can
|
|
377
380
|
// name it and refuse it deliberately.
|
|
378
381
|
"id-alg-noSignature": 2,
|
|
382
|
+
// RSASSA-PSS with SHAKE (RFC 8692): RSA signature algorithms on the PKIX arc.
|
|
383
|
+
// Registered so a consumer can name them -- e.g. the CMP keySpec RSA exclusion.
|
|
384
|
+
"id-RSASSA-PSS-SHAKE128": 30, "id-RSASSA-PSS-SHAKE256": 31,
|
|
379
385
|
"id-alg-xmss-hashsig": 34, "id-alg-xmssmt-hashsig": 35,
|
|
380
386
|
// Composite ML-DSA signature algorithms (draft-ietf-lamps-pq-composite-sigs
|
|
381
387
|
// sec. 6): a PQ ML-DSA paired with a traditional RSA / ECDSA / EdDSA so the
|
|
@@ -432,7 +438,49 @@ var FAMILIES = {
|
|
|
432
438
|
|
|
433
439
|
// OIW Secsig SHA-1 (1.3.14.3.2.26) -- the most common OCSP CertID hashAlgorithm;
|
|
434
440
|
// nistHash covers only SHA-256 and above.
|
|
435
|
-
|
|
441
|
+
// OIW Secsig arc (1.3.14.3.2). id-SHA1 (.26) plus every RSA identifier the OIW SIG assigned under the
|
|
442
|
+
// arc: md4WithRSA (.2), md5WithRSA (.3), md4WithRSAEncryption (.4), rsaSignature (.11, the ISO 9796 RSA
|
|
443
|
+
// scheme), mdc2WithRSASignature (.14), shaWithRSAEncryption (.15), rsaKeyTransport (.22), and the
|
|
444
|
+
// md2/md5/sha1-WithRSASignature set (.24/.25/.29) -- pre-PKCS#1 RSA algorithms still seen in old certs.
|
|
445
|
+
// This is the arc's complete RSA membership per the OIW SIG assignments. Registered so a consumer can
|
|
446
|
+
// name them -- e.g. the CMP keySpec RSA exclusion, which recognizes RSA wherever it is standardized.
|
|
447
|
+
// The arc is shared with DES, DSA, DH, and the hashes, so no prefix isolates RSA; each is named.
|
|
448
|
+
oiwSecsig: { base: [1, 3, 14, 3, 2], of: {
|
|
449
|
+
sha1: 26,
|
|
450
|
+
"md4WithRSA": 2, "md5WithRSA": 3, "md4WithRSAEncryption": 4, "rsaSignature": 11,
|
|
451
|
+
"mdc2WithRSASignature": 14, "shaWithRSAEncryption": 15, "rsaKeyTransport": 22,
|
|
452
|
+
"md2WithRSASignature": 24, "md5WithRSASignature": 25, "sha1WithRSASignature": 29 } },
|
|
453
|
+
|
|
454
|
+
// TeleTrusT (BSI) RSA signature arc (1.3.36.3.3.1) -- RSA signatures with SHA-1 and the RIPEMD family.
|
|
455
|
+
// The arc is RSA-dedicated, so a consumer can classify the whole family by its prefix; one member is
|
|
456
|
+
// registered here so that prefix can be derived from the registry rather than a dotted-decimal literal.
|
|
457
|
+
teletrustRsaSig: { base: [1, 3, 36, 3, 3, 1], of: { "rsaSignatureWithripemd160": 2 } },
|
|
458
|
+
|
|
459
|
+
// X.509 directory encryptionAlgorithm arc (2.5.8.1). id-ea-rsa (.1) is the X.509 `rsa` public-key
|
|
460
|
+
// identifier, distinct from PKCS#1 rsaEncryption. Registered so a consumer can name it -- e.g. the
|
|
461
|
+
// CMP keySpec RSA exclusion, which must recognize RSA wherever it is standardized.
|
|
462
|
+
x509EncAlg: { base: [2, 5, 8, 1], of: { "rsa": 1 } },
|
|
463
|
+
|
|
464
|
+
// X.500 directory signatureAlgorithm arc (2.5.8.3). Its RSA members are sqMod-nWithRSA (.1, the
|
|
465
|
+
// ITU-T X.509 / ISO 9594-8 square-mod-n-with-RSA signature) and mdc2WithRSA (.100, MDC-2-with-RSA).
|
|
466
|
+
// Registered so the CMP keySpec RSA exclusion can name them; the arc is not RSA-dedicated, so each
|
|
467
|
+
// member is named individually rather than prefix-matched.
|
|
468
|
+
x509SigAlg: { base: [2, 5, 8, 3], of: { "sqMod-nWithRSA": 1, "mdc2WithRSA": 100 } },
|
|
469
|
+
|
|
470
|
+
// Chinese GM/T (SCA) SM algorithm arc (1.2.156.10197.1). Its one RSA member is sm3WithRSAEncryption
|
|
471
|
+
// (.504, a PKCS#1 v1.5 RSA signature over an SM3 digest); sm2 (.301), sm3 (.401), and SM2-with-SM3
|
|
472
|
+
// (.501) on the arc are not RSA. Registered so the CMP keySpec RSA exclusion can name it -- the arc is
|
|
473
|
+
// mixed, so the RSA member is named individually rather than prefix-matched.
|
|
474
|
+
smAlg: { base: [1, 2, 156, 10197, 1], of: { "sm3WithRSAEncryption": 504 } },
|
|
475
|
+
|
|
476
|
+
// BSI TR-03110 EAC Terminal Authentication RSA arc (0.4.0.127.0.7.2.2.2.1, id-TA-RSA). Every child is an
|
|
477
|
+
// RSA signature scheme -- PKCS#1 v1.5 or PSS over SHA-1/256/512 -- and the sibling id-TA-ECDSA arc
|
|
478
|
+
// (0.4.0.127.0.7.2.2.2.2) is EC, so this arc is RSA-dedicated. The members are registered so a consumer
|
|
479
|
+
// can derive the arc prefix from the registry rather than a dotted-decimal literal and classify the
|
|
480
|
+
// whole family, including a variant BSI adds later, by that prefix.
|
|
481
|
+
bsiTaRsa: { base: [0, 4, 0, 127, 0, 7, 2, 2, 2, 1], of: {
|
|
482
|
+
"id-TA-RSA-v1-5-SHA-1": 1, "id-TA-RSA-v1-5-SHA-256": 2, "id-TA-RSA-PSS-SHA-1": 3,
|
|
483
|
+
"id-TA-RSA-PSS-SHA-256": 4, "id-TA-RSA-v1-5-SHA-512": 5, "id-TA-RSA-PSS-SHA-512": 6 } },
|
|
436
484
|
|
|
437
485
|
// NIST AES content-encryption algorithms (arc 2.16.840.1.101.3.4.1) -- the
|
|
438
486
|
// modern PBES2 / CMS content ciphers a PKCS#12 or EnvelopedData names.
|
|
@@ -451,6 +499,11 @@ var FAMILIES = {
|
|
|
451
499
|
// sets .35-.46, each pairing a parameter set with its message-digest hash. The
|
|
452
500
|
// parameters MUST be absent for every one of them (enforced via paramsMustBeAbsent).
|
|
453
501
|
nistSig: { base: [2, 16, 840, 1, 101, 3, 4, 3], of: {
|
|
502
|
+
// RSASSA-PKCS1-v1_5 with SHA-3 (NIST CSOR): RSA signature algorithms on the NIST
|
|
503
|
+
// signature arc, alongside the ML-DSA / SLH-DSA sets. Registered so a consumer can
|
|
504
|
+
// name them -- e.g. the CMP keySpec check that must recognize RSA to exclude it.
|
|
505
|
+
"id-rsassa-pkcs1-v1_5-with-sha3-224": 13, "id-rsassa-pkcs1-v1_5-with-sha3-256": 14,
|
|
506
|
+
"id-rsassa-pkcs1-v1_5-with-sha3-384": 15, "id-rsassa-pkcs1-v1_5-with-sha3-512": 16,
|
|
454
507
|
"id-ml-dsa-44": 17, "id-ml-dsa-65": 18, "id-ml-dsa-87": 19,
|
|
455
508
|
"id-slh-dsa-sha2-128s": 20, "id-slh-dsa-sha2-128f": 21,
|
|
456
509
|
"id-slh-dsa-sha2-192s": 22, "id-slh-dsa-sha2-192f": 23,
|