@blamejs/pki 0.6.13 → 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -1
- package/README.md +1 -1
- package/index.js +1 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +59 -233
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/crl-verify.js
CHANGED
|
@@ -1,22 +1,13 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal -- the single CRL signature-verify seam, shared by pki.path.crlChecker and pki.crl.verify
|
|
6
|
-
// so both route through one signature engine (algorithm-confusion + EdDSA low-order + composite
|
|
7
|
-
// gates) and no second, weaker CRL verifier can drift. The slot, the wiring assertion and the
|
|
8
|
-
// octet-alignment refusal are the shared seam (lib/verify-seam.js). The seam lives in this internal
|
|
9
|
-
// module, not on path-validate's module.exports, which keeps it off the public pki.path surface: it
|
|
10
|
-
// takes the path-internal SubjectPublicKeyInfo bytes, not a documented issuer shape. It is not
|
|
11
|
-
// wired into index.js and is reached only by require.
|
|
4
|
+
// @internal
|
|
12
5
|
|
|
13
6
|
var frameworkError = require("./framework-error");
|
|
14
7
|
var seam = require("./verify-seam");
|
|
15
8
|
|
|
16
9
|
var _seam = seam.makeSeam("crl-verify", frameworkError.CrlError, "crl/bad-input");
|
|
17
10
|
|
|
18
|
-
// Verify a parsed CRL's signature over its raw tbsCertList bytes under the issuer
|
|
19
|
-
// SubjectPublicKeyInfo DER.
|
|
20
11
|
function verifyCrlSignature(crl, spkiBytes) {
|
|
21
12
|
return _seam.verify(crl.signatureAlgorithm, crl.signatureValue, spkiBytes, crl.tbsBytes);
|
|
22
13
|
}
|
package/lib/crmf-sign.js
CHANGED
|
@@ -16,14 +16,6 @@
|
|
|
16
16
|
* @spec RFC 4211
|
|
17
17
|
* @card Build a CRMF CertReqMessages with a signature proof of possession.
|
|
18
18
|
*/
|
|
19
|
-
//
|
|
20
|
-
// RFC 4211 App. B / RFC 5912 sec. 10 are DEFINITIONS IMPLICIT TAGS: a CertTemplate [0]..[9] field tag
|
|
21
|
-
// REPLACES the base universal tag (built via asn1.build.implicit, preserving the primitive/constructed
|
|
22
|
-
// bit); the shipped parser encodes issuer [3] / subject [5] Name IMPLICITLY (the dominant CMP/EST wire
|
|
23
|
-
// form), so the builder does too. The two EXPLICIT exceptions are the OptionalValidity notBefore [0] /
|
|
24
|
-
// notAfter [1] Time (a genuine UTCTime/GeneralizedTime CHOICE). The signature scheme resolves from the
|
|
25
|
-
// requested publicKey through the shared sign-scheme registry; the Name / extension / SPKI encoders and
|
|
26
|
-
// the post-sign self-check are the shared lib/pki-build primitives, bound to the crmf namespace.
|
|
27
19
|
|
|
28
20
|
var asn1 = require("./asn1-der");
|
|
29
21
|
var oid = require("./oid");
|
|
@@ -34,14 +26,12 @@ var pkiBuild = require("./pki-build");
|
|
|
34
26
|
var frameworkError = require("./framework-error");
|
|
35
27
|
var guard = require("./guard-all");
|
|
36
28
|
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
29
|
var _hasOwn = intrinsic.hasOwn;
|
|
40
30
|
var _stringify = intrinsic.stringify;
|
|
41
31
|
var cms = require("./schema-cms");
|
|
42
32
|
var cmsEncrypt = require("./cms-encrypt");
|
|
43
33
|
var key = require("./key");
|
|
44
|
-
require("./path-validate");
|
|
34
|
+
require("./path-validate");
|
|
45
35
|
var crmfVerify = require("./crmf-verify");
|
|
46
36
|
|
|
47
37
|
var CrmfError = frameworkError.CrmfError;
|
|
@@ -58,14 +48,6 @@ var _b = pkiBuild.makeBuilder({
|
|
|
58
48
|
});
|
|
59
49
|
|
|
60
50
|
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
51
|
var POP_KEYS_BY_ARM = {
|
|
70
52
|
raVerified: { type: 1, raVerified: 1 },
|
|
71
53
|
signature: { type: 1, sender: 1 },
|
|
@@ -74,8 +56,6 @@ var POP_KEYS_BY_ARM = {
|
|
|
74
56
|
type: 1, method: 1, privateKey: 1, identifier: 1, recipients: 1, archive: 1,
|
|
75
57
|
contentEncryptionAlgorithm: 1,
|
|
76
58
|
},
|
|
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
59
|
agreeMAC: { type: 1, method: 1 },
|
|
80
60
|
thisMessage: { type: 1, method: 1 },
|
|
81
61
|
dhMAC: { type: 1, method: 1 },
|
|
@@ -83,72 +63,50 @@ var POP_KEYS_BY_ARM = {
|
|
|
83
63
|
var KNOWN_BATCH_KEYS = { messages: 1 };
|
|
84
64
|
var KNOWN_TEMPLATE_KEYS = { version: 1, subject: 1, publicKey: 1, validity: 1, extensions: 1, issuer: 1 };
|
|
85
65
|
var REVOCATION_TEMPLATE_KEYS = { version: 1, subject: 1, publicKey: 1, validity: 1, extensions: 1, issuer: 1, serialNumber: 1 };
|
|
86
|
-
// The controls (RFC 4211 sec. 6) and regInfo (sec. 7) are disjoint AttributeTypeAndValue namespaces:
|
|
87
|
-
// regToken/authenticator/oldCertID/protocolEncrKey are controls and utf8Pairs is regInfo, so the object
|
|
88
|
-
// form validates each key against its own field's registry (a control name is not a valid regInfo, and
|
|
89
|
-
// vice versa). The key name equals the registered OID name; complex values (pkiPublicationInfo, certReq)
|
|
90
|
-
// ride the pre-encoded escape hatch. Each entry is the value encoder for that OID.
|
|
91
66
|
function _ctlUtf8(v) { return b.utf8(String(v)); }
|
|
92
67
|
function _ctlSpki(v) { var k = _b.reqDer(v, "protocolEncrKey (an SPKI DER)"); _b.assertValidSpki(k, "protocolEncrKey"); return b.raw(k); }
|
|
93
68
|
var CONTROL_VALUE = { regToken: _ctlUtf8, authenticator: _ctlUtf8, oldCertID: function (v) { return _encodeCertId(v); }, protocolEncrKey: _ctlSpki };
|
|
94
69
|
var REGINFO_VALUE = { utf8Pairs: _ctlUtf8 };
|
|
95
70
|
|
|
96
|
-
// ---- CertTemplate + POP structural encoders (byte-exact inverses of schema-crmf.js) ----
|
|
97
71
|
|
|
98
|
-
// OptionalValidity [4] IMPLICIT SEQUENCE { notBefore [0] EXPLICIT Time, notAfter [1] EXPLICIT Time } --
|
|
99
|
-
// at least one present (RFC 4211 sec. 5). Time is a CHOICE so [0]/[1] are EXPLICIT.
|
|
100
72
|
function _encodeOptionalValidity(validity) {
|
|
101
73
|
if (!validity || typeof validity !== "object" || Buffer.isBuffer(validity)) throw _err("crmf/bad-validity", "validity must be an object { notBefore?, notAfter? }");
|
|
102
74
|
var nb = validity.notBefore, na = validity.notAfter;
|
|
103
75
|
if (nb == null && na == null) throw _err("crmf/bad-validity", "validity must contain notBefore or notAfter (RFC 4211 sec. 5)");
|
|
104
76
|
var parts = [];
|
|
105
|
-
// timeDer validates each instant (guard.time.assertValid throws on an Invalid Date) before the
|
|
106
|
-
// inverted-window comparison, so getTime() below cannot be NaN.
|
|
107
77
|
if (nb != null) parts.push(b.explicit(0, _b.timeDer(nb, "validity notBefore")));
|
|
108
78
|
if (na != null) parts.push(b.explicit(1, _b.timeDer(na, "validity notAfter")));
|
|
109
79
|
// allow:nan-date-comparison-unguarded -- both instants passed timeDer's guard.time.assertValid above.
|
|
110
80
|
if (nb != null && na != null && guard.time.instantOf(nb) > guard.time.instantOf(na)) throw _err("crmf/bad-validity", "notBefore must not be after notAfter");
|
|
111
81
|
return b.implicit(4, b.sequence(parts));
|
|
112
82
|
}
|
|
113
|
-
// CertTemplate ::= SEQUENCE { [0..9] all IMPLICIT OPTIONAL }. A request omits serialNumber [1] / signingAlg
|
|
114
|
-
// [2] / issuerUID [7] / subjectUID [8] (CA-assigned or deprecated, RFC 4211 sec. 5), and the builder never
|
|
115
|
-
// emits them. Fields are emitted in ascending tag order.
|
|
116
83
|
function _encodeCertTemplate(tpl, opts) {
|
|
117
84
|
if (!tpl || typeof tpl !== "object" || Buffer.isBuffer(tpl)) throw _err("crmf/bad-cert-template", "certTemplate must be an object");
|
|
118
|
-
// A request template omits serialNumber (CA-assigned, RFC 4211 sec. 5); a revocation template (CMP rr,
|
|
119
|
-
// RFC 9810 sec. 5.3.9) carries serialNumber [1] to name the certificate to revoke, allowed only then.
|
|
120
85
|
var allowed = (opts && opts.revocation) ? REVOCATION_TEMPLATE_KEYS : KNOWN_TEMPLATE_KEYS;
|
|
121
86
|
guard.identifier.assertKnownKeys(tpl, allowed, _err, "crmf/bad-input", "unknown certTemplate field ");
|
|
122
87
|
var fields = [];
|
|
123
88
|
if (tpl.version != null) {
|
|
124
89
|
if (tpl.version !== 2) throw _err("crmf/bad-version", "certTemplate version MUST be 2 (v3) if supplied (RFC 4211 sec. 5)");
|
|
125
|
-
fields.push(b.implicit(0, b.integer(2n)));
|
|
90
|
+
fields.push(b.implicit(0, b.integer(2n)));
|
|
126
91
|
}
|
|
127
|
-
if (tpl.serialNumber != null) fields.push(b.implicit(1, _b.serialInteger(tpl.serialNumber)));
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
if (tpl.issuer != null) fields.push(b.explicit(3, _b.encodeName(tpl.issuer))); // issuer [3] EXPLICIT
|
|
132
|
-
if (tpl.validity != null) fields.push(_encodeOptionalValidity(tpl.validity)); // validity [4] IMPLICIT
|
|
133
|
-
if (tpl.subject != null) fields.push(b.explicit(5, _b.encodeName(tpl.subject))); // subject [5] EXPLICIT
|
|
92
|
+
if (tpl.serialNumber != null) fields.push(b.implicit(1, _b.serialInteger(tpl.serialNumber)));
|
|
93
|
+
if (tpl.issuer != null) fields.push(b.explicit(3, _b.encodeName(tpl.issuer)));
|
|
94
|
+
if (tpl.validity != null) fields.push(_encodeOptionalValidity(tpl.validity));
|
|
95
|
+
if (tpl.subject != null) fields.push(b.explicit(5, _b.encodeName(tpl.subject)));
|
|
134
96
|
var spki = null;
|
|
135
97
|
if (tpl.publicKey != null) {
|
|
136
98
|
spki = _b.reqDer(tpl.publicKey, "certTemplate.publicKey (the SPKI DER of the requested key)");
|
|
137
99
|
_b.assertValidSpki(spki, "certTemplate.publicKey");
|
|
138
|
-
fields.push(b.implicit(6, spki));
|
|
100
|
+
fields.push(b.implicit(6, spki));
|
|
139
101
|
}
|
|
140
|
-
if (tpl.extensions != null) fields.push(b.implicit(9, _b.requestedExtensions(tpl.extensions, spki)));
|
|
102
|
+
if (tpl.extensions != null) fields.push(b.implicit(9, _b.requestedExtensions(tpl.extensions, spki)));
|
|
141
103
|
return { der: b.sequence(fields), spki: spki, complete: tpl.subject != null && tpl.publicKey != null };
|
|
142
104
|
}
|
|
143
105
|
|
|
144
|
-
// Controls / regInfo ::= SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue { type OID, value ANY }. Object
|
|
145
|
-
// form maps recognized names to typed value encoders; any other (or a pre-encoded AttributeTypeAndValue
|
|
146
|
-
// DER array) rides the escape hatch, shape-validated.
|
|
147
106
|
function _buildAttrTypeAndValues(spec, code, label, valueMap) {
|
|
148
107
|
if (Array.isArray(spec)) {
|
|
149
108
|
if (!spec.length) throw _err(code, label + " must carry at least one entry");
|
|
150
109
|
var seenA = {};
|
|
151
|
-
// Density before the map, which would otherwise skip a hole into b.sequence as a native error.
|
|
152
110
|
return b.sequence(pkiBuild.reqDenseArray(spec, label, _err, code).map(function (e, i) {
|
|
153
111
|
var der = _b.reqDer(e, label + " [" + i + "]");
|
|
154
112
|
var n;
|
|
@@ -176,9 +134,6 @@ function _buildAttrTypeAndValues(spec, code, label, valueMap) {
|
|
|
176
134
|
if (!out.length) throw _err(code, label + " must carry at least one entry");
|
|
177
135
|
return b.sequence(out);
|
|
178
136
|
}
|
|
179
|
-
// certReqId is a signed INTEGER, value UNCONSTRAINED (the RFC 9483 -1 sentinel and 0 are both legal). A
|
|
180
|
-
// number must be a safe integer (a fractional or > 2^53 number loses precision through BigInt); a BigInt
|
|
181
|
-
// or a decimal / 0x-hex string carries an arbitrary value.
|
|
182
137
|
function _certReqId(v) {
|
|
183
138
|
if (v == null) return 0n;
|
|
184
139
|
if (typeof v === "bigint") return v;
|
|
@@ -186,49 +141,22 @@ function _certReqId(v) {
|
|
|
186
141
|
if (typeof v === "string") { try { return BigInt(v); } catch (e) { throw _err("crmf/bad-input", "certReqId string must be a decimal or 0x-hex integer", e); } }
|
|
187
142
|
throw _err("crmf/bad-input", "certReqId must be a BigInt, a safe integer, or a string");
|
|
188
143
|
}
|
|
189
|
-
// CertId ::= SEQUENCE { issuer GeneralName, serialNumber INTEGER } (RFC 4211 sec. 6.4, oldCertID value).
|
|
190
144
|
function _encodeCertId(id) {
|
|
191
145
|
if (!id || typeof id !== "object" || id.issuer == null || id.serialNumber == null) throw _err("crmf/bad-input", "oldCertID must be { issuer: GeneralName, serialNumber }");
|
|
192
146
|
return b.sequence([_b.encodeGeneralName(id.issuer), _b.serialInteger(id.serialNumber)]);
|
|
193
147
|
}
|
|
194
148
|
|
|
195
|
-
// The two ProofOfPossession arms a key that cannot sign has to use: keyEncipherment [2] and
|
|
196
|
-
// keyAgreement [3], each carrying a POPOPrivKey. Both field tags are EXPLICIT, because POPOPrivKey is
|
|
197
|
-
// itself a CHOICE and X.680 sec. 31.2.7 forbids implicit tagging of one. The parser states and
|
|
198
|
-
// enforces the same rule (schema-crmf.js), so an implicit emission here would disagree with the
|
|
199
|
-
// module's own reader.
|
|
200
149
|
var POPO_PRIVKEY_TAGS = { keyEncipherment: 2, keyAgreement: 3 };
|
|
201
|
-
// SubsequentMessage ::= INTEGER { encrCert (0), challengeResp (1) } (RFC 4211 sec. 4.2).
|
|
202
150
|
var SUBSEQUENT_MESSAGE = { encrCert: 0n, challengeResp: 1n };
|
|
203
|
-
// The specification deprecates two of the five alternatives in the same breath as defining them:
|
|
204
|
-
// thisMessage "has been deprecated in favor of encryptedKey", dhMAC "is deprecated in favor of the
|
|
205
|
-
// agreeMAC field" (sec. 4.2), and RFC 9810 sec. 5.2.8.3 restates both as `-- deprecated`. A reader
|
|
206
|
-
// still has to accept them, since a peer may send one; a producer choosing to emit one today is
|
|
207
|
-
// choosing the superseded encoding, so the builder names the successor instead of emitting it.
|
|
208
151
|
var DEPRECATED_POPO_METHOD = { thisMessage: "encryptedKey", dhMAC: "agreeMAC" };
|
|
209
152
|
|
|
210
|
-
// EncKeyWithID ::= SEQUENCE { privateKey PrivateKeyInfo,
|
|
211
|
-
// identifier CHOICE { string UTF8String, generalName GeneralName } OPTIONAL }
|
|
212
|
-
// (RFC 4211 sec. 4.2.1). `identifier` is marked OPTIONAL in the module and then made mandatory by the
|
|
213
|
-
// prose for this use: "This field MUST be present if the purpose is to prove possession of the private
|
|
214
|
-
// key." The reason is stated there too. Without it an escrow agent decrypts a key without knowing
|
|
215
|
-
// whose it is, so an attacker can wrap someone else's intercepted key in a request of their own and
|
|
216
|
-
// then ask for it back. Building a POP is that first purpose, so the caller supplies one.
|
|
217
153
|
function _encKeyWithID(privateKeyDer, identifier) {
|
|
218
154
|
var idNode;
|
|
219
155
|
if (typeof identifier === "string") idNode = b.utf8(identifier);
|
|
220
|
-
else idNode = _b.encodeGeneralName(identifier);
|
|
156
|
+
else idNode = _b.encodeGeneralName(identifier);
|
|
221
157
|
return b.sequence([b.raw(privateKeyDer), idNode]);
|
|
222
158
|
}
|
|
223
159
|
|
|
224
|
-
// POPOPrivKey, the proof an encryption or key-agreement key gives instead of a signature (RFC 4211
|
|
225
|
-
// sec. 4.2 / 4.3). A key that cannot sign has no POPOSigningKey available, so without these arms the
|
|
226
|
-
// only reachable alternative is raVerified, an RA's assertion and not a proof at all. For a toolkit
|
|
227
|
-
// whose signature story is PQC-first that gap lands exactly on ML-KEM enrollment.
|
|
228
|
-
//
|
|
229
|
-
// `subsequentMessage` carries no cryptography: it declares which follow-up exchange will complete the
|
|
230
|
-
// proof, and the completion happens in the enrolling protocol (RFC 9810 sec. 5.2.8.3.2 / .3), not here.
|
|
231
|
-
// `encryptedKey` carries the private key itself to the CA, which is why it is gated twice below.
|
|
232
160
|
function _buildPopoPrivKey(pop, mode, template) {
|
|
233
161
|
var outerTag = POPO_PRIVKEY_TAGS[mode];
|
|
234
162
|
var method = pop && pop.method;
|
|
@@ -239,20 +167,11 @@ function _buildPopoPrivKey(pop, mode, template) {
|
|
|
239
167
|
throw _err("crmf/bad-popo", "the " + method + " proof of possession is deprecated by RFC 4211 sec. 4.2 in favor of " +
|
|
240
168
|
DEPRECATED_POPO_METHOD[method] + "; a reader still accepts it, a new message should not carry it");
|
|
241
169
|
}
|
|
242
|
-
// sec. 4.2 lists exactly three methods for an encipherment key (the private key provided, a
|
|
243
|
-
// decrypted challenge, an encrypted certificate) and introduces the MAC alternatives only in
|
|
244
|
-
// sec. 4.3, "for keyAgreement (only)". The parser refuses a MAC arm under keyEncipherment; the
|
|
245
|
-
// builder refuses to produce one, so the two directions agree about the same message.
|
|
246
170
|
if (method === "agreeMAC" && mode === "keyEncipherment") {
|
|
247
171
|
throw _err("crmf/bad-popo", "keyEncipherment proof of possession cannot use the agreeMAC alternative -- " +
|
|
248
172
|
"it is defined for keyAgreement only (RFC 4211 sec. 4.2, sec. 4.3)");
|
|
249
173
|
}
|
|
250
174
|
if (method === "agreeMAC") {
|
|
251
|
-
// Conforming, and not built here. It needs the static Diffie-Hellman shared secret of RFC 2875
|
|
252
|
-
// sec. 3, derived between the requester's private key and a CA certificate the requester already
|
|
253
|
-
// holds, a classical key-agreement primitive this toolkit does not otherwise carry. It is
|
|
254
|
-
// reported, not approximated: verifyPop already names an inbound agreeMAC as unverified, and an
|
|
255
|
-
// emitted one nothing here can check would be worse than none.
|
|
256
175
|
throw _err("crmf/unsupported-popo", "the agreeMAC proof of possession is not built by this toolkit: it requires " +
|
|
257
176
|
"the RFC 2875 static Diffie-Hellman shared secret between the requester's key and a CA certificate it " +
|
|
258
177
|
"already holds. Use 'subsequentMessage' (encrCert or challengeResp), which every key type can produce");
|
|
@@ -263,27 +182,15 @@ function _buildPopoPrivKey(pop, mode, template) {
|
|
|
263
182
|
throw _err("crmf/bad-popo", "pop.subsequentMessage must be 'encrCert' or 'challengeResp' -- " +
|
|
264
183
|
"SubsequentMessage ::= INTEGER { encrCert (0), challengeResp (1) } has no other value (RFC 4211 sec. 4.2)");
|
|
265
184
|
}
|
|
266
|
-
// subsequentMessage [1] IMPLICIT SubsequentMessage, inside the EXPLICIT [2]/[3] wrapper.
|
|
267
185
|
return b.explicit(outerTag, b.implicit(1, b.integer(SUBSEQUENT_MESSAGE[which])));
|
|
268
186
|
}
|
|
269
187
|
if (method !== "encryptedKey") {
|
|
270
188
|
throw _err("crmf/bad-popo", "unsupported POPOPrivKey method " + JSON.stringify(method) +
|
|
271
189
|
" (supported: 'subsequentMessage', 'encryptedKey')");
|
|
272
190
|
}
|
|
273
|
-
// encryptedKey is legal under BOTH outer arms, and the asymmetry with agreeMAC above is the
|
|
274
|
-
// specification's own. RFC 4211 sec. 4.3: key-agreement POP "is accomplished by one of four
|
|
275
|
-
// different methods. The first three are identical to those presented above for key encryption
|
|
276
|
-
// keys" -- and the first of sec. 4.2's three is "The private key can be provided to the CA/RA",
|
|
277
|
-
// which is this alternative. Only the fourth, the MAC, is agreement-only. The parser reads
|
|
278
|
-
// encryptedKey under both arms for the same reason, so gating it to keyEncipherment here would put
|
|
279
|
-
// the producer and the reader back into disagreement about one message.
|
|
280
191
|
return _buildEncryptedKeyPop(pop, outerTag, template);
|
|
281
192
|
}
|
|
282
193
|
|
|
283
|
-
// encryptedKey [4] EnvelopedData. RFC 9810 sec. 5.2.8.3.1: "This method SHALL only be used if archival
|
|
284
|
-
// of the private key is desired." Nothing in a builder can confirm that intent, so it is an explicit
|
|
285
|
-
// opt-in on the raVerified precedent: handing your private key to the CA should take a sentence that
|
|
286
|
-
// says so, not a mistyped method name.
|
|
287
194
|
function _buildEncryptedKeyPop(pop, outerTag, template) {
|
|
288
195
|
if (pop.archive !== true) {
|
|
289
196
|
throw _err("crmf/bad-popo", "an encryptedKey proof of possession sends the requester's private key to the CA, " +
|
|
@@ -294,11 +201,6 @@ function _buildEncryptedKeyPop(pop, outerTag, template) {
|
|
|
294
201
|
"RFC 4211 sec. 4.2.1 makes EncKeyWithID.identifier mandatory when the purpose is proving possession, so a " +
|
|
295
202
|
"decrypting agent knows whose key it holds");
|
|
296
203
|
}
|
|
297
|
-
// From here the function owns a plaintext copy of the caller's private key, so every exit clears it.
|
|
298
|
-
// The checks below reject a malformed PKCS#8, an empty recipient list, an AEAD algorithm and a
|
|
299
|
-
// template with no key, and each of those throws synchronously: cleanup attached only to the
|
|
300
|
-
// promise would run on none of them, leaving the copy readable exactly on the paths a caller hits
|
|
301
|
-
// by getting the call wrong. `wipe` tolerates the second buffer not existing yet.
|
|
302
204
|
var privateKeyDer = guard.bytes.snapshot(pop.privateKey, CrmfError, "crmf/bad-input", "pop.privateKey");
|
|
303
205
|
var encKeyWithIdDer = null;
|
|
304
206
|
function wipe() { guard.secret.zeroizeAll([privateKeyDer, encKeyWithIdDer], CrmfError, "crmf/bad-input", "the enclosed private key"); }
|
|
@@ -308,8 +210,6 @@ function _buildEncryptedKeyPop(pop, outerTag, template) {
|
|
|
308
210
|
|
|
309
211
|
function _encryptedKeyPopBody() {
|
|
310
212
|
if (!privateKeyDer.length) throw _err("crmf/bad-input", "pop.privateKey must be the requester's PrivateKeyInfo (PKCS#8) DER");
|
|
311
|
-
// PrivateKeyInfo.version MUST be 0 (sec. 4.2.1). These are caller bytes, so the value is read rather
|
|
312
|
-
// than assumed; a v1 OneAsymmetricKey carries a public key the CA did not ask to be told.
|
|
313
213
|
var pkiKids, pkiVersion;
|
|
314
214
|
try { pkiKids = pkix.rootSequenceChildren(asn1.decode(privateKeyDer), 3); }
|
|
315
215
|
catch (e) { throw _err("crmf/bad-input", "pop.privateKey must be a PrivateKeyInfo (PKCS#8) SEQUENCE", e); }
|
|
@@ -323,37 +223,19 @@ function _buildEncryptedKeyPop(pop, outerTag, template) {
|
|
|
323
223
|
if (!intrinsic.isArray(recipients) || recipients.length === 0) {
|
|
324
224
|
throw _err("crmf/bad-input", "an encryptedKey proof of possession requires pop.recipients (the CA or archive agents to envelope the key to)");
|
|
325
225
|
}
|
|
326
|
-
// The field is typed EnvelopedData, so the content-encryption algorithm has to be one that produces
|
|
327
|
-
// one. An AEAD algorithm yields an AuthEnvelopedData (RFC 5083), a different structure this field
|
|
328
|
-
// cannot carry and the parser will not read. It is refused by name, not emitted for a peer to
|
|
329
|
-
// reject. CBC is the default here for that reason, not as a preference between the two.
|
|
330
226
|
var cea = pop.contentEncryptionAlgorithm || "aes-256-cbc";
|
|
331
227
|
if (typeof cea !== "string") throw _err("crmf/bad-input", "pop.contentEncryptionAlgorithm must be an algorithm name");
|
|
332
|
-
var _ceaLc = intrinsic.toLowerCase(cea);
|
|
228
|
+
var _ceaLc = intrinsic.toLowerCase(cea);
|
|
333
229
|
if (intrinsic.stringIndexOf(_ceaLc, "gcm") !== -1 || intrinsic.stringIndexOf(_ceaLc, "ccm") !== -1 || intrinsic.stringIndexOf(_ceaLc, "chacha") !== -1) {
|
|
334
230
|
throw _err("crmf/bad-popo", "encryptedKey is typed EnvelopedData, and the AEAD algorithm " + cea +
|
|
335
231
|
" produces an AuthEnvelopedData instead (RFC 5083) -- choose a CBC content-encryption algorithm");
|
|
336
232
|
}
|
|
337
|
-
// RFC 4211 sec. 4.2: encryptedKey "contains the encrypted private key MATCHING THE PUBLIC KEY for
|
|
338
|
-
// which the certificate is to be issued". Without this the arm proves nothing it claims to: a
|
|
339
|
-
// requester could ask for a certificate over one key while enclosing an unrelated private key, and
|
|
340
|
-
// the CA would issue on a proof of possession of something it never certified. The signature arm
|
|
341
|
-
// self-verifies before returning for the same reason; this is that check for this arm.
|
|
342
233
|
if (template.spki == null) {
|
|
343
234
|
throw _err("crmf/bad-input", "an encryptedKey proof of possession requires certTemplate.publicKey -- " +
|
|
344
235
|
"the proof is that the enclosed private key is the one being certified (RFC 4211 sec. 4.2)");
|
|
345
236
|
}
|
|
346
|
-
// The second plaintext copy: the EncKeyWithID that carries the key into the message. This arm is
|
|
347
|
-
// the one place in the toolkit that deliberately holds a caller's private key in the clear, so both
|
|
348
|
-
// copies are cleared on every exit. The caller's own buffer stays intact, since it was copied.
|
|
349
237
|
encKeyWithIdDer = _encKeyWithID(privateKeyDer, pop.identifier);
|
|
350
|
-
// Composed, never hand-rolled: the one CMS producer emits the RecipientInfos, and the content type is
|
|
351
|
-
// the id-ct-encKeyWithID sec. 4.2 requires -- the same OID the parser independently checks for.
|
|
352
238
|
return _assertEnclosedKeyMatches(privateKeyDer, template.spki).then(function () {
|
|
353
|
-
// The CMS producer raises its own cms/* faults for a malformed recipient or an algorithm it does
|
|
354
|
-
// not carry. Those are this module's inputs, reached through a composition the caller cannot see,
|
|
355
|
-
// so they are re-raised in this module's namespace with the original kept as the cause -- the
|
|
356
|
-
// same translation the CRMF parser performs where it composes the CMS reader.
|
|
357
239
|
return cmsEncrypt.encrypt(encKeyWithIdDer, recipients, { contentType: "encKeyWithID", contentEncryptionAlgorithm: cea })
|
|
358
240
|
.then(null, function (e) {
|
|
359
241
|
if (e instanceof CrmfError) throw e;
|
|
@@ -362,9 +244,6 @@ function _buildEncryptedKeyPop(pop, outerTag, template) {
|
|
|
362
244
|
});
|
|
363
245
|
})
|
|
364
246
|
.then(function (contentInfo) {
|
|
365
|
-
// cms.encrypt returns a ContentInfo wrapping the EnvelopedData; the field carries the bare
|
|
366
|
-
// structure. Read back through the shipped walker rather than indexing into our own output, so a
|
|
367
|
-
// change in what encrypt emits is a typed error here and not a malformed message on the wire.
|
|
368
247
|
var inner = _bareEnvelopedData(contentInfo);
|
|
369
248
|
return b.explicit(outerTag, b.implicit(4, inner));
|
|
370
249
|
})
|
|
@@ -372,11 +251,6 @@ function _buildEncryptedKeyPop(pop, outerTag, template) {
|
|
|
372
251
|
}
|
|
373
252
|
}
|
|
374
253
|
|
|
375
|
-
// Is the private key about to be enclosed the one the template asks to have certified? Derived through
|
|
376
|
-
// the toolkit's own key engine, not a second re-implementation, so every algorithm it drives (RSA, EC,
|
|
377
|
-
// the Edwards curves, ML-KEM, ML-DSA) answers the same way. Both values are public, so this is an
|
|
378
|
-
// ordinary comparison and not a secret one.
|
|
379
|
-
// nosemgrep: pki-non-constant-time-secret-compare
|
|
380
254
|
function _assertEnclosedKeyMatches(privateKeyDer, templateSpki) {
|
|
381
255
|
return Promise.resolve()
|
|
382
256
|
.then(function () { return key.publicFromPrivate(privateKeyDer); })
|
|
@@ -395,11 +269,6 @@ function _assertEnclosedKeyMatches(privateKeyDer, templateSpki) {
|
|
|
395
269
|
});
|
|
396
270
|
}
|
|
397
271
|
|
|
398
|
-
// The EnvelopedData SEQUENCE inside the ContentInfo cms.encrypt returns, validated on the way out.
|
|
399
|
-
// Every fault here comes from re-reading a composition the caller never sees. The codec's decode and
|
|
400
|
-
// the CMS walker each raise in their own namespace, so the whole body is translated, not the one call
|
|
401
|
-
// that happens to be likeliest. `pki.crmf.build` promises a typed CrmfError, and a promise with an
|
|
402
|
-
// exception for the paths nobody expected to take is the shape that makes it untrue exactly once.
|
|
403
272
|
function _bareEnvelopedData(contentInfoDer) {
|
|
404
273
|
try {
|
|
405
274
|
var kids = pkix.rootSequenceChildren(asn1.decode(contentInfoDer), 2, 2);
|
|
@@ -408,7 +277,7 @@ function _bareEnvelopedData(contentInfoDer) {
|
|
|
408
277
|
throw _err("crmf/bad-popo", "the enveloped private key is not a ContentInfo carrying one [0] EXPLICIT content");
|
|
409
278
|
}
|
|
410
279
|
var env = wrapper.children[0];
|
|
411
|
-
cms.walkEnvelopedData(env);
|
|
280
|
+
cms.walkEnvelopedData(env);
|
|
412
281
|
return b.raw(env.bytes);
|
|
413
282
|
} catch (e) {
|
|
414
283
|
if (e instanceof CrmfError) throw e;
|
|
@@ -417,11 +286,6 @@ function _bareEnvelopedData(contentInfoDer) {
|
|
|
417
286
|
}
|
|
418
287
|
}
|
|
419
288
|
|
|
420
|
-
// Check spec.pop against the arm the caller selected, never against every arm's fields at once. Every
|
|
421
|
-
// opt-in below is a refusal a caller turns off by naming it, so a field that belongs to a different
|
|
422
|
-
// arm must not read as one this arm accepted and ignored: the caller believes they asked for
|
|
423
|
-
// something the message does not say. An unrecognized arm falls through with only the selector
|
|
424
|
-
// fields allowed, so the error names the arm rather than a field beside it.
|
|
425
289
|
function _assertPopArmKeys(pop, mode) {
|
|
426
290
|
var arm = _hasOwn(POPO_PRIVKEY_TAGS, mode) ? pop.method : mode;
|
|
427
291
|
var allowed = (typeof arm === "string" && _hasOwn(POP_KEYS_BY_ARM, arm))
|
|
@@ -433,25 +297,14 @@ function _assertPopArmKeys(pop, mode) {
|
|
|
433
297
|
});
|
|
434
298
|
}
|
|
435
299
|
|
|
436
|
-
// ProofOfPossession. The signature arm (the default when a key is given): when the template carries both
|
|
437
|
-
// subject and publicKey (complete), sign the CertRequest DER and omit poposkInput; otherwise build a
|
|
438
|
-
// POPOSigningKeyInput (authInfo sender [0] GeneralName + the requested publicKey), sign its SEQUENCE, and
|
|
439
|
-
// carry it as poposkInput [0] (RFC 4211 sec. 4.1). raVerified is emitted only on an explicit opt-in.
|
|
440
300
|
function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
|
|
441
301
|
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 })");
|
|
442
|
-
// Every opt-in below is a refusal a caller turns off by naming it, so a misspelled key must not
|
|
443
|
-
// read as an omitted one: `archve: true` silently withholding the archival consent, or `identifer`
|
|
444
|
-
// silently omitting the field RFC 4211 sec. 4.2.1 makes mandatory, would each be a message built
|
|
445
|
-
// on the opposite of what the caller asked for.
|
|
446
|
-
// The arm is resolved BEFORE its fields are checked, because `type` is not the only thing that
|
|
447
|
-
// picks one: a spec that supplies a key and omits `type` selects the signature arm, and checking
|
|
448
|
-
// against an unresolved arm would refuse the `pop.sender` that arm reads.
|
|
449
302
|
var mode = (pop && pop.type) || (signingKey != null ? "signature" : null);
|
|
450
303
|
if (pop != null) _assertPopArmKeys(pop, mode);
|
|
451
|
-
if (mode == null) return null;
|
|
304
|
+
if (mode == null) return null;
|
|
452
305
|
if (mode === "raVerified") {
|
|
453
306
|
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)");
|
|
454
|
-
return b.implicit(0, b.nullValue());
|
|
307
|
+
return b.implicit(0, b.nullValue());
|
|
455
308
|
}
|
|
456
309
|
if (_hasOwn(POPO_PRIVKEY_TAGS, mode)) return _buildPopoPrivKey(pop, mode, template);
|
|
457
310
|
if (mode !== "signature") {
|
|
@@ -463,14 +316,13 @@ function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
|
|
|
463
316
|
var scheme = signScheme.resolveSignScheme(_b.certLikeFromSpki(template.spki), { combinedRsaSig: true, pss: opts.pss, digestAlgorithm: opts.digestAlgorithm }, true, _signE);
|
|
464
317
|
var signedRegion, poposkInputField = null;
|
|
465
318
|
if (template.complete) {
|
|
466
|
-
signedRegion = certReqDer;
|
|
319
|
+
signedRegion = certReqDer;
|
|
467
320
|
} else {
|
|
468
|
-
// POPOSigningKeyInput ::= SEQUENCE { authInfo CHOICE { sender [0] GeneralName, publicKeyMAC }, publicKey }.
|
|
469
321
|
var sender = pop && pop.sender;
|
|
470
322
|
if (sender == null) throw _err("crmf/bad-popo", "an incomplete template (missing subject or publicKey) requires pop.sender (a GeneralName) for the POPOSigningKeyInput authInfo (RFC 4211 sec. 4.1)");
|
|
471
323
|
var poposkSeq = b.sequence([b.explicit(0, _b.encodeGeneralName(sender)), b.raw(template.spki)]);
|
|
472
324
|
signedRegion = poposkSeq;
|
|
473
|
-
poposkInputField = b.implicit(0, poposkSeq);
|
|
325
|
+
poposkInputField = b.implicit(0, poposkSeq);
|
|
474
326
|
}
|
|
475
327
|
return signScheme.signOverTbs(scheme, signingKey, signedRegion, _signE).then(function (sig) {
|
|
476
328
|
return Promise.resolve(_b.assertSignatureVerifies(signedRegion, sig, template.spki, scheme)).then(function () {
|
|
@@ -478,7 +330,7 @@ function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
|
|
|
478
330
|
if (poposkInputField) popoChildren.push(poposkInputField);
|
|
479
331
|
popoChildren.push(scheme.sigAlgId);
|
|
480
332
|
popoChildren.push(b.bitString(sig, 0));
|
|
481
|
-
return b.implicit(1, b.sequence(popoChildren));
|
|
333
|
+
return b.implicit(1, b.sequence(popoChildren));
|
|
482
334
|
});
|
|
483
335
|
}, function (e) {
|
|
484
336
|
if (e instanceof CrmfError) throw e;
|
|
@@ -546,8 +398,6 @@ function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
|
|
|
546
398
|
* pki.schema.crmf.parse(msg).messages[0].certReq.certTemplate.subject.dn; // "CN=device-42"
|
|
547
399
|
*/
|
|
548
400
|
function build(spec, key, opts) {
|
|
549
|
-
// Every caller-owned argument copied at entry and released when the call settles -- see the note
|
|
550
|
-
// on the same call in x509-sign.
|
|
551
401
|
return guard.bytes.fixedCall(CrmfError, "crmf/bad-input", [
|
|
552
402
|
[spec, "the certificate-request-message spec"], [key, "the signing key"], [opts, "pki.crmf.build options"],
|
|
553
403
|
], _build);
|
|
@@ -568,7 +418,7 @@ function _buildCertReqMsg(spec, key, opts) {
|
|
|
568
418
|
var msgChildren = [certReqDer];
|
|
569
419
|
if (popoDer) msgChildren.push(popoDer);
|
|
570
420
|
if (spec.regInfo != null) msgChildren.push(_buildAttrTypeAndValues(spec.regInfo, "crmf/bad-reg-info", "regInfo", REGINFO_VALUE));
|
|
571
|
-
return b.sequence(msgChildren);
|
|
421
|
+
return b.sequence(msgChildren);
|
|
572
422
|
});
|
|
573
423
|
}
|
|
574
424
|
|
|
@@ -577,8 +427,6 @@ function _build(spec, key, opts) {
|
|
|
577
427
|
if (!spec || typeof spec !== "object" || Buffer.isBuffer(spec)) throw _err("crmf/bad-input", "the certificate-request-message spec must be an object");
|
|
578
428
|
var specs;
|
|
579
429
|
if (spec.messages != null) {
|
|
580
|
-
// Batch form: the envelope carries ONLY `messages` -- reject a stray field so a request spec written at
|
|
581
|
-
// the wrong nesting level (e.g. certTemplate alongside messages) is not silently dropped.
|
|
582
430
|
if (!Array.isArray(spec.messages)) throw _err("crmf/bad-input", "spec.messages must be an array of certificate-request-message specs");
|
|
583
431
|
guard.identifier.assertKnownKeys(spec, KNOWN_BATCH_KEYS, _err, "crmf/bad-input", function (k) {
|
|
584
432
|
return "unknown batch-envelope field " + JSON.stringify(k) + " -- a batch spec carries only 'messages'";
|
|
@@ -589,7 +437,7 @@ function _build(spec, key, opts) {
|
|
|
589
437
|
}
|
|
590
438
|
if (!specs.length) throw _err("crmf/bad-input", "at least one certificate request message is required (RFC 4211 sec. 3)");
|
|
591
439
|
return Promise.all(specs.map(function (s) { return _buildCertReqMsg(s, key, opts); })).then(function (msgs) {
|
|
592
|
-
var der = b.sequence(msgs);
|
|
440
|
+
var der = b.sequence(msgs);
|
|
593
441
|
if (opts.pem != null) {
|
|
594
442
|
if (typeof opts.pem !== "string" || !opts.pem) throw _err("crmf/bad-input", "opts.pem must be a non-empty PEM label string");
|
|
595
443
|
return crmf.pemEncode(der, opts.pem);
|
|
@@ -622,8 +470,6 @@ function _coerceMessages(input) {
|
|
|
622
470
|
return guard.parsed.acceptDerived(input, "crmf", crmf.parse, _err, "crmf/bad-input", "the certificate request messages");
|
|
623
471
|
}
|
|
624
472
|
|
|
625
|
-
// One CertReqMsg's proof of possession, reduced to a verdict. Every return names a method and
|
|
626
|
-
// reports the requested identity, so a refusal is as informative as an acceptance.
|
|
627
473
|
function _verifyOne(msg) {
|
|
628
474
|
var t = msg.certReq && msg.certReq.certTemplate;
|
|
629
475
|
var base = {
|
|
@@ -632,7 +478,7 @@ function _verifyOne(msg) {
|
|
|
632
478
|
subjectBound: false,
|
|
633
479
|
publicKey: (t && t.publicKey && t.publicKey.bytes) || null,
|
|
634
480
|
};
|
|
635
|
-
function settle(extra) { var v = Object.assign({}, base, extra); v.valid = v.verified === true; return Promise.resolve(v); }
|
|
481
|
+
function settle(extra) { var v = Object.assign({}, base, extra); v.valid = v.verified === true; return Promise.resolve(v); }
|
|
636
482
|
|
|
637
483
|
var popo = msg.popo;
|
|
638
484
|
if (popo == null) {
|
|
@@ -640,42 +486,17 @@ function _verifyOne(msg) {
|
|
|
640
486
|
reason: "the request carries no proof of possession" });
|
|
641
487
|
}
|
|
642
488
|
if (popo.type === "raVerified") {
|
|
643
|
-
// sec. 4: an assertion by the RA that it checked possession out of band. It is not a proof and
|
|
644
|
-
// must not read as one; a caller that trusts its RA opts in by reading `method`.
|
|
645
489
|
return settle({ verified: false, method: "raVerified", cryptographicallyVerified: false,
|
|
646
490
|
reason: "raVerified asserts the RA checked possession out of band -- this is not a proof and cannot be verified from the message" });
|
|
647
491
|
}
|
|
648
492
|
if (popo.type !== "signature") {
|
|
649
|
-
// keyEncipherment / keyAgreement complete over a later protocol exchange (a challenge the
|
|
650
|
-
// verifier does not hold) or need the CA's decryption key, so no verdict is available here.
|
|
651
493
|
return settle({ verified: false, method: popo.type, cryptographicallyVerified: false,
|
|
652
494
|
reason: "a " + popo.type + " proof of possession completes over an exchange this verifier does not hold (RFC 4211 sec. 4.2, 4.3)" });
|
|
653
495
|
}
|
|
654
496
|
|
|
655
|
-
// Which bytes the signature covers is fixed by whether poposkInput is present: the DER of
|
|
656
|
-
// poposkInput when it is, the DER of certReq when it is not (RFC 4211 sec. 4.1, and the ASN.1
|
|
657
|
-
// module on p.33 -- sec. 4.1's case-3 prose says "certificate template" where the field
|
|
658
|
-
// definition and the module both say certReq).
|
|
659
|
-
//
|
|
660
|
-
// The three conformance rules that decide which of those is legitimate are enforced ONE layer
|
|
661
|
-
// down, at parse: poposkInput MUST be omitted exactly when the template carries both subject and
|
|
662
|
-
// publicKey, and poposkInput.publicKey MUST equal the template's (schema-crmf.js, the CertReqMsg
|
|
663
|
-
// build). A message breaking either never reaches this function, and re-checking here would add
|
|
664
|
-
// a branch no input can take while implying the parser might let one through.
|
|
665
497
|
var pin = popo.poposkInput;
|
|
666
498
|
var preimage = pin ? pin.signedBytes : msg.certReq.certReqBytes;
|
|
667
|
-
// The key possession is proven for is the one inside the preimage. With poposkInput that is its
|
|
668
|
-
// own publicKey; sec. 4.1 requires the template to carry the same value, and the parser enforces
|
|
669
|
-
// that whenever the template has one at all. A template that omits it entirely still parses (the
|
|
670
|
-
// equality rule has nothing to compare), so reading the template here would report null for a key
|
|
671
|
-
// whose possession was just proven.
|
|
672
499
|
var spki = pin ? pin.publicKey : base.publicKey;
|
|
673
|
-
// Which fields the verdict may report follows from which bytes were signed. The certReq preimage
|
|
674
|
-
// is the whole CertRequest, so the template's subject rides with it. The poposkInput preimage
|
|
675
|
-
// covers the key and the authInfo sender and never the template, so a subject sitting in the
|
|
676
|
-
// message alongside it is unsigned: reporting it beside `verified: true` would let a CA issue a
|
|
677
|
-
// name the requester proved nothing about. It is withheld, and `subjectBound` says so rather than
|
|
678
|
-
// leaving the absence to look like a request that named no subject.
|
|
679
500
|
var bound = !pin;
|
|
680
501
|
return crmfVerify.verifyPopSignature(popo, spki, preimage).then(function (ok) {
|
|
681
502
|
return Object.assign({}, base, {
|
package/lib/crmf-verify.js
CHANGED
|
@@ -1,24 +1,13 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal -- the single RFC 4211 proof-of-possession signature seam for an INBOUND CertReqMsg,
|
|
6
|
-
// shared by pki.crmf.verifyPop and by every enrollment path that accepts one. The slot, the wiring
|
|
7
|
-
// assertion and the octet-alignment refusal are the shared seam (lib/verify-seam.js); what is
|
|
8
|
-
// domain-specific is the error class and which bytes the signature covers. It is not wired into
|
|
9
|
-
// index.js and is reached only by require.
|
|
4
|
+
// @internal
|
|
10
5
|
|
|
11
6
|
var frameworkError = require("./framework-error");
|
|
12
7
|
var seam = require("./verify-seam");
|
|
13
8
|
|
|
14
9
|
var _seam = seam.makeSeam("crmf-verify", frameworkError.CrmfError, "crmf/bad-input");
|
|
15
10
|
|
|
16
|
-
// Verify one CertReqMsg's POPOSigningKey signature over `preimage` under `spkiBytes`.
|
|
17
|
-
//
|
|
18
|
-
// `preimage` is chosen by the caller from the two the RFC defines, because the choice is a
|
|
19
|
-
// conformance rule about the message rather than a property of the signature: the DER of
|
|
20
|
-
// poposkInput when that field is present, and the DER of `CertReqMsg certReq` when it is absent
|
|
21
|
-
// (RFC 4211 sec. 4.1 and the ASN.1 module on p.33).
|
|
22
11
|
function verifyPopSignature(popo, spkiBytes, preimage) {
|
|
23
12
|
return _seam.verify(popo.algorithmIdentifier, popo.signature, spkiBytes, preimage);
|
|
24
13
|
}
|