@blamejs/pki 0.6.14 → 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 +14 -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 +8 -252
- 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/cmc-build.js
CHANGED
|
@@ -1,44 +1,22 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal -- the pki.cmc.build implementation. So the pki.cmc namespace has one @module home,
|
|
4
|
+
// @internal
|
|
6
5
|
// the operator-facing @module pki.cmc + the @primitive pki.cmc.build documentation block live in
|
|
7
|
-
|
|
8
|
-
//
|
|
9
|
-
/**
|
|
10
|
-
* RFC 5272 Full PKI Request production -- the producing half of `pki.cmc`.
|
|
11
|
-
*
|
|
12
|
-
* `build(spec, signer, opts)` assembles a PKIData (all four sequences, each
|
|
13
|
-
* possibly empty), allocates a unique body part identifier for every element,
|
|
14
|
-
* attaches controls through an OID-keyed encoder registry that enforces the RFC
|
|
15
|
-
* 6402 placement rules, computes the Identity Proof / POP Link witnesses over the
|
|
16
|
-
* bytes it is about to EMIT, and signs the result through `pki.cms.sign` with
|
|
17
|
-
* `eContentType: id-cct-PKIData`.
|
|
18
|
-
*
|
|
19
|
-
* The witness rule is the load-bearing one. RFC 5272 sec. 6.2.1 step 1 says the
|
|
20
|
-
* value to be validated is "The PKIData reqSequence field (encoded exactly as it
|
|
21
|
-
* appears in the Full PKI Request including the sequence type and length)". So
|
|
22
|
-
* the reqSequence TLV is built once and both the message and the witness are
|
|
23
|
-
* derived from that single buffer; computing the witness over a second
|
|
24
|
-
* serialization would agree with itself while disagreeing with the wire the
|
|
25
|
-
* moment any encoding detail differed.
|
|
26
|
-
*/
|
|
6
|
+
|
|
27
7
|
|
|
28
8
|
var nodeCrypto = require("node:crypto");
|
|
29
9
|
var asn1 = require("./asn1-der");
|
|
30
|
-
var pkiBuild = require("./pki-build");
|
|
31
|
-
var csr = require("./schema-csr");
|
|
32
|
-
require("./path-validate");
|
|
33
|
-
var csrVerify = require("./csr-verify");
|
|
34
|
-
var crmf = require("./schema-crmf");
|
|
35
|
-
var cmcFmt = require("./schema-cmc");
|
|
10
|
+
var pkiBuild = require("./pki-build");
|
|
11
|
+
var csr = require("./schema-csr");
|
|
12
|
+
require("./path-validate");
|
|
13
|
+
var csrVerify = require("./csr-verify");
|
|
14
|
+
var crmf = require("./schema-crmf");
|
|
15
|
+
var cmcFmt = require("./schema-cmc");
|
|
36
16
|
var oid = require("./oid");
|
|
37
17
|
var cmsSign = require("./cms-sign");
|
|
38
18
|
var guard = require("./guard-all");
|
|
39
19
|
var intrinsic = require("./guard-intrinsic");
|
|
40
|
-
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
41
|
-
// replaceable properties to answer one thing, and here it decides which controls a request carries.
|
|
42
20
|
var _hasOwn = intrinsic.hasOwn;
|
|
43
21
|
var frameworkError = require("./framework-error");
|
|
44
22
|
|
|
@@ -53,44 +31,18 @@ var OID_IDENTIFICATION = O("id-cmc-identification");
|
|
|
53
31
|
var OID_SHA256 = O("sha256");
|
|
54
32
|
var OID_HMAC_SHA256 = O("hmacWithSHA256");
|
|
55
33
|
|
|
56
|
-
// The identity-bearing controls a renewal must not carry: RFC 5272 sec. 3.2 (a),
|
|
57
|
-
// "The Identification and Identity Proof controls are absent."
|
|
58
|
-
//
|
|
59
|
-
// "Identity Proof" covers both versions: the original id-cmc-identityProof (sec.
|
|
60
|
-
// 6.2.2) and identityProofV2 (sec. 6.2.1). Listing only V2 would leave a renewal
|
|
61
|
-
// able to carry the v1 control, which is the same nonconformance the rule names.
|
|
62
34
|
var RENEWAL_FORBIDDEN = {};
|
|
63
35
|
RENEWAL_FORBIDDEN[OID_IDENTIFICATION] = "Identification";
|
|
64
36
|
RENEWAL_FORBIDDEN[O("id-cmc-identityProof")] = "Identity Proof";
|
|
65
37
|
RENEWAL_FORBIDDEN[OID_IDENTITY_PROOF_V2] = "Identity Proof V2";
|
|
66
38
|
|
|
67
|
-
// RFC 6402 sec. 2.6: responseBody appears only in a PKIResponse. Enforced at
|
|
68
|
-
// build time as well as at parse, so a caller cannot emit a message its own
|
|
69
|
-
// decoder would refuse.
|
|
70
39
|
var OID_RESPONSE_BODY = O("id-cmc-responseBody");
|
|
71
40
|
|
|
72
|
-
// RFC 5272 sec. 6.2.1 says "Implementations MUST be able to support tokens at
|
|
73
|
-
// least 16 characters long", a requirement on what an implementation must be
|
|
74
|
-
// able to accept, not a floor every token has to clear. Reading it as a minimum
|
|
75
|
-
// inverts it, and would refuse a shorter secret a CA legitimately provisioned.
|
|
76
|
-
// The shared secret's strength is the deploying CA's policy; what this layer can
|
|
77
|
-
// say is that an empty string is not a credential at all.
|
|
78
|
-
// PL1: R "SHOULD be at least 512 bits in length".
|
|
79
41
|
var POP_LINK_RANDOM_BYTES = 64;
|
|
80
42
|
|
|
81
|
-
// The spec fields pki.cmc.build understands. Anything else is a caller mistake
|
|
82
|
-
// caught at the entry point, never silently dropped from the message.
|
|
83
43
|
var KNOWN_SPEC_KEYS = {
|
|
84
44
|
requests: 1, controls: 1, cmsSequence: 1, otherMsgSequence: 1,
|
|
85
|
-
// `identification` is deliberately absent: the Identification control is attached through
|
|
86
|
-
// identityProof.identity, which is what emits it. Listing a key nothing reads
|
|
87
|
-
// would recreate the very hole this table closes, accepted at the door and
|
|
88
|
-
// silently absent from the message.
|
|
89
45
|
identityProof: 1, popLink: 1, renewal: 1,
|
|
90
|
-
// The exchange binding (RFC 5272 sec. 6.6 / 6.4). First-class here because
|
|
91
|
-
// pki.cmc.verify names these same three when checking the response: a builder
|
|
92
|
-
// that made the caller hand-encode them while the verifier took them by name is
|
|
93
|
-
// the asymmetry that lets a request ship with no replay defense.
|
|
94
46
|
transactionId: 1, senderNonce: 1, dataReturn: 1,
|
|
95
47
|
};
|
|
96
48
|
|
|
@@ -98,22 +50,11 @@ var OID_TRANSACTION_ID = O("id-cmc-transactionId");
|
|
|
98
50
|
var OID_SENDER_NONCE = O("id-cmc-senderNonce");
|
|
99
51
|
var OID_DATA_RETURN = O("id-cmc-dataReturn");
|
|
100
52
|
|
|
101
|
-
// AlgorithmIdentifier { OID } with no parameters: the form the MAC / hash
|
|
102
|
-
// algorithm identifiers in these two controls take.
|
|
103
53
|
function algId(o) { return b.sequence([b.oid(o)]); }
|
|
104
54
|
|
|
105
|
-
// ---- body part identity ---------------------------------------------
|
|
106
55
|
|
|
107
|
-
// RFC 5272 sec. 3.2.2: identifiers are unique across the whole message and 0 is
|
|
108
|
-
// reserved for the reference to the current PKIData. The allocator therefore
|
|
109
|
-
// starts at 1, and a caller-supplied value is validated, never adjusted;
|
|
110
|
-
// silently renumbering would break any control that already references it.
|
|
111
56
|
function makeIdAllocator() {
|
|
112
57
|
var used = Object.create(null);
|
|
113
|
-
// Reserved ahead of allocation and not yet consumed by the element that owns
|
|
114
|
-
// it. Kept distinct from `used` so an element re-claiming its own reservation
|
|
115
|
-
// is a no-op, while a second element asking for the same value is still the
|
|
116
|
-
// duplicate the rule forbids.
|
|
117
58
|
var pending = Object.create(null);
|
|
118
59
|
var next = 1;
|
|
119
60
|
function assertValid(requested, what) {
|
|
@@ -143,7 +84,7 @@ function makeIdAllocator() {
|
|
|
143
84
|
return next;
|
|
144
85
|
}
|
|
145
86
|
assertValid(requested, what);
|
|
146
|
-
if (pending[requested]) { delete pending[requested]; return requested; }
|
|
87
|
+
if (pending[requested]) { delete pending[requested]; return requested; }
|
|
147
88
|
if (used[requested]) {
|
|
148
89
|
throw E("cmc/bad-input",
|
|
149
90
|
"bodyPartID " + requested + " is used more than once; identifiers MUST be unique within a single PKIData (RFC 5272 sec. 3.2.2)");
|
|
@@ -154,28 +95,8 @@ function makeIdAllocator() {
|
|
|
154
95
|
};
|
|
155
96
|
}
|
|
156
97
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
// TaggedRequest ::= CHOICE { tcr [0], crm [1], orm [2] }, IMPLICIT tags. For the
|
|
160
|
-
// crm arm the identity is the CertReqMsg's own certReqId (sec. 3.2.2), so it is
|
|
161
|
-
// read back out of the supplied message and never allocated; allocating one
|
|
162
|
-
// would put a second, contradictory identifier on the wire.
|
|
163
|
-
// The identifier a request fixes, or null when it leaves the choice to us. Read
|
|
164
|
-
// in a pre-pass so every caller-determined value is reserved before a single
|
|
165
|
-
// generated one is handed out; otherwise acceptance depends on the order the
|
|
166
|
-
// caller happened to write the list in, which is not a property of the message.
|
|
167
|
-
// The fields a request descriptor understands -- the same door KNOWN_SPEC_KEYS
|
|
168
|
-
// closes on the spec, on the objects nested inside it. A misspelled `bodyPartID`
|
|
169
|
-
// is not a harmless extra: the request would be auto-allocated a DIFFERENT
|
|
170
|
-
// identifier while a control the caller wrote still references the intended one,
|
|
171
|
-
// and the builder would sign a message whose halves point at different requests.
|
|
98
|
+
|
|
172
99
|
var KNOWN_REQUEST_KEYS = { tcr: 1, crm: 1, orm: 1, bodyPartID: 1 };
|
|
173
|
-
// The same door on the other descriptors nested in a spec. A control's identifier
|
|
174
|
-
// is always allocated, so a `bodyPartID` written on one is ignored outright; and a
|
|
175
|
-
// misspelled `identity` does not merely go missing: the Identification control
|
|
176
|
-
// it would have emitted is what tells the server to derive the Identity Proof key
|
|
177
|
-
// from the secret and the identity, so the message would carry a witness no
|
|
178
|
-
// conforming server can reproduce.
|
|
179
100
|
var KNOWN_CONTROL_KEYS = { type: 1, value: 1 };
|
|
180
101
|
var KNOWN_IDENTITY_PROOF_KEYS = { identity: 1, secret: 1 };
|
|
181
102
|
var KNOWN_POP_LINK_KEYS = { secret: 1 };
|
|
@@ -192,12 +113,6 @@ function fixedRequestId(req, index) {
|
|
|
192
113
|
"each request names exactly one of tcr / crm / orm, got " + (arms.length ? arms.join(" + ") : "none") +
|
|
193
114
|
" (request " + index + ")");
|
|
194
115
|
}
|
|
195
|
-
// A crm arm's identity is its CertReqMsg's own certReqId, so it is always fixed:
|
|
196
|
-
// there is nothing to allocate and nothing that may displace it. A caller who
|
|
197
|
-
// also writes bodyPartID is stating an identity, and the only reason to state one
|
|
198
|
-
// is that something already references it; taking the certReqId silently would
|
|
199
|
-
// sign a message whose control points at no request in it. Equal is accepted,
|
|
200
|
-
// since the caller is agreeing with the message, not overriding it.
|
|
201
116
|
if (req.crm != null) {
|
|
202
117
|
var crmId = _certReqIdOf(_asCertReqMsg(_der(req.crm, "a crm CertReqMsg")));
|
|
203
118
|
if (req.bodyPartID != null && _asBigInt(req.bodyPartID, "a crm request bodyPartID") !== BigInt(crmId)) {
|
|
@@ -210,23 +125,12 @@ function fixedRequestId(req, index) {
|
|
|
210
125
|
return req.bodyPartID == null ? null : req.bodyPartID;
|
|
211
126
|
}
|
|
212
127
|
|
|
213
|
-
// Verify each embedded PKCS#10 (tcr) request's proof-of-possession before the message is signed. A CSR
|
|
214
|
-
// whose self-signature does not verify under its own subjectPKInfo is one the CA will reject (RFC 5272
|
|
215
|
-
// sec. 4.1), so refusing it here -- through the shared inbound-PoP seam pki.csr.verify uses -- keeps the
|
|
216
|
-
// producer from signing a request that cannot be acted on, the same "do not sign what the recipient
|
|
217
|
-
// refuses" discipline the assembled-message readback already applies. The crm / orm arms carry their own
|
|
218
|
-
// POP structures (CRMF POPOSigningKey etc.), validated by their own parse; only tcr is a bare CSR.
|
|
219
|
-
// `await`, not `.then`: the PoP verdict is read through the native promise job, not the replaceable
|
|
220
|
-
// Promise.prototype.then a co-resident could swap to force `ok` true and slip an unverified request past.
|
|
221
128
|
async function _assertTcrPops(requests) {
|
|
222
129
|
for (var i = 0; i < requests.length; i++) {
|
|
223
130
|
if (requests[i] && requests[i].tcr != null) await _assertOneTcrPop(requests[i].tcr, i);
|
|
224
131
|
}
|
|
225
132
|
}
|
|
226
133
|
async function _assertOneTcrPop(tcr, i) {
|
|
227
|
-
// encodeRequest already validated this tcr's structure synchronously (it runs before this async pass),
|
|
228
|
-
// so the re-parse here only recovers the parse the PoP check verifies over; on the shipped path it
|
|
229
|
-
// cannot fail, and a sync throw surfaces as a rejection through build's async boundary regardless.
|
|
230
134
|
var parsed = csr.parse(_der(tcr, "a tcr certificationRequest"));
|
|
231
135
|
if (!(await csrVerify.verifyCsrSignature(parsed))) {
|
|
232
136
|
throw E("cmc/bad-popo", "a tcr request [" + i + "] failed its proof-of-possession: the PKCS#10 signature does not verify under the request's own subject public key (RFC 5272 sec. 6.3), so a CA would reject it -- refusing it here rather than signing it");
|
|
@@ -235,11 +139,6 @@ async function _assertOneTcrPop(tcr, i) {
|
|
|
235
139
|
|
|
236
140
|
function encodeRequest(req, ids, index) {
|
|
237
141
|
if (req.tcr != null) {
|
|
238
|
-
// A tcr arm is a PKCS#10 CertificationRequest, so it is parsed and never
|
|
239
|
-
// taken on the tag: the readback of the assembled message checks the CMC
|
|
240
|
-
// structure around it, and would pass an empty SEQUENCE here as happily as a
|
|
241
|
-
// real request. Signing a request body that is not one produces an enrollment
|
|
242
|
-
// no CA can act on, and the producer is where that is cheap to catch.
|
|
243
142
|
try { csr.parse(_der(req.tcr, "a tcr certificationRequest")); }
|
|
244
143
|
catch (e) {
|
|
245
144
|
throw E("cmc/bad-input", "a tcr request must be a PKCS#10 CertificationRequest: " +
|
|
@@ -250,23 +149,12 @@ function encodeRequest(req, ids, index) {
|
|
|
250
149
|
}
|
|
251
150
|
if (req.crm != null) {
|
|
252
151
|
var msg = _asCertReqMsg(_der(req.crm, "a crm CertReqMsg"));
|
|
253
|
-
// Parsed as the CertReqMsg it is, the same way a tcr is parsed as a
|
|
254
|
-
// CertificationRequest: the readback checks the CMC structure around the
|
|
255
|
-
// request, and would pass a SEQUENCE that merely starts like one.
|
|
256
152
|
try { crmf.parse(b.sequence([msg])); }
|
|
257
153
|
catch (e) {
|
|
258
154
|
throw E("cmc/bad-input", "a crm request must be an RFC 4211 CertReqMsg: " +
|
|
259
155
|
(e && e.message ? e.message : "it did not parse as one"), e);
|
|
260
156
|
}
|
|
261
|
-
// CLAIM, not just read. The certReqId was reserved in the pre-pass with every
|
|
262
|
-
// other caller-determined identifier; leaving the reservation outstanding lets
|
|
263
|
-
// a later cmsSequence or otherMsg element asking for the same number be taken
|
|
264
|
-
// for its owner, and the message would then carry the identifier twice: a
|
|
265
|
-
// PKIData this toolkit's own parser refuses. The tcr and orm arms already
|
|
266
|
-
// claim; this arm is the same rule.
|
|
267
157
|
var certReqId = ids.claim(_certReqIdOf(msg), "a crm request");
|
|
268
|
-
// IMPLICIT [1]: the tag replaces the CertReqMsg SEQUENCE tag, so the content
|
|
269
|
-
// is re-headered, not nested.
|
|
270
158
|
var node = asn1.decode(msg);
|
|
271
159
|
var headerLen = node.header.end - node.header.start;
|
|
272
160
|
return { bodyPartID: certReqId, der: b.contextConstructed(1, node.bytes.subarray(headerLen)) };
|
|
@@ -283,18 +171,6 @@ function encodeRequest(req, ids, index) {
|
|
|
283
171
|
};
|
|
284
172
|
}
|
|
285
173
|
|
|
286
|
-
// A TaggedRequest's crm arm carries one CertReqMsg, but `pki.crmf.build` returns
|
|
287
|
-
// a CertReqMessages (SEQUENCE SIZE(1..MAX) OF CertReqMsg), which is what a
|
|
288
|
-
// caller naturally has in hand. Both are accepted, told apart structurally:
|
|
289
|
-
//
|
|
290
|
-
// CertReqMsg ::= SEQUENCE { certReq CertRequest, ... } first child is a
|
|
291
|
-
// SEQUENCE whose own first child is an INTEGER (certReqId)
|
|
292
|
-
// CertReqMessages ::= SEQUENCE OF CertReqMsg first child is a
|
|
293
|
-
// CertReqMsg, whose own first child is a SEQUENCE
|
|
294
|
-
//
|
|
295
|
-
// A CertReqMessages carrying more than one message is refused, never reduced to
|
|
296
|
-
// its first: each CertReqMsg is its own TaggedRequest with its own identity,
|
|
297
|
-
// so silently dropping the rest would emit a request the caller did not ask for.
|
|
298
174
|
function _asCertReqMsg(der) {
|
|
299
175
|
var node = asn1.decode(der);
|
|
300
176
|
var first = node.children && node.children[0];
|
|
@@ -302,7 +178,7 @@ function _asCertReqMsg(der) {
|
|
|
302
178
|
throw E("cmc/bad-input", "a crm arm must be a CertReqMsg or a CertReqMessages carrying one");
|
|
303
179
|
}
|
|
304
180
|
var grand = first.children[0];
|
|
305
|
-
if (grand.tagClass === "universal" && grand.tagNumber === asn1.TAGS.INTEGER) return der;
|
|
181
|
+
if (grand.tagClass === "universal" && grand.tagNumber === asn1.TAGS.INTEGER) return der;
|
|
306
182
|
if (node.children.length !== 1) {
|
|
307
183
|
throw E("cmc/bad-input",
|
|
308
184
|
"a crm arm carries exactly one CertReqMsg; this CertReqMessages holds " + node.children.length +
|
|
@@ -311,8 +187,6 @@ function _asCertReqMsg(der) {
|
|
|
311
187
|
return first.bytes;
|
|
312
188
|
}
|
|
313
189
|
|
|
314
|
-
// A CertReqMsg's identity: CertReqMsg ::= SEQUENCE { certReq CertRequest, ... },
|
|
315
|
-
// CertRequest ::= SEQUENCE { certReqId INTEGER, ... }.
|
|
316
190
|
function _certReqIdOf(msg) {
|
|
317
191
|
var node = asn1.decode(msg);
|
|
318
192
|
var certReq = node.children && node.children[0];
|
|
@@ -323,10 +197,6 @@ function _certReqIdOf(msg) {
|
|
|
323
197
|
return guard.range.int(v, 0n, 4294967295n, E, "cmc/bad-input", "a crm certReqId");
|
|
324
198
|
}
|
|
325
199
|
|
|
326
|
-
// A pre-encoded TaggedContentInfo / OtherMsg supplied by the caller. Only its
|
|
327
|
-
// leading bodyPartID is read (the payload is the caller's and stays untouched),
|
|
328
|
-
// so the identifier joins the same allocation space every other element draws
|
|
329
|
-
// from and a collision is refused here, never discovered later by the parser.
|
|
330
200
|
function _claimRawElement(el, ids, what) {
|
|
331
201
|
var der = _der(el, what);
|
|
332
202
|
var node = asn1.decode(der);
|
|
@@ -339,24 +209,11 @@ function _claimRawElement(el, ids, what) {
|
|
|
339
209
|
return der;
|
|
340
210
|
}
|
|
341
211
|
|
|
342
|
-
// The binding controls have a named spec field and can also be hand-encoded into
|
|
343
|
-
// spec.controls. Emitting both would put two of the same control in one message,
|
|
344
|
-
// and duplicates of these three are what decide what the response must echo: two
|
|
345
|
-
// values means no value. Also refuses two hand-encoded copies, since the same
|
|
346
|
-
// ambiguity arrives that way.
|
|
347
212
|
var BINDING_BY_OID = {};
|
|
348
213
|
BINDING_BY_OID[O("id-cmc-transactionId")] = "transactionId";
|
|
349
214
|
BINDING_BY_OID[O("id-cmc-senderNonce")] = "senderNonce";
|
|
350
215
|
BINDING_BY_OID[O("id-cmc-dataReturn")] = "dataReturn";
|
|
351
216
|
|
|
352
|
-
// ... and the type each one carries. The CMC parser keeps every control value raw,
|
|
353
|
-
// so nothing downstream of the builder objects to a Transaction Identifier encoded
|
|
354
|
-
// as an OCTET STRING. But these three are read, not carried: pki.cmc.verify
|
|
355
|
-
// compares them against the response, and pki.est.fullcmc reads them back out of
|
|
356
|
-
// the request before it goes out. A hand-encoded value of the wrong type therefore
|
|
357
|
-
// signs a request that this toolkit's own client will refuse to send. The type is
|
|
358
|
-
// checked by READING the value, so a wrong tag and a malformed encoding of the
|
|
359
|
-
// right one are refused alike.
|
|
360
217
|
var BINDING_READER = {
|
|
361
218
|
transactionId: function (node) { return asn1.read.integer(node); },
|
|
362
219
|
senderNonce: function (node) { return asn1.read.octetString(node); },
|
|
@@ -390,8 +247,6 @@ function _assertNoDuplicateBinding(callerControls, spec) {
|
|
|
390
247
|
});
|
|
391
248
|
}
|
|
392
249
|
|
|
393
|
-
// A Transaction Identifier is an unbounded INTEGER on the wire; the shared authoring
|
|
394
|
-
// guard normalizes the number-or-bigint a caller naturally has.
|
|
395
250
|
function _asBigInt(v, what) { return guard.range.authoredInteger(v, E, "cmc/bad-input", what); }
|
|
396
251
|
|
|
397
252
|
function _der(v, what) {
|
|
@@ -403,20 +258,7 @@ function _oidOf(v) {
|
|
|
403
258
|
return oid.isDottedDecimal(v) ? v : O(v);
|
|
404
259
|
}
|
|
405
260
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
// TaggedAttribute ::= SEQUENCE { bodyPartID, attrType, attrValues SET OF ANY }.
|
|
409
|
-
// Controls this builder will not put in a Full PKI Request. A row per control with
|
|
410
|
-
// the reason it does not belong in this direction: id-cmc-responseBody by an
|
|
411
|
-
// explicit RFC 6402 sec. 2.6 MUST NOT, the two status controls because RFC 5272
|
|
412
|
-
// sec. 6.1 makes them what a server emits and a client processes in a PKI Response.
|
|
413
|
-
// a request has no verdict of its own to report, so one written here is a fabricated
|
|
414
|
-
// answer to a question nobody asked.
|
|
415
|
-
//
|
|
416
|
-
// Only the BUILDER refuses these. pki.schema.cmc keeps decoding them wherever they
|
|
417
|
-
// appear, because the spec states a placement MUST for id-cmc-responseBody alone --
|
|
418
|
-
// refusing the rest on the wire would reject a message the ASN.1 permits, and this
|
|
419
|
-
// decoder's job is to surface what arrived, not to judge who should have sent it.
|
|
261
|
+
|
|
420
262
|
var REQUEST_FORBIDDEN_CONTROL = {};
|
|
421
263
|
REQUEST_FORBIDDEN_CONTROL[OID_RESPONSE_BODY] =
|
|
422
264
|
"id-cmc-responseBody may appear only in the control sequence of a PKIResponse (RFC 6402 sec. 2.6)";
|
|
@@ -435,14 +277,8 @@ function encodeControl(attrType, values, ids) {
|
|
|
435
277
|
return b.sequence([b.integer(BigInt(id)), b.oid(attrType), b.set(values)]);
|
|
436
278
|
}
|
|
437
279
|
|
|
438
|
-
// ---- the witnesses ---------------------------------------------------
|
|
439
280
|
|
|
440
281
|
function _assertSecret(secret, what) {
|
|
441
|
-
// An entry-tier argument check: this tests the TYPE and whether the string is empty, and
|
|
442
|
-
// compares no secret-dependent content. The length of a caller's own argument is already
|
|
443
|
-
// known to that caller, so there is no secret for a timing-safe compare to protect. The
|
|
444
|
-
// MAC comparisons this rule exists for route through guard.crypto.constantTimeEqual.
|
|
445
|
-
// nosemgrep: pki-non-constant-time-secret-compare
|
|
446
282
|
if (typeof secret !== "string" || secret.length === 0) {
|
|
447
283
|
throw E("cmc/bad-input",
|
|
448
284
|
what + " requires the shared secret as a non-empty string; got " +
|
|
@@ -450,13 +286,6 @@ function _assertSecret(secret, what) {
|
|
|
450
286
|
}
|
|
451
287
|
}
|
|
452
288
|
|
|
453
|
-
// IdentifyProofV2 ::= SEQUENCE { hashAlgID, macAlgID, witness OCTET STRING }.
|
|
454
|
-
// key = hash(shared-secret as a UTF8 string); witness = MAC(reqSequenceBytes, key).
|
|
455
|
-
// `reqSequenceBytes` is the buffer that becomes the message's reqSequence, never
|
|
456
|
-
// a re-encode of it (RFC 5272 sec. 6.2.1 step 1).
|
|
457
|
-
// Tracks a buffer this module allocated that carries secret material, so the caller clears it
|
|
458
|
-
// on every exit. The shared secret arrives as a JS string, which is immutable and cannot be
|
|
459
|
-
// cleared; what this closes is every copy the toolkit itself makes of it.
|
|
460
289
|
function _ownSecret(buf, owned) {
|
|
461
290
|
owned.push(buf);
|
|
462
291
|
return buf;
|
|
@@ -464,18 +293,6 @@ function _ownSecret(buf, owned) {
|
|
|
464
293
|
|
|
465
294
|
function identityProofV2(secret, reqSequenceBytes, identity) {
|
|
466
295
|
_assertSecret(secret, "an Identity Proof V2 control");
|
|
467
|
-
// RFC 5272 sec. 6.2.3: the Identification control is OPTIONAL ("servers MAY
|
|
468
|
-
// require" it), but when it is present the key derivation changes: "the
|
|
469
|
-
// hash of the concatenation of the shared-secret and the UTF8 identity value
|
|
470
|
-
// (without the type and length bytes) are hashed rather than just the
|
|
471
|
-
// shared-secret". Same controls on the wire, different key: a producer that
|
|
472
|
-
// emits the Identification control while hashing the secret alone computes a
|
|
473
|
-
// witness every conforming server rejects.
|
|
474
|
-
// Every allocation that carries the shared secret is cleared, not only the derived key.
|
|
475
|
-
// The derivation input holds the secret in the clear, and with an identity present the
|
|
476
|
-
// concatenation leaves two more copies of it behind: the operand and the joined buffer.
|
|
477
|
-
// Wiping the key while those survive clears the cheapest copy and keeps the rest. The
|
|
478
|
-
// derivation runs inside the try so a throw partway still reaches the clear.
|
|
479
296
|
var owned = [];
|
|
480
297
|
var key = null;
|
|
481
298
|
try {
|
|
@@ -497,10 +314,6 @@ function identityProofV2(secret, reqSequenceBytes, identity) {
|
|
|
497
314
|
}
|
|
498
315
|
}
|
|
499
316
|
|
|
500
|
-
// PopLinkWitnessV2 ::= SEQUENCE { keyGenAlgorithm, macAlgorithm, witness }.
|
|
501
|
-
// key = keyGen(shared-secret); witness = MAC(R, key), with R carried alongside in
|
|
502
|
-
// the POP Link Random control -- PL1 makes that control mandatory in the same
|
|
503
|
-
// request, which is why the two are produced together and never separately.
|
|
504
317
|
function popLinkWitnessV2(secret, R) {
|
|
505
318
|
_assertSecret(secret, "a POP Link Witness V2 control");
|
|
506
319
|
var key = nodeCrypto.createHash("sha256").update(secret, "utf8").digest();
|
|
@@ -515,10 +328,6 @@ function popLinkWitnessV2(secret, R) {
|
|
|
515
328
|
// The operator-facing @primitive block for this function lives beside its
|
|
516
329
|
// re-export in cmc-verify.js, the pki.cmc @module home.
|
|
517
330
|
function build(spec, signer, opts) {
|
|
518
|
-
// Every caller-owned argument copied at entry and released when the call settles; see the note
|
|
519
|
-
// on the same call in x509-sign. It matters most here: the Identity Proof and POP Link witnesses
|
|
520
|
-
// are computed over the bytes this builder is about to emit, so a spec that changed in between
|
|
521
|
-
// would witness a different request than the one that goes out.
|
|
522
331
|
return guard.bytes.fixedCall(CmcError, "cmc/bad-input", [
|
|
523
332
|
[spec, "the CMC request spec"], [signer, "the signer"], [opts, "pki.cmc.build options"],
|
|
524
333
|
], _build);
|
|
@@ -530,14 +339,7 @@ async function _build(spec, signer, opts) {
|
|
|
530
339
|
throw E("cmc/bad-input", "the CMC request spec must be an object");
|
|
531
340
|
}
|
|
532
341
|
if (typeof opts !== "object" || Buffer.isBuffer(opts)) throw E("cmc/bad-input", "pki.cmc.build options must be an object");
|
|
533
|
-
// A misspelled or unsupported spec field fails open in the quietest way there is:
|
|
534
|
-
// the message builds, is signed, and simply does not carry what was asked for.
|
|
535
|
-
// That is worst for the exchange-binding fields below: a request built without
|
|
536
|
-
// them has no replay defense, and pki.cmc.verify cannot enforce a binding the
|
|
537
|
-
// client never sent, so the omission is invisible from both ends.
|
|
538
342
|
guard.identifier.assertKnownKeys(spec, KNOWN_SPEC_KEYS, E, "cmc/bad-input", "unknown spec field ");
|
|
539
|
-
// Density up front: requests is iterated by both the forEach below and the map that encodes it, and a
|
|
540
|
-
// hole would survive either (map/forEach skip it) to reach b.sequence as a native error.
|
|
541
343
|
var requests = spec.requests != null ? pkiBuild.reqDenseArray(spec.requests, "requests", E, "cmc/bad-input") : [];
|
|
542
344
|
if (!Array.isArray(requests)) throw E("cmc/bad-input", "spec.requests must be an array");
|
|
543
345
|
var callerControls = spec.controls || [];
|
|
@@ -545,17 +347,6 @@ async function _build(spec, signer, opts) {
|
|
|
545
347
|
|
|
546
348
|
var ids = makeIdAllocator();
|
|
547
349
|
|
|
548
|
-
// Every identifier the caller determines is reserved before a single generated
|
|
549
|
-
// one is handed out. There are four sources, and reserving only some of them
|
|
550
|
-
// makes acceptance depend on the order the caller wrote things in, not on
|
|
551
|
-
// the message:
|
|
552
|
-
// 1. a request's explicit bodyPartID
|
|
553
|
-
// 2. a crm arm's certReqId, which is fixed by the CertReqMsg itself
|
|
554
|
-
// 3. a cmsSequence element's bodyPartID
|
|
555
|
-
// 4. an otherMsgSequence element's bodyPartID
|
|
556
|
-
// Auto-allocation walks upward from 1, so anything minted before these were
|
|
557
|
-
// claimed could take a value the caller already spent, and the caller's own
|
|
558
|
-
// element would then be rejected as a duplicate of whatever displaced it.
|
|
559
350
|
requests.forEach(function (r, i) {
|
|
560
351
|
var fixed = fixedRequestId(r, i);
|
|
561
352
|
if (fixed != null) ids.reserve(fixed, "request " + i);
|
|
@@ -567,15 +358,9 @@ async function _build(spec, signer, opts) {
|
|
|
567
358
|
return _claimRawElement(el, ids, "an otherMsgSequence element (index " + i + ")");
|
|
568
359
|
});
|
|
569
360
|
|
|
570
|
-
// Now the requests: a fixed identifier is re-claimed as a no-op against the
|
|
571
|
-
// reservation above, and only the ones that omitted it are allocated. The
|
|
572
|
-
// reqSequence bytes this produces are what the Identity Proof witness covers.
|
|
573
361
|
var encodedRequests = requests.map(function (r, i) { return encodeRequest(r, ids, i); });
|
|
574
362
|
var reqSequenceBytes = b.sequence(encodedRequests.map(function (r) { return r.der; }));
|
|
575
363
|
|
|
576
|
-
// A renewal carries neither identity control. Refusing the combination is the
|
|
577
|
-
// point: dropping the control silently would emit a message that looks like a
|
|
578
|
-
// renewal to us and an identity-proofed request to nobody.
|
|
579
364
|
if (spec.renewal) {
|
|
580
365
|
if (spec.identityProof) {
|
|
581
366
|
throw E("cmc/bad-input",
|
|
@@ -588,13 +373,6 @@ async function _build(spec, signer, opts) {
|
|
|
588
373
|
"a renewal request carries no " + forbidden + " control (RFC 5272 sec. 3.2 (a))");
|
|
589
374
|
}
|
|
590
375
|
}
|
|
591
|
-
// A renewal is authenticated by the certificate being renewed: sec. 6.3.3 says
|
|
592
|
-
// "the outermost signature layer is created using the current signing
|
|
593
|
-
// certificate, which allows the original identity to be associated with the
|
|
594
|
-
// certification request". That identity is the whole mechanism: it is what
|
|
595
|
-
// replaces the Identity Proof this mode just refused. A key-only signer has no
|
|
596
|
-
// certificate and so carries no prior identity, leaving a message with nothing
|
|
597
|
-
// for the CA to authenticate the renewal against.
|
|
598
376
|
var renewalSigners = Array.isArray(signer) ? signer : [signer];
|
|
599
377
|
for (var si = 0; si < renewalSigners.length; si++) {
|
|
600
378
|
var rs = renewalSigners[si];
|
|
@@ -618,15 +396,6 @@ async function _build(spec, signer, opts) {
|
|
|
618
396
|
controls.push(encodeControl(_oidOf(c.type), [_der(c.value, "a control value")], ids));
|
|
619
397
|
});
|
|
620
398
|
|
|
621
|
-
// The exchange binding (RFC 5272 sec. 6.6 Transaction Identifier / Sender Nonce,
|
|
622
|
-
// sec. 6.4 Data Return). These are what pki.cmc.verify checks the response
|
|
623
|
-
// against, and a request that omits them has no replay defense, so they are
|
|
624
|
-
// named fields here and not something the caller hand-encodes into
|
|
625
|
-
// spec.controls and can silently get wrong.
|
|
626
|
-
// A named field and a hand-encoded control of the same type would emit the
|
|
627
|
-
// control twice, and a message carrying two of them has no single value the
|
|
628
|
-
// response can be bound to; this toolkit's own /fullcmc refuses exactly that.
|
|
629
|
-
// Refuse it at the source, before signing something no one can bind to.
|
|
630
399
|
_assertNoDuplicateBinding(callerControls, spec);
|
|
631
400
|
if (spec.transactionId != null) {
|
|
632
401
|
controls.push(encodeControl(OID_TRANSACTION_ID,
|
|
@@ -648,10 +417,6 @@ async function _build(spec, signer, opts) {
|
|
|
648
417
|
if (identity != null && typeof identity !== "string") {
|
|
649
418
|
throw E("cmc/bad-input", "identityProof.identity is the UTF8String the Identification control carries");
|
|
650
419
|
}
|
|
651
|
-
// The pair is emitted together when an identity is given, because the two are
|
|
652
|
-
// coupled: the Identification control tells the server which shared secret to
|
|
653
|
-
// look up, and its presence is what changes how the key is derived. Letting a
|
|
654
|
-
// caller supply one without the other would put that coupling out of reach.
|
|
655
420
|
if (identity != null) controls.push(encodeControl(OID_IDENTIFICATION, [b.utf8(identity)], ids));
|
|
656
421
|
controls.push(encodeControl(OID_IDENTITY_PROOF_V2,
|
|
657
422
|
[identityProofV2(spec.identityProof.secret, reqSequenceBytes, identity)], ids));
|
|
@@ -665,18 +430,11 @@ async function _build(spec, signer, opts) {
|
|
|
665
430
|
throw E("cmc/bad-input", "popLinkRandomBytes must be a positive integer");
|
|
666
431
|
}
|
|
667
432
|
var R = nodeCrypto.randomBytes(rBytes);
|
|
668
|
-
// Both are emitted together: PL1 makes the Random control mandatory whenever
|
|
669
|
-
// the witness is present, so producing one without the other is not an option
|
|
670
|
-
// the API offers.
|
|
671
433
|
controls.push(encodeControl(OID_POP_LINK_RANDOM, [b.octetString(R)], ids));
|
|
672
434
|
controls.push(encodeControl(O("id-cmc-popLinkWitnessV2"),
|
|
673
435
|
[popLinkWitnessV2(spec.popLink.secret, R)], ids));
|
|
674
436
|
}
|
|
675
437
|
|
|
676
|
-
// PKIData ::= SEQUENCE { controlSequence, reqSequence, cmsSequence,
|
|
677
|
-
// otherMsgSequence }: all four emitted, the unused ones empty (sec. 3.2.1).
|
|
678
|
-
// reqSequenceBytes is spliced in as the same buffer the witness was computed
|
|
679
|
-
// over, which is the whole of IP1.
|
|
680
438
|
var pkiData = b.sequence([
|
|
681
439
|
b.sequence(controls),
|
|
682
440
|
b.raw(reqSequenceBytes),
|
|
@@ -684,14 +442,6 @@ async function _build(spec, signer, opts) {
|
|
|
684
442
|
b.sequence(otherMsgSequence),
|
|
685
443
|
]);
|
|
686
444
|
|
|
687
|
-
// Read the assembled message back through the shipped parser before signing it.
|
|
688
|
-
// Every arm here splices caller-supplied DER (a tcr's CertificationRequest, a
|
|
689
|
-
// cmsSequence TaggedContentInfo, an otherMsg's value), and checking each shape
|
|
690
|
-
// by hand would restate the parser's rules in a second place, where they would
|
|
691
|
-
// drift and where a newly added arm would silently miss them. One round-trip
|
|
692
|
-
// covers them all, including arms not yet written: whatever the parser refuses,
|
|
693
|
-
// this refuses at build time, so no message goes out whose recipient (this
|
|
694
|
-
// toolkit's own decoder included) cannot read it.
|
|
695
445
|
try {
|
|
696
446
|
cmcFmt.parsePkiData(pkiData);
|
|
697
447
|
} catch (e) {
|
|
@@ -702,28 +452,12 @@ async function _build(spec, signer, opts) {
|
|
|
702
452
|
|
|
703
453
|
_assertKeyOnlySigner(signer, requests);
|
|
704
454
|
|
|
705
|
-
// The signer is copied for the same reason the message was assembled at the
|
|
706
|
-
// call: cms.sign reads `key` inside its own promise chain, so a caller who
|
|
707
|
-
// swaps signer.key on the next line would have the request signed by the
|
|
708
|
-
// replacement while the original certificate stays embedded: a message whose
|
|
709
|
-
// signature does not belong to the certificate beside it.
|
|
710
|
-
//
|
|
711
|
-
// cms.sign resolves eContentType through oid.byName, so it takes the registry
|
|
712
|
-
// name; handing it the dotted value resolves to undefined.
|
|
713
|
-
// Copying a private key makes a second copy of a secret, so it is cleared once signing has
|
|
714
|
-
// settled and never left for the collector, the same discipline the MAC keys above follow.
|
|
715
|
-
// The clear covers the rejecting path too: a wrong or malformed key is the case a caller can
|
|
716
|
-
// reach, and a success-only wipe would keep the secret exactly when it matters.
|
|
717
455
|
var ownedKeyBytes = [];
|
|
718
456
|
var copiedSigner = _copySigners(signer, ownedKeyBytes);
|
|
719
457
|
function _wipeOwnedKeys() {
|
|
720
458
|
if (ownedKeyBytes.length) guard.secret.zeroizeAll(ownedKeyBytes, CmcError, "cmc/bad-input", "the signer key copy");
|
|
721
459
|
ownedKeyBytes.length = 0;
|
|
722
460
|
}
|
|
723
|
-
// Verify every embedded tcr proof-of-possession BEFORE signing: do not sign a request the CA will
|
|
724
|
-
// reject. `await`, not a `.then` chain, so the PoP gate and the sign are sequenced through native
|
|
725
|
-
// promise jobs a co-resident cannot swap; the finally clears the signer key copy on every path,
|
|
726
|
-
// the rejecting one included.
|
|
727
461
|
try {
|
|
728
462
|
await _assertTcrPops(requests);
|
|
729
463
|
return await cmsSign.sign(pkiData, copiedSigner, { eContentType: "id-cct-PKIData", pem: opts.pem });
|
|
@@ -732,15 +466,6 @@ async function _build(spec, signer, opts) {
|
|
|
732
466
|
}
|
|
733
467
|
}
|
|
734
468
|
|
|
735
|
-
// Each signer descriptor, and every byte value in it. cms.sign reads these inside
|
|
736
|
-
// its own promise chain, so both levels matter: re-pointing signer.key is one way
|
|
737
|
-
// to change who signs, and rewriting the PKCS#8 buffer it already points at is
|
|
738
|
-
// the other. A CryptoKey handle is passed through as-is: it is an opaque
|
|
739
|
-
// reference the caller is meant to share, and there is nothing to copy.
|
|
740
|
-
// `owned` collects the buffers this function allocated that hold private key material, so the
|
|
741
|
-
// caller can clear them once signing has settled. Only the key is listed: the other copied
|
|
742
|
-
// values are certificates and identifiers, which are public and outlive the call by design.
|
|
743
|
-
// The caller's own key is never written to; only our copy of it is.
|
|
744
469
|
function _copySigners(signer, owned) {
|
|
745
470
|
function one(s) {
|
|
746
471
|
if (!s || typeof s !== "object") return s;
|
|
@@ -751,25 +476,13 @@ function _copySigners(signer, owned) {
|
|
|
751
476
|
}
|
|
752
477
|
return out;
|
|
753
478
|
}
|
|
754
|
-
// One level further for a composite key, which is an object of component keys
|
|
755
|
-
// ({ mldsa, trad }) and not a buffer. Copying only the top level would leave
|
|
756
|
-
// those components, the actual PKCS#8 bytes that sign, the caller's to
|
|
757
|
-
// replace or zeroize while cms.sign reads them in a later turn. A CryptoKey is
|
|
758
|
-
// an opaque handle and is passed through as-is.
|
|
759
479
|
function copyValue(v, isSecret) {
|
|
760
480
|
if (!isSecret) {
|
|
761
|
-
// A public signer field (cert / spki / keyIdentifier DER): any BufferSource is
|
|
762
|
-
// snapshotted to a private Buffer so a caller cannot mutate it in a later turn;
|
|
763
|
-
// an ArrayBuffer or DataView is accepted, not silently turned into an empty object.
|
|
764
|
-
// A non-byte descriptor or scalar (a parsed certificate, a CryptoKey handle) passes
|
|
765
|
-
// through untouched.
|
|
766
481
|
if (guard.bytes.isByteSource(v)) return guard.bytes.snapshotSource(v, CmcError, "cmc/bad-input", "a signer field");
|
|
767
482
|
return v;
|
|
768
483
|
}
|
|
769
|
-
// The private key stays on the ownership contract: a Buffer is copied, a Uint8Array is
|
|
770
|
-
// copied, and every copy is collected so it can be zeroized once signing settles.
|
|
771
484
|
if (Buffer.isBuffer(v) || v instanceof Uint8Array) return _own(Buffer.from(v), true); // allow:byte-source-narrow -- private key bytes (Buffer borrowed / Uint8Array copied+wiped)
|
|
772
|
-
if (!v || typeof v !== "object" || typeof v.type === "string") return v;
|
|
485
|
+
if (!v || typeof v !== "object" || typeof v.type === "string") return v;
|
|
773
486
|
var c = {}, ck;
|
|
774
487
|
for (ck in v) {
|
|
775
488
|
if (!_hasOwn(v, ck)) continue;
|
|
@@ -778,8 +491,6 @@ function _copySigners(signer, owned) {
|
|
|
778
491
|
}
|
|
779
492
|
return c;
|
|
780
493
|
}
|
|
781
|
-
// A composite key's components are listed too: each is PKCS#8 bytes that actually sign, so
|
|
782
|
-
// leaving them out would wipe the wrapper and keep the secrets it was wrapping.
|
|
783
494
|
function _own(buf, isSecret) {
|
|
784
495
|
if (isSecret && owned) owned.push(buf);
|
|
785
496
|
return buf;
|
|
@@ -787,38 +498,11 @@ function _copySigners(signer, owned) {
|
|
|
787
498
|
return Array.isArray(signer) ? signer.map(one) : one(signer);
|
|
788
499
|
}
|
|
789
500
|
|
|
790
|
-
|
|
791
|
-
* RFC 5272 sec. 3.2, the three rules that apply when the signature is made with
|
|
792
|
-
* the private key of a certification request the message carries, and not with
|
|
793
|
-
* an already-certified key:
|
|
794
|
-
*
|
|
795
|
-
* a. that request MUST include a Subject Key Identifier extension;
|
|
796
|
-
* b. the subjectKeyIdentifier form of SignerIdentifier MUST be used;
|
|
797
|
-
* c. its value MUST be the Subject Key Identifier that request specifies.
|
|
798
|
-
*
|
|
799
|
-
* (b) is structural and pki.cms.sign already emits only that form for a key-only
|
|
800
|
-
* signer. (a) and (c) are about agreement between the signer and the requests
|
|
801
|
-
* beside it, which is checked here: an identifier the requests never declare
|
|
802
|
-
* leaves the CA unable to tie the signature to the key being enrolled, so the
|
|
803
|
-
* request is signed but unusable, and it is the producer's job to catch that,
|
|
804
|
-
* not the CA's to guess.
|
|
805
|
-
*
|
|
806
|
-
* This lives here, not in pki.cms.sign, because it is a CMC rule: only this
|
|
807
|
-
* layer knows the content is a PKIData and which requests are in it. Teaching the
|
|
808
|
-
* generic CMS signer to parse CMC content would put the protocol's rules in the
|
|
809
|
-
* wrong module.
|
|
810
|
-
*
|
|
811
|
-
* A signer that carries a certificate is untouched: it identifies itself by that
|
|
812
|
-
* certificate, and the clause does not reach it.
|
|
813
|
-
*/
|
|
501
|
+
|
|
814
502
|
function _assertKeyOnlySigner(signer, requests) {
|
|
815
503
|
var all = Array.isArray(signer) ? signer : [signer];
|
|
816
504
|
var keyOnly = all.filter(function (x) { return x && x.cert == null && x.spki != null; });
|
|
817
505
|
if (!keyOnly.length) return;
|
|
818
|
-
// Sec. 3.2's fourth rule: "If the request key is used for signing, there MUST be
|
|
819
|
-
// only one SignerInfo in the SignedData." A request key has no certificate and
|
|
820
|
-
// so no independent identity; letting it sign alongside others would produce a
|
|
821
|
-
// message whose signer set the CA cannot reason about.
|
|
822
506
|
if (all.length !== 1) {
|
|
823
507
|
throw E("cmc/bad-signer",
|
|
824
508
|
"a Full PKI Request signed with a certification request's own key MUST carry exactly one SignerInfo, " +
|
|
@@ -827,7 +511,6 @@ function _assertKeyOnlySigner(signer, requests) {
|
|
|
827
511
|
keyOnly.forEach(function (so) { _assertKeyOnlySignerBinding(so, requests); });
|
|
828
512
|
}
|
|
829
513
|
|
|
830
|
-
// The sec. 3.2a/3.2c binding for a single key-only signer.
|
|
831
514
|
function _assertKeyOnlySignerBinding(so, requests) {
|
|
832
515
|
var id = so.keyIdentifier;
|
|
833
516
|
if (!guard.bytes.isByteSource(id)) {
|
|
@@ -842,24 +525,14 @@ function _assertKeyOnlySignerBinding(so, requests) {
|
|
|
842
525
|
var declaredSki = false, sawRequest = false;
|
|
843
526
|
for (var i = 0; i < requests.length; i++) {
|
|
844
527
|
var req = requests[i];
|
|
845
|
-
// Both key-bearing arms: sec. 3.2 says the signing key may belong to a request
|
|
846
|
-
// "included in the TaggedRequest tcr or crm fields", so reading only PKCS#10
|
|
847
|
-
// would reject every conforming CRMF enrollment of a brand-new key.
|
|
848
528
|
var declaredBy;
|
|
849
529
|
try {
|
|
850
530
|
declaredBy = req && req.tcr != null ? _csrKeyIdentity(req.tcr)
|
|
851
531
|
: (req && req.crm != null ? _crmKeyIdentity(req.crm) : null);
|
|
852
|
-
} catch (_e) { declaredBy = null; }
|
|
532
|
+
} catch (_e) { declaredBy = null; }
|
|
853
533
|
if (req && (req.tcr != null || req.crm != null)) sawRequest = true;
|
|
854
534
|
if (!declaredBy || !declaredBy.ski) continue;
|
|
855
535
|
declaredSki = true;
|
|
856
|
-
// The identifier and the key together. Matching the identifier alone would accept a
|
|
857
|
-
// signer holding key A while the request it points at asks to certify key B:
|
|
858
|
-
// the SKI is caller-chosen, so the two can be made to agree while the keys do
|
|
859
|
-
// not, and a CA resolving the SID to the requested key could then not verify
|
|
860
|
-
// the carrier at all. The identifier says which request; the key is what makes
|
|
861
|
-
// the claim true. The comparison runs through the toolkit's shared byte
|
|
862
|
-
// equality; neither value here is secret, but there is one way to compare.
|
|
863
536
|
if (guard.crypto.constantTimeEqual(declaredBy.ski, idBytes) &&
|
|
864
537
|
declaredBy.spki && guard.crypto.constantTimeEqual(declaredBy.spki, signerSpki)) {
|
|
865
538
|
return;
|
|
@@ -880,14 +553,8 @@ function _assertKeyOnlySignerBinding(so, requests) {
|
|
|
880
553
|
"to be the one asking for this very public key (RFC 5272 sec. 3.2)");
|
|
881
554
|
}
|
|
882
555
|
|
|
883
|
-
// { ski, spki } for a CRMF certification request: the identifier it declares and
|
|
884
|
-
// the key it asks to have certified. The CertTemplate carries extensions directly,
|
|
885
|
-
// so there is no attribute wrapper to unwrap; the extnValue is the same DER
|
|
886
|
-
// SubjectKeyIdentifier OCTET STRING.
|
|
887
556
|
function _crmKeyIdentity(crmDer) {
|
|
888
557
|
var msg = _asCertReqMsg(_der(crmDer, "a crm CertReqMsg"));
|
|
889
|
-
// pki.schema.crmf reads CertReqMessages (SEQUENCE OF CertReqMsg); one message is
|
|
890
|
-
// wrapped here and decoded there, so the CRMF rules stay in the CRMF parser.
|
|
891
558
|
var msgs = crmf.parse(b.sequence([msg])).messages;
|
|
892
559
|
var tmpl = msgs && msgs[0] && msgs[0].certReq && msgs[0].certReq.certTemplate;
|
|
893
560
|
var found = null;
|
|
@@ -895,19 +562,12 @@ function _crmKeyIdentity(crmDer) {
|
|
|
895
562
|
if (e.name !== "subjectKeyIdentifier" || found) return;
|
|
896
563
|
found = asn1.read.octetString(asn1.decode(e.value));
|
|
897
564
|
});
|
|
898
|
-
// RFC 4211 sec. 4.1: the requested key may live in the signature POP's
|
|
899
|
-
// POPOSigningKeyInput instead of the CertTemplate, and the CRMF parser surfaces
|
|
900
|
-
// it there. Reading only the template would refuse a key-only signer whose
|
|
901
|
-
// request is perfectly conforming.
|
|
902
565
|
var msg0 = msgs && msgs[0];
|
|
903
566
|
var pk = (tmpl && tmpl.publicKey) ||
|
|
904
567
|
(msg0 && msg0.popo && msg0.popo.poposkInput && msg0.popo.poposkInput.publicKey);
|
|
905
568
|
return { ski: found, spki: Buffer.isBuffer(pk) ? pk : (pk && pk.bytes) || null };
|
|
906
569
|
}
|
|
907
570
|
|
|
908
|
-
// { ski, spki } for a PKCS#10 certification request. Read through the shipped CSR
|
|
909
|
-
// parser and the extensionRequest attribute it already decodes, so the extension
|
|
910
|
-
// rules are not restated here.
|
|
911
571
|
function _csrKeyIdentity(csrDer) {
|
|
912
572
|
var parsed = csr.parse(_der(csrDer, "a tcr certificationRequest"));
|
|
913
573
|
var found = null;
|
|
@@ -915,8 +575,6 @@ function _csrKeyIdentity(csrDer) {
|
|
|
915
575
|
if (a.name !== "extensionRequest") return;
|
|
916
576
|
(a.extensions || []).forEach(function (e) {
|
|
917
577
|
if (e.name !== "subjectKeyIdentifier" || found) return;
|
|
918
|
-
// extnValue holds a DER SubjectKeyIdentifier ::= OCTET STRING; the identifier
|
|
919
|
-
// is its contents, not the TLV.
|
|
920
578
|
found = asn1.read.octetString(asn1.decode(e.value));
|
|
921
579
|
});
|
|
922
580
|
});
|