@blamejs/pki 0.8.31 → 0.8.49
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 +401 -0
- package/README.md +66 -15
- package/bin/pki.js +716 -30
- package/examples/issue-a-certificate.js +90 -0
- package/examples/post-quantum.js +66 -0
- package/examples/read-a-certificate.js +56 -0
- package/examples/revoke-and-check.js +86 -0
- package/examples/sign-and-verify-cms.js +63 -0
- package/index.js +18 -5
- package/lib/acme.js +223 -74
- package/lib/alt-sig.js +441 -0
- package/lib/asn1-der.js +48 -25
- package/lib/attrcert-sign.js +27 -19
- package/lib/cbor-det.js +24 -14
- package/lib/cmc-build.js +9 -2
- package/lib/cmc-verify.js +14 -3
- package/lib/cmp-build.js +17 -5
- package/lib/cmp-session.js +24 -11
- package/lib/cmp-verify.js +28 -19
- package/lib/cms-compress.js +3 -1
- package/lib/cms-decrypt.js +102 -7
- package/lib/cms-encrypt.js +59 -7
- package/lib/cms-sign.js +31 -9
- package/lib/cms-verify.js +22 -18
- package/lib/composite-kem.js +30 -18
- package/lib/composite-sig.js +29 -14
- package/lib/constants.js +26 -0
- package/lib/crl-sign.js +111 -19
- package/lib/crmf-sign.js +167 -15
- package/lib/csr-sign.js +117 -6
- package/lib/ct.js +1008 -58
- package/lib/est.js +29 -16
- package/lib/framework-error.js +12 -0
- package/lib/guard-async.js +1 -1
- package/lib/guard-bytes.js +103 -20
- package/lib/guard-crypto.js +205 -3
- package/lib/guard-encoding.js +6 -1
- package/lib/guard-identifier.js +55 -8
- package/lib/guard-intrinsic.js +43 -0
- package/lib/guard-json.js +122 -9
- package/lib/guard-limits.js +12 -1
- package/lib/guard-list.js +63 -8
- package/lib/guard-name.js +167 -20
- package/lib/guard-range.js +43 -5
- package/lib/guard-text.js +41 -0
- package/lib/guard-verdict.js +39 -8
- package/lib/hpke.js +688 -42
- package/lib/http-transport.js +156 -4
- package/lib/identity-match.js +4 -4
- package/lib/inspect.js +33 -6
- package/lib/jose.js +93 -25
- package/lib/key.js +18 -13
- package/lib/lint.js +165 -1
- package/lib/merkle.js +233 -27
- package/lib/ocsp.js +27 -14
- package/lib/oid.js +14 -4
- package/lib/path-validate.js +143 -61
- package/lib/pkcs12-build.js +7 -2
- package/lib/pki-build.js +31 -7
- package/lib/possession.js +575 -0
- package/lib/related-cert.js +708 -0
- package/lib/revocation-fetch.js +6 -6
- package/lib/scep.js +15 -5
- package/lib/schema-c509.js +5 -2
- package/lib/schema-cmc.js +1 -1
- package/lib/schema-cmp.js +8 -8
- package/lib/schema-cms.js +2 -15
- package/lib/schema-csr.js +69 -0
- package/lib/schema-engine.js +10 -3
- package/lib/schema-pkix.js +228 -2
- package/lib/schema-tsp.js +7 -6
- package/lib/sign-scheme.js +22 -9
- package/lib/sigstore.js +684 -168
- package/lib/sleep.js +8 -1
- package/lib/smime.js +22 -1
- package/lib/tlog.js +1235 -0
- package/lib/tls-cert-compress.js +25 -8
- package/lib/tsp-sign.js +120 -35
- package/lib/tuf.js +1395 -0
- package/lib/validator-tpm.js +4 -1
- package/lib/verify-seam.js +7 -1
- package/lib/webauthn-mds.js +41 -4
- package/lib/webauthn.js +43 -25
- package/lib/webcrypto.js +48 -46
- package/lib/x509-sign.js +225 -19
- package/package.json +2 -1
- package/sbom.cdx.json +6 -6
package/lib/composite-sig.js
CHANGED
|
@@ -10,6 +10,21 @@ var validator = require("./validator-all");
|
|
|
10
10
|
var edwardsPoint = require("./edwards-point");
|
|
11
11
|
var _verdictOf = require("./guard-verdict").of;
|
|
12
12
|
var subtle = webcrypto.webcrypto.subtle;
|
|
13
|
+
/** @internal The verify operation, captured at load. A composite signature is verified in two halves
|
|
14
|
+
* and each must hold, so a replacement answering true for either accepts a signature that half of the
|
|
15
|
+
* pair never made, which is the guarantee a composite exists to give. */
|
|
16
|
+
var _subtleVerify = require("./guard-intrinsic").uncurry(subtle.verify);
|
|
17
|
+
/** @internal Every refusal this module returns is a RESOLVED promise carrying a negative verdict, and
|
|
18
|
+
* `Promise.resolve` builds from its RECEIVER. Read from the global binding at the call, the refusal is
|
|
19
|
+
* constructed by whatever that binding holds, so a replacement settling truthy turns each of these
|
|
20
|
+
* into an acceptance. The constructor and the operation are both taken at load. */
|
|
21
|
+
var _intrinsic = require("./guard-intrinsic");
|
|
22
|
+
var _Promise = _intrinsic.Promise;
|
|
23
|
+
var _promiseResolve = _intrinsic.uncurry(_intrinsic.promiseResolve);
|
|
24
|
+
/** @internal The aggregator is the same defect one step out: the composite verdict is `ok` only when
|
|
25
|
+
* BOTH component verifications resolve true, so a replaced `all` that resolves `[true, true]` without
|
|
26
|
+
* awaiting either half reports a forged signature as verified. */
|
|
27
|
+
var _promiseAll = _intrinsic.uncurry(_intrinsic.promiseAll);
|
|
13
28
|
var _b = asn1.build;
|
|
14
29
|
|
|
15
30
|
function _codeOf(e, fallback) {
|
|
@@ -69,10 +84,10 @@ function compositeMprime(d, phBuf, ctx) {
|
|
|
69
84
|
|
|
70
85
|
function _verifyTradComponent(trad, tradPK, tradSig, mprime, E, badSig) {
|
|
71
86
|
if (trad.ec) {
|
|
72
|
-
if (tradPK.length < 1 || tradPK[0] !== 0x04) return
|
|
87
|
+
if (tradPK.length < 1 || tradPK[0] !== 0x04) return _promiseResolve(_Promise, false);
|
|
73
88
|
return subtle.importKey("spki", _ecSpki(trad.ec, tradPK), { name: "ECDSA" }, false, ["verify"]).then(function (k) {
|
|
74
89
|
var p1363 = validator.sig.ecdsaDerToP1363(tradSig, trad.ec, E, badSig);
|
|
75
|
-
return subtle
|
|
90
|
+
return _subtleVerify(subtle, { name: "ECDSA", hash: trad.hash }, k, p1363, mprime);
|
|
76
91
|
});
|
|
77
92
|
}
|
|
78
93
|
if (trad.eddsa) {
|
|
@@ -80,21 +95,21 @@ function _verifyTradComponent(trad, tradPK, tradSig, mprime, E, badSig) {
|
|
|
80
95
|
throw new E(badSig, "the composite EdDSA component public key is not a valid, full-order Edwards point");
|
|
81
96
|
}
|
|
82
97
|
return subtle.importKey("spki", _edSpki(trad.eddsa, tradPK), { name: trad.eddsa }, false, ["verify"])
|
|
83
|
-
.then(function (k) { return subtle
|
|
98
|
+
.then(function (k) { return _subtleVerify(subtle, { name: trad.eddsa }, k, tradSig, mprime); });
|
|
84
99
|
}
|
|
85
100
|
if (trad.rsaPss || trad.rsaPkcs1) {
|
|
86
101
|
var bits;
|
|
87
102
|
try { bits = _rsaModulusBits(tradPK); }
|
|
88
|
-
catch (_e) { return
|
|
89
|
-
if (bits !== trad.rsaBits) return
|
|
103
|
+
catch (_e) { return _promiseResolve(_Promise, false); }
|
|
104
|
+
if (bits !== trad.rsaBits) return _promiseResolve(_Promise, false);
|
|
90
105
|
if (trad.rsaPss) {
|
|
91
106
|
return subtle.importKey("spki", _rsaSpki(tradPK), { name: "RSA-PSS", hash: trad.hash }, false, ["verify"])
|
|
92
|
-
.then(function (k) { return subtle
|
|
107
|
+
.then(function (k) { return _subtleVerify(subtle, { name: "RSA-PSS", saltLength: trad.salt }, k, tradSig, mprime); });
|
|
93
108
|
}
|
|
94
109
|
return subtle.importKey("spki", _rsaSpki(tradPK), { name: "RSASSA-PKCS1-v1_5", hash: trad.hash }, false, ["verify"])
|
|
95
|
-
.then(function (k) { return subtle
|
|
110
|
+
.then(function (k) { return _subtleVerify(subtle, { name: "RSASSA-PKCS1-v1_5" }, k, tradSig, mprime); });
|
|
96
111
|
}
|
|
97
|
-
return
|
|
112
|
+
return _promiseResolve(_Promise, false);
|
|
98
113
|
}
|
|
99
114
|
|
|
100
115
|
/** @internal Every verdict this returns carries its own `then`: resolving a promise reads that property
|
|
@@ -102,7 +117,7 @@ function _verifyTradComponent(trad, tradPK, tradSig, mprime, E, badSig) {
|
|
|
102
117
|
* caller a different object than the one the comparison produced (see guard-verdict). */
|
|
103
118
|
function compositeVerify(spkiBytes, sigBytes, message, d, E, unsupported, badSig) {
|
|
104
119
|
if (d.trad.unsupported) {
|
|
105
|
-
return
|
|
120
|
+
return _promiseResolve(_Promise, _verdictOf({ ok: false, code: unsupported,
|
|
106
121
|
error: new E(unsupported, "composite " + d.name + ": " + d.trad.unsupported) }));
|
|
107
122
|
}
|
|
108
123
|
var rawKey;
|
|
@@ -110,9 +125,9 @@ function compositeVerify(spkiBytes, sigBytes, message, d, E, unsupported, badSig
|
|
|
110
125
|
var bs = asn1.read.bitString(asn1.decode(spkiBytes).children[1]);
|
|
111
126
|
if (bs.unusedBits !== 0) throw new E(badSig, "composite subjectPublicKey has unused bits");
|
|
112
127
|
rawKey = bs.bytes;
|
|
113
|
-
} catch (e) { return
|
|
128
|
+
} catch (e) { return _promiseResolve(_Promise, _verdictOf({ ok: false, code: _codeOf(e, badSig), error: e })); }
|
|
114
129
|
if (rawKey.length <= d.mldsaPk || sigBytes.length <= d.mldsaSig) {
|
|
115
|
-
return
|
|
130
|
+
return _promiseResolve(_Promise, _verdictOf({ ok: false, code: badSig,
|
|
116
131
|
error: new E(badSig, "composite key/signature shorter than the fixed ML-DSA component") }));
|
|
117
132
|
}
|
|
118
133
|
var mldsaPK = rawKey.subarray(0, d.mldsaPk), tradPK = rawKey.subarray(d.mldsaPk);
|
|
@@ -120,9 +135,9 @@ function compositeVerify(spkiBytes, sigBytes, message, d, E, unsupported, badSig
|
|
|
120
135
|
return subtle.digest({ name: d.ph }, message).then(function (phBuf) {
|
|
121
136
|
var mprime = compositeMprime(d, phBuf);
|
|
122
137
|
var mldsaP = subtle.importKey("spki", _mldsaSpki(d.mldsaOid, mldsaPK), { name: d.mldsa }, false, ["verify"])
|
|
123
|
-
.then(function (mk) { return subtle
|
|
138
|
+
.then(function (mk) { return _subtleVerify(subtle, { name: d.mldsa, context: d.label }, mk, mldsaSig, mprime); });
|
|
124
139
|
var tradP = _verifyTradComponent(d.trad, tradPK, tradSig, mprime, E, badSig);
|
|
125
|
-
return
|
|
140
|
+
return _promiseAll(_Promise, [mldsaP, tradP]).then(function (r) { return _verdictOf({ ok: r[0] === true && r[1] === true }); });
|
|
126
141
|
}).catch(function (e) { return _verdictOf({ ok: false, code: _codeOf(e, badSig), error: e }); });
|
|
127
142
|
}
|
|
128
143
|
|
|
@@ -141,7 +156,7 @@ function compositeSign(d, keys, message, ctx) {
|
|
|
141
156
|
var mldsaP = subtle.importKey("pkcs8", keys.mldsa, { name: d.mldsa }, false, ["sign"])
|
|
142
157
|
.then(function (k) { return subtle.sign({ name: d.mldsa, context: d.label }, k, mprime); });
|
|
143
158
|
var tradP = _signTradComponent(d.trad, keys.trad, mprime);
|
|
144
|
-
return
|
|
159
|
+
return _promiseAll(_Promise, [mldsaP, tradP]).then(function (s) { return Buffer.concat([Buffer.from(s[0]), Buffer.from(s[1])]); });
|
|
145
160
|
});
|
|
146
161
|
}
|
|
147
162
|
function _signTradComponent(trad, pkcs8, mprime) {
|
package/lib/constants.js
CHANGED
|
@@ -114,6 +114,20 @@ var LIMITS = Object.assign(Object.create(null), {
|
|
|
114
114
|
DER_MAX_ITEMS: 4000000,
|
|
115
115
|
DUMP_MAX_NODES: 65536,
|
|
116
116
|
DUMP_MAX_VALUE_BYTES: 256,
|
|
117
|
+
/** @internal The values one deep copy of a caller's object may visit. A copy visits every reference
|
|
118
|
+
* independently, so a value holding the same subobject in two places costs twice, and depth alone
|
|
119
|
+
* bounds nothing: nested 19 levels with each level sharing its child expands to half a million values.
|
|
120
|
+
* The figure bounds the WORST case as well as admitting the real one, which is why it is not larger.
|
|
121
|
+
* MEASURED: a 1000-record trust-anchor list is about 9000 values and copies in 54 ms, 10000 records in
|
|
122
|
+
* 482 ms, and this bound is reached in about 400 ms, so a hostile value costs that much once instead of
|
|
123
|
+
* growing without limit. An options bag or a key is tens of values. */
|
|
124
|
+
DEEP_COPY_MAX_NODES: 65536,
|
|
125
|
+
/** @internal The certificates a CT log's accepted-roots response may carry. A configured log is trusted
|
|
126
|
+
* for its own signatures and not for its resource use, and the response body cap bounds the BYTES while
|
|
127
|
+
* saying nothing about the element count: a body well under it carries about a million empty strings,
|
|
128
|
+
* each of which becomes its own Buffer. The figure is far above any real log, the public ones listing a
|
|
129
|
+
* few hundred roots. */
|
|
130
|
+
CT_MAX_ROOTS: 4096,
|
|
117
131
|
NAMED_SCAN_MAX_ELEMENTS: 16777216,
|
|
118
132
|
MAX_DECODE_DEPTH_CEILING: 256,
|
|
119
133
|
PEM_MAX_BYTES: BYTES.mib(16),
|
|
@@ -149,6 +163,7 @@ var LIMITS = Object.assign(Object.create(null), {
|
|
|
149
163
|
CMP_KEM_MAX_KEY_BYTES: 1024,
|
|
150
164
|
CMP_KEM_MIN_KEY_BYTES: 20,
|
|
151
165
|
MERKLE_MAX_PROOF_NODES: 65,
|
|
166
|
+
MERKLE_MAX_LEAVES: 1048576,
|
|
152
167
|
PROXY_MAX_TLS_ANCHORS: 256,
|
|
153
168
|
PATH_MAX_CERTS: 100,
|
|
154
169
|
PATH_MAX_POLICY_NODES: 4096,
|
|
@@ -168,6 +183,8 @@ var LIMITS = Object.assign(Object.create(null), {
|
|
|
168
183
|
JSON_MAX_BYTES: BYTES.mib(1),
|
|
169
184
|
JSON_MAX_DEPTH: 32,
|
|
170
185
|
CT_LOG_LIST_MAX_BYTES: BYTES.mib(4),
|
|
186
|
+
CT_MAX_ENTRIES_PER_REQUEST: 1024,
|
|
187
|
+
TUF_MAX_ROOT_CHAIN: 1024,
|
|
171
188
|
PBKDF2_MAX_ITERATIONS: 10000000,
|
|
172
189
|
PBKDF2_MAX_SALT: 1024,
|
|
173
190
|
MIME_MAX_BYTES: BYTES.mib(16),
|
|
@@ -175,6 +192,15 @@ var LIMITS = Object.assign(Object.create(null), {
|
|
|
175
192
|
COMPRESS_MAX_BYTES: BYTES.mib(16),
|
|
176
193
|
TLS_CERT_MSG_MAX_BYTES: BYTES.mib(16) - 1,
|
|
177
194
|
TLS_CERT_MAX_ENTRIES: 100,
|
|
195
|
+
TLS_MAX_PEER_CHAIN: 16,
|
|
196
|
+
TLOG_MAX_NOTE_BYTES: BYTES.mib(1),
|
|
197
|
+
TLOG_MAX_SIGNATURES: 64,
|
|
198
|
+
TLOG_MAX_EXTENSION_LINES: 64,
|
|
199
|
+
TLOG_MAX_ENTRY_BUNDLE_BYTES: BYTES.mib(16),
|
|
200
|
+
/** @internal A tile is 256 wide, so an entry bundle covers at most that many entries (C2SP tlog-tiles).
|
|
201
|
+
* The byte cap alone does not bound the COUNT: a megabyte of zero bytes is half a million zero-length
|
|
202
|
+
* entries, each one an object, so the permitted input allocates hundreds of megabytes. */
|
|
203
|
+
TLOG_MAX_ENTRY_BUNDLE_ENTRIES: 256,
|
|
178
204
|
MDS_BLOB_MAX_BYTES: BYTES.mib(32),
|
|
179
205
|
MDS_BLOB_HEADER_MAX_BYTES: BYTES.kib(256),
|
|
180
206
|
MDS_BLOB_SIG_MAX_BYTES: BYTES.kib(8),
|
package/lib/crl-sign.js
CHANGED
|
@@ -25,6 +25,11 @@ var signScheme = require("./sign-scheme");
|
|
|
25
25
|
var guard = require("./guard-all");
|
|
26
26
|
var intrinsic = require("./guard-intrinsic");
|
|
27
27
|
var _hasOwn = intrinsic.hasOwn, _bufferEquals = intrinsic.bufferEquals;
|
|
28
|
+
var _arrSlice = intrinsic.arraySlice, _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
29
|
+
/** @internal The RECEIVER for the captured `Promise.resolve`, captured too. `resolve` builds through
|
|
30
|
+
* its receiver, so handing it the live `globalThis.Promise` put the capture back under a constructor a
|
|
31
|
+
* replacement could take over and decide what the awaited value became. */
|
|
32
|
+
var _Promise = intrinsic.Promise;
|
|
28
33
|
var frameworkError = require("./framework-error");
|
|
29
34
|
var pkix = require("./schema-pkix");
|
|
30
35
|
var schema = require("./schema-engine");
|
|
@@ -53,7 +58,8 @@ var _encodeName = _b.encodeName, _isEmptyName = _b.isEmptyName, _reqDer = _b.req
|
|
|
53
58
|
_assertValidSpki = _b.assertValidSpki, _assertValidExtension = _b.assertValidExtension,
|
|
54
59
|
_timeDer = _b.timeDer, _ext = _b.ext, _extAki = _b.extAki, _spkiKeyId = _b.spkiKeyId,
|
|
55
60
|
_serialInteger = _b.serialInteger, _encodeGeneralName = _b.encodeGeneralName,
|
|
56
|
-
_certLikeFromSpki = _b.certLikeFromSpki, _assertSignatureVerifies = _b.assertSignatureVerifies
|
|
61
|
+
_certLikeFromSpki = _b.certLikeFromSpki, _assertSignatureVerifies = _b.assertSignatureVerifies,
|
|
62
|
+
_altPublicKeyValueOf = _b.altPublicKeyValueOf;
|
|
57
63
|
var EXT_DECODERS = pkix.certExtensionDecoders(NS).byOid;
|
|
58
64
|
|
|
59
65
|
/** @internal A pre-encoded extension is admitted on the shape of its Extension SEQUENCE, which says
|
|
@@ -84,8 +90,11 @@ var KNOWN_SPEC_KEYS = intrinsic.assign(intrinsic.create(null), {
|
|
|
84
90
|
var KNOWN_SPEC_KEYS_ISSUER_NAMED = intrinsic.assign(intrinsic.create(null), {
|
|
85
91
|
thisUpdate: 1, nextUpdate: 1, revoked: 1, crlNumber: 1, extensions: 1,
|
|
86
92
|
});
|
|
87
|
-
|
|
88
|
-
|
|
93
|
+
/** @internal `altKey` and `altPublicKey` are the issuer's ALTERNATIVE signing key and its public half.
|
|
94
|
+
* ITU-T X.509 (2019) clause 7.10.3 gives a CRL the same alternative-signature procedure a certificate
|
|
95
|
+
* gets, with the same two extensions. */
|
|
96
|
+
var KNOWN_ISSUER_CERT_KEYS = intrinsic.assign(intrinsic.create(null), { key: 1, cert: 1, altKey: 1, altPublicKey: 1 });
|
|
97
|
+
var KNOWN_ISSUER_EXPLICIT_KEYS = intrinsic.assign(intrinsic.create(null), { key: 1, name: 1, publicKey: 1, altKey: 1, altPublicKey: 1 });
|
|
89
98
|
var KNOWN_SIGN_OPTS = intrinsic.assign(intrinsic.create(null), { digestAlgorithm: 1, pem: 1, pss: 1, profile: 1 });
|
|
90
99
|
|
|
91
100
|
/** @internal The signature the build-time gate lints against, standing in for one that does not exist
|
|
@@ -214,7 +223,7 @@ function _freshestValue(spec) {
|
|
|
214
223
|
function _aiaValue(spec) {
|
|
215
224
|
if (!Array.isArray(spec) || !spec.length) throw _err("crl/bad-input", "authorityInfoAccess must be a non-empty array of caIssuers GeneralNames");
|
|
216
225
|
var caIssuers = O("caIssuers");
|
|
217
|
-
return b.sequence(
|
|
226
|
+
return b.sequence(guard.list.copyMap(spec, function (gn) { return b.sequence([b.oid(caIssuers), _encodeGeneralName(gn)]); }));
|
|
218
227
|
}
|
|
219
228
|
|
|
220
229
|
function _buildCrlExtensions(spec, ctx) {
|
|
@@ -231,7 +240,10 @@ function _buildCrlExtensions(spec, ctx) {
|
|
|
231
240
|
if (ext == null) ext = {};
|
|
232
241
|
if (Array.isArray(ext)) {
|
|
233
242
|
var arrBase = null, arrCrlNum = null;
|
|
234
|
-
|
|
243
|
+
/** @internal The walk goes through `guard.list.copyMap`, never the caller's own `forEach`. Reached
|
|
244
|
+
* off the array a caller supplied, it is that caller's to replace, and one handing the callback a
|
|
245
|
+
* different list bypasses every per-element rule below at once on content this verb then signs. */
|
|
246
|
+
guard.list.copyMap(ext, function (e, i) {
|
|
235
247
|
var der = _reqDer(e, "extension");
|
|
236
248
|
_assertValidExtension(der, i, EXT_DECODERS);
|
|
237
249
|
var node = asn1.decode(der);
|
|
@@ -324,7 +336,9 @@ function _buildRevoked(entryList, isDelta) {
|
|
|
324
336
|
if (e.certificateIssuer != null) throw _err("crl/bad-input", _CERT_ISSUER_DEFERRED);
|
|
325
337
|
if (e.extensions != null) {
|
|
326
338
|
if (!Array.isArray(e.extensions)) throw _err("crl/bad-input", "revoked entry [" + idx + "] extensions must be an array of pre-encoded Extension DER");
|
|
327
|
-
|
|
339
|
+
/** @internal Through the captured walk for the reason the extension list above gives: a revoked
|
|
340
|
+
* entry's extensions are signed content too. */
|
|
341
|
+
guard.list.copyMap(e.extensions, function (x, j) {
|
|
328
342
|
var der = _reqDer(x, "entry extension");
|
|
329
343
|
_assertValidExtension(der, j, EXT_DECODERS);
|
|
330
344
|
var xnode = asn1.decode(der);
|
|
@@ -423,21 +437,65 @@ function _sign(spec, issuer, opts) {
|
|
|
423
437
|
|
|
424
438
|
var scheme = signScheme.resolveSignScheme(_certLikeFromSpki(issuerSpki), { combinedRsaSig: true, pss: opts.pss, digestAlgorithm: opts.digestAlgorithm }, true, _signE);
|
|
425
439
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
if (
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
440
|
+
/** @internal ITU-T X.509 (2019) clause 7.10.3. The alternative signature covers the PreTBSCertList,
|
|
441
|
+
* which is the tbsCertList without its `signature` AlgorithmIdentifier and without its own
|
|
442
|
+
* altSignatureValue extension (draft-truskovsky-lamps-pq-hybrid-x509 sec. 5), so the extension list
|
|
443
|
+
* is built twice from one function and the alternative signature is generated first.
|
|
444
|
+
* subjectAltPublicKeyInfo is not emitted on a CRL: the NOTE in clause 7.2.2 states it is a
|
|
445
|
+
* certificate extension only. */
|
|
446
|
+
var altCrl = _resolveCrlAltSigning(issuer, issuerCert);
|
|
447
|
+
if (altCrl !== null) {
|
|
448
|
+
guard.list.append(crlExts, _ext(O("altSignatureAlgorithm"), false, b.raw(altCrl.altSigAlgId)));
|
|
449
|
+
version = 2;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/** @internal `omitSignatureAlg` drops the `signature` AlgorithmIdentifier, turning the tbsCertList
|
|
453
|
+
* into the PreTBSCertList the alternative signature covers. */
|
|
454
|
+
function tbsOver(extList, omitSignatureAlg) {
|
|
455
|
+
var tbsChildren = [];
|
|
456
|
+
if (version === 2) tbsChildren.push(b.integer(1n));
|
|
457
|
+
if (omitSignatureAlg !== true) tbsChildren.push(scheme.sigAlgId);
|
|
458
|
+
tbsChildren.push(issuerDer);
|
|
459
|
+
tbsChildren.push(thisU);
|
|
460
|
+
if (nextU) tbsChildren.push(nextU);
|
|
461
|
+
if (revoked.entries.length) tbsChildren.push(b.sequence(revoked.entries));
|
|
462
|
+
if (extList.length) tbsChildren.push(b.explicit(0, b.sequence(extList)));
|
|
463
|
+
return b.sequence(tbsChildren);
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** @internal Self-checked against issuer.altPublicKey before it is embedded, the same gate the native
|
|
467
|
+
* pass applies below. An altKey and an altPublicKey that are different keys of one algorithm resolve
|
|
468
|
+
* to the same scheme, so without this the CRL ships with an alternative signature that does not
|
|
469
|
+
* verify under the alternative key its own issuer names. */
|
|
470
|
+
var altFirst;
|
|
471
|
+
if (altCrl === null) {
|
|
472
|
+
altFirst = _promiseResolve(_Promise, null);
|
|
473
|
+
} else {
|
|
474
|
+
var altPreimage = tbsOver(crlExts, true);
|
|
475
|
+
altFirst = signScheme.signOverTbs(altCrl.scheme, issuer.altKey, altPreimage, _signE)
|
|
476
|
+
.then(function (altSigBytes) {
|
|
477
|
+
return _promiseResolve(_Promise,
|
|
478
|
+
_assertSignatureVerifies(altPreimage, altSigBytes, altCrl.issuerAltSpki, altCrl.scheme)
|
|
479
|
+
).then(function () { return altSigBytes; });
|
|
480
|
+
});
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
return altFirst.then(function (altSigBytes) {
|
|
484
|
+
var finalExts = crlExts;
|
|
485
|
+
if (altCrl !== null) {
|
|
486
|
+
finalExts = _arrSlice(crlExts);
|
|
487
|
+
guard.list.append(finalExts, _ext(O("altSignatureValue"), false, b.bitString(altSigBytes, 0)));
|
|
488
|
+
}
|
|
489
|
+
var tbsDer = tbsOver(finalExts);
|
|
490
|
+
lint.assertBuildClean("crl",
|
|
491
|
+
b.sequence([tbsDer, scheme.sigAlgId, b.bitString(GATE_PLACEHOLDER_SIG, 0)]),
|
|
492
|
+
opts.profile, _err, "crl/profile-violation");
|
|
493
|
+
return _signNativeCrl(tbsDer);
|
|
494
|
+
});
|
|
438
495
|
|
|
496
|
+
function _signNativeCrl(tbsDer) {
|
|
439
497
|
return signScheme.signOverTbs(scheme, issuer.key, tbsDer, _signE).then(function (sig) {
|
|
440
|
-
return
|
|
498
|
+
return _promiseResolve(_Promise, _assertSignatureVerifies(tbsDer, sig, issuerSpki, scheme)).then(function () {
|
|
441
499
|
var crlDer = b.sequence([tbsDer, scheme.sigAlgId, b.bitString(sig, 0)]);
|
|
442
500
|
return opts.pem ? crlSchema.pemEncode(crlDer, "X509 CRL") : crlDer;
|
|
443
501
|
});
|
|
@@ -445,6 +503,40 @@ function _sign(spec, issuer, opts) {
|
|
|
445
503
|
if (e instanceof CrlError) throw e;
|
|
446
504
|
throw _err("crl/bad-input", "signing the CRL failed -- the signing key does not match the resolved algorithm or is invalid", e);
|
|
447
505
|
});
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/** @internal The alternative signing side for a CRL. A CRL carries no subjectAltPublicKeyInfo, so the
|
|
510
|
+
* issuer's alternative key alone turns the mechanism on and there is nothing for the spec to name. */
|
|
511
|
+
function _resolveCrlAltSigning(issuer, issuerCert) {
|
|
512
|
+
if (issuer.altKey == null) {
|
|
513
|
+
if (issuer.altPublicKey != null) {
|
|
514
|
+
throw _err("crl/bad-input", "issuer.altPublicKey needs issuer.altKey to sign the alternative signature with (ITU-T X.509 (2019) clause 7.10.3)");
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
if (issuer.altPublicKey == null) {
|
|
519
|
+
throw _err("crl/bad-input", "an issuer altKey needs issuer.altPublicKey, the SPKI of its public half, to resolve the alternative signature algorithm");
|
|
520
|
+
}
|
|
521
|
+
var issuerAltSpki = _reqDer(issuer.altPublicKey, "issuer.altPublicKey (the issuer's alternative SPKI DER)");
|
|
522
|
+
_assertValidSpki(issuerAltSpki, "issuer.altPublicKey");
|
|
523
|
+
/** @internal Bound to the issuer's certificate, as the native key already is: that one is read OUT of
|
|
524
|
+
* issuer.cert while this one arrives beside it. A relying party checks a CRL's alternative signature
|
|
525
|
+
* with the alternative key the CRL issuer's certificate publishes (clause 9.8.4), so a CRL signed with
|
|
526
|
+
* any other key carries a signature nobody can check. */
|
|
527
|
+
if (issuerCert !== null && issuerCert !== undefined) {
|
|
528
|
+
var published = _altPublicKeyValueOf(issuerCert);
|
|
529
|
+
if (published === null) {
|
|
530
|
+
throw _err("crl/bad-input",
|
|
531
|
+
"issuer.cert publishes no subjectAltPublicKeyInfo, so it names no alternative key for a relying party to check this CRL's alternative signature with (ITU-T X.509 (2019) clause 9.8.4)");
|
|
532
|
+
}
|
|
533
|
+
if (!_bufferEquals(published, issuerAltSpki)) {
|
|
534
|
+
throw _err("crl/bad-input",
|
|
535
|
+
"issuer.altPublicKey is not the alternative key issuer.cert publishes in its subjectAltPublicKeyInfo, so the alternative signature would verify under no key a relying party can find (ITU-T X.509 (2019) clause 9.8.4)");
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
var altScheme = signScheme.resolveSignScheme(_certLikeFromSpki(issuerAltSpki), { combinedRsaSig: true }, true, _signE);
|
|
539
|
+
return { scheme: altScheme, altSigAlgId: altScheme.sigAlgId, issuerAltSpki: issuerAltSpki };
|
|
448
540
|
}
|
|
449
541
|
|
|
450
542
|
/**
|
package/lib/crmf-sign.js
CHANGED
|
@@ -20,8 +20,6 @@
|
|
|
20
20
|
var nodeCrypto = require("node:crypto");
|
|
21
21
|
/** @internal Bound at module load, like every other capture here: the proof below decides what it
|
|
22
22
|
* agrees and what it hashes, so a replacement swapped in afterwards must not be able to steer it. */
|
|
23
|
-
var _createHash = nodeCrypto.createHash;
|
|
24
|
-
var _createHmac = nodeCrypto.createHmac;
|
|
25
23
|
var _diffieHellman = nodeCrypto.diffieHellman;
|
|
26
24
|
var _createPublicKey = nodeCrypto.createPublicKey;
|
|
27
25
|
var _createPrivateKey = nodeCrypto.createPrivateKey;
|
|
@@ -37,10 +35,21 @@ var frameworkError = require("./framework-error");
|
|
|
37
35
|
var guard = require("./guard-all");
|
|
38
36
|
var constants = require("./constants");
|
|
39
37
|
var intrinsic = require("./guard-intrinsic");
|
|
38
|
+
/** @internal The promise a signature verdict is awaited through, built from the constructor captured at
|
|
39
|
+
* load. `Promise.resolve` builds from its RECEIVER, so reading the global binding at the call hands the
|
|
40
|
+
* construction to whatever that binding holds: one settling without awaiting its argument drops the
|
|
41
|
+
* self-check's rejection, and one settling truthy answers the verdict the caller reads. */
|
|
42
|
+
var _Promise = intrinsic.Promise;
|
|
43
|
+
var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
44
|
+
/** @internal `verifyPop`'s verdict is the aggregate of the per-message results, so a replaced `all`
|
|
45
|
+
* that resolves a fabricated array reports an unverified proof of possession as verified. */
|
|
46
|
+
var _promiseAll = intrinsic.uncurry(intrinsic.promiseAll);
|
|
40
47
|
var _hasOwn = intrinsic.hasOwn;
|
|
41
48
|
var _isBuffer = intrinsic.isBuffer;
|
|
49
|
+
var _push = intrinsic.push, _String = intrinsic.String;
|
|
42
50
|
var _stringify = intrinsic.stringify;
|
|
43
51
|
var cms = require("./schema-cms");
|
|
52
|
+
var possession = require("./possession");
|
|
44
53
|
var cmsEncrypt = require("./cms-encrypt");
|
|
45
54
|
var key = require("./key");
|
|
46
55
|
require("./path-validate");
|
|
@@ -77,7 +86,89 @@ var KNOWN_TEMPLATE_KEYS = intrinsic.assign(intrinsic.create(null), { version: 1,
|
|
|
77
86
|
var REVOCATION_TEMPLATE_KEYS = intrinsic.assign(intrinsic.create(null), { version: 1, subject: 1, publicKey: 1, validity: 1, extensions: 1, issuer: 1, serialNumber: 1 });
|
|
78
87
|
function _ctlUtf8(v) { return b.utf8(String(v)); }
|
|
79
88
|
function _ctlSpki(v) { var k = _b.reqDer(v, "protocolEncrKey (an SPKI DER)"); _b.assertValidSpki(k, "protocolEncrKey"); return b.raw(k); }
|
|
80
|
-
|
|
89
|
+
/** @internal RFC 9883 sec. 5 gives its registration control the value of its PKCS#10 attribute,
|
|
90
|
+
* `regCtrl-privateKeyPossessionStatement ATTRIBUTE ::= privateKeyPossessionStatement` under
|
|
91
|
+
* `id-regCtrl-statementOfPossession ::= id-at-statementOfPossession`, so the control and the attribute
|
|
92
|
+
* are one OID carrying one structure. The structure itself is sec. 3. The value is built and then read
|
|
93
|
+
* back through the one statement reader, which is what applies the signer-names-the-certificate rule
|
|
94
|
+
* here rather than a second copy of it. */
|
|
95
|
+
function _ctlPossession(v) {
|
|
96
|
+
if (v === null || typeof v !== "object" || _isBuffer(v)) {
|
|
97
|
+
throw _err("crmf/bad-controls", "statementOfPossession must be an object { signer, certificate? }");
|
|
98
|
+
}
|
|
99
|
+
guard.identifier.assertKnownKeys(v, KNOWN_POSSESSION_CONTROL_KEYS, _err, "crmf/bad-controls",
|
|
100
|
+
"statementOfPossession has an unknown field: ");
|
|
101
|
+
var signer = v.signer;
|
|
102
|
+
if (signer === null || typeof signer !== "object" || _isBuffer(signer)) {
|
|
103
|
+
throw _err("crmf/bad-controls", "statementOfPossession needs a signer naming the signature certificate's issuer and serial (RFC 9883 sec. 3)");
|
|
104
|
+
}
|
|
105
|
+
var issuerRaw = (signer.issuer !== null && typeof signer.issuer === "object" && !_isBuffer(signer.issuer) &&
|
|
106
|
+
signer.issuer.bytes !== undefined) ? signer.issuer.bytes : signer.issuer;
|
|
107
|
+
var fields = [b.sequence([
|
|
108
|
+
b.raw(_b.reqDer(issuerRaw, "statementOfPossession signer.issuer (the signature certificate's issuer Name DER)")),
|
|
109
|
+
b.integer(guard.range.authoredInteger(signer.serialNumber, _err, "crmf/bad-controls", "statementOfPossession signer.serialNumber")),
|
|
110
|
+
])];
|
|
111
|
+
if (v.certificate != null) _push(fields, b.raw(_b.reqDer(v.certificate, "statementOfPossession certificate")));
|
|
112
|
+
var der = b.sequence(fields);
|
|
113
|
+
try { possession.parse(der); }
|
|
114
|
+
catch (e) { throw _err("crmf/bad-controls", "statementOfPossession: " + ((e && e.message) || _String(e)), e); }
|
|
115
|
+
return b.raw(der);
|
|
116
|
+
}
|
|
117
|
+
var KNOWN_POSSESSION_CONTROL_KEYS = intrinsic.assign(intrinsic.create(null), { signer: 1, certificate: 1 });
|
|
118
|
+
/** @internal The RFC 9883 statement a parsed certReq carries, or null. The control value is raw DER, so
|
|
119
|
+
* it goes through the one statement reader; a value that reader refuses is reported as absent here and
|
|
120
|
+
* the verdict below says so, since a control this verifier cannot read is not a proof it can use. */
|
|
121
|
+
function _possessionControlOf(certReq) {
|
|
122
|
+
var controls = (certReq && certReq.controls) || [];
|
|
123
|
+
/** @internal A request carrying MORE THAN ONE statement is refused before any of it is read, the way
|
|
124
|
+
* `pki.possession.verifyRequest` refuses the PKCS#10 attribute. Taking the first and returning left
|
|
125
|
+
* every later statement unchecked: a request whose first statement is sound and whose second names a
|
|
126
|
+
* different certificate verified on the strength of the first. The controls are a sequence nothing
|
|
127
|
+
* orders for this purpose, and which one a CA should honor is stated nowhere, so naming the ambiguity
|
|
128
|
+
* is the only reading that does not check one statement and certify against another. */
|
|
129
|
+
var found = null, count = 0;
|
|
130
|
+
for (var i = 0; i < controls.length; i++) {
|
|
131
|
+
if (controls[i].type !== possession.OID_SOP) continue;
|
|
132
|
+
count += 1;
|
|
133
|
+
if (found === null) found = controls[i].value;
|
|
134
|
+
}
|
|
135
|
+
if (count > 1) {
|
|
136
|
+
throw _err("crmf/bad-controls", "this request carries " + count + " statementOfPossession controls, " +
|
|
137
|
+
"and which of them a CA should honor is not stated anywhere, so the RFC 9883 checks cannot be made " +
|
|
138
|
+
"against one of them alone");
|
|
139
|
+
}
|
|
140
|
+
if (found === null) return null;
|
|
141
|
+
try { return possession.parse(found); }
|
|
142
|
+
catch (_e) { return { signer: null, certificate: null }; }
|
|
143
|
+
}
|
|
144
|
+
/** @internal The signature certificate's key from an ALREADY-ENCODED control list, so the array form of
|
|
145
|
+
* `spec.controls` resolves the proof-of-possession algorithm the same way the object form does. The
|
|
146
|
+
* entries arrive as `_buildAttrTypeAndValues` decoded them, which is the point: that walk already held
|
|
147
|
+
* each one to being a `SEQUENCE { type OID, value }` with no duplicate type, so this one neither
|
|
148
|
+
* re-decodes nor re-validates. A second validator over the same bytes is the shape that put a
|
|
149
|
+
* fail-open skip here, where an entry that did not decode was passed over and the request was then
|
|
150
|
+
* reported as carrying no statement, leaving the algorithm resolved from the key being certified while
|
|
151
|
+
* the request traveled carrying the statement that was skipped. Returns null when the list carries
|
|
152
|
+
* none. */
|
|
153
|
+
function _encodedPossessionSpki(entries) {
|
|
154
|
+
for (var i = 0; i < entries.length; i++) {
|
|
155
|
+
if (entries[i].type !== possession.OID_SOP) continue;
|
|
156
|
+
var statement;
|
|
157
|
+
try { statement = possession.parse(entries[i].node.children[1].bytes); }
|
|
158
|
+
catch (e) {
|
|
159
|
+
throw _err("crmf/bad-controls",
|
|
160
|
+
"the pre-encoded statementOfPossession control does not decode as a PrivateKeyPossessionStatement", e);
|
|
161
|
+
}
|
|
162
|
+
if (statement.certificate == null) {
|
|
163
|
+
throw _err("crmf/bad-controls",
|
|
164
|
+
"statementOfPossession needs the certificate: this verb resolves the proof-of-possession algorithm from its key and checks the signature against it");
|
|
165
|
+
}
|
|
166
|
+
return x509.parse(statement.certificate).subjectPublicKeyInfo.bytes;
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
var CONTROL_VALUE = intrinsic.assign(intrinsic.create(null), { regToken: _ctlUtf8, authenticator: _ctlUtf8, oldCertID: function (v) { return _encodeCertId(v); }, protocolEncrKey: _ctlSpki, statementOfPossession: _ctlPossession });
|
|
81
172
|
var REGINFO_VALUE = intrinsic.assign(intrinsic.create(null), { utf8Pairs: _ctlUtf8 });
|
|
82
173
|
|
|
83
174
|
|
|
@@ -115,7 +206,11 @@ function _encodeCertTemplate(tpl, opts) {
|
|
|
115
206
|
return { der: b.sequence(fields), spki: spki, complete: tpl.subject != null && tpl.publicKey != null };
|
|
116
207
|
}
|
|
117
208
|
|
|
118
|
-
|
|
209
|
+
/** @internal `collect`, when given, receives one `{ type, node }` per entry of the ARRAY form, in order.
|
|
210
|
+
* That is how a caller that needs an entry back reads it: this walk already decoded each one and held
|
|
211
|
+
* it to the shape, so a second walk would be a second validator of the same bytes, weaker than this one
|
|
212
|
+
* wherever the two drifted apart. */
|
|
213
|
+
function _buildAttrTypeAndValues(spec, code, label, valueMap, collect) {
|
|
119
214
|
if (Array.isArray(spec)) {
|
|
120
215
|
if (!spec.length) throw _err(code, label + " must carry at least one entry");
|
|
121
216
|
var seenA = intrinsic.create(null);
|
|
@@ -128,6 +223,7 @@ function _buildAttrTypeAndValues(spec, code, label, valueMap) {
|
|
|
128
223
|
try { t = asn1.read.oid(n.children[0]); } catch (err) { throw _err("crmf/bad-input", "pre-encoded " + label + " [" + i + "] type is not an OBJECT IDENTIFIER", err); }
|
|
129
224
|
if (seenA[t]) throw _err(code, "duplicate " + label + " type " + (oid.name(t) || t));
|
|
130
225
|
seenA[t] = true;
|
|
226
|
+
if (collect) guard.list.append(collect, { type: t, node: n });
|
|
131
227
|
return b.raw(der);
|
|
132
228
|
}));
|
|
133
229
|
}
|
|
@@ -367,11 +463,11 @@ function _buildAgreeMac(pop, outerTag, certReqDer, template) {
|
|
|
367
463
|
* digest the proof's own algorithm identifier names. */
|
|
368
464
|
var popAlgOid = O("id-dhPop-static-HMAC-SHA1");
|
|
369
465
|
var digest = DH_POP_DIGEST[popAlgOid];
|
|
370
|
-
k =
|
|
466
|
+
k = guard.crypto.digestParts(digest, [leading, zz, trailing]);
|
|
371
467
|
/** @internal Ordinary HMAC-SHA1. RFC 2875 sec. 3(d) writes the construction correctly and then
|
|
372
468
|
* labels its pads the wrong way round, calling 0x36 the outer pad and 0x5C the inner one, which is
|
|
373
469
|
* the reverse of RFC 2104; the same sentence says the MAC is computed "as per [RFC2104]". */
|
|
374
|
-
var mac =
|
|
470
|
+
var mac = guard.crypto.hmac(digest, k, certReqDer);
|
|
375
471
|
/** @internal The BIT STRING carries the DhPopStatic SEQUENCE, not the raw MAC. RFC 2875 sec. 3
|
|
376
472
|
* says so after the computation: "DhPopStatic is encoded as a BIT STRING and is the signature
|
|
377
473
|
* value (i.e. encodes the above sequence instead of the raw output from 3d)". `issuerAndSerial`
|
|
@@ -868,7 +964,7 @@ function _assertEnclosedKeyMatches(privateKeyDer, templateSpki) {
|
|
|
868
964
|
* cannot use it, so those are proven by USING the key against the template. */
|
|
869
965
|
var enclosedAlg = key.algorithmOidAt(privateKeyDer, 1);
|
|
870
966
|
if (enclosedAlg !== null && !key.isDhOid(enclosedAlg)) return _assertEnclosedKeyCorresponds(privateKeyDer, templateSpki);
|
|
871
|
-
return
|
|
967
|
+
return _promiseResolve(_Promise, undefined)
|
|
872
968
|
.then(function () { return key.publicFromPrivate(privateKeyDer); })
|
|
873
969
|
.then(function (derivedSpki) {
|
|
874
970
|
var derived = guard.bytes.view(derivedSpki, CrmfError, "crmf/bad-input", "the derived public key");
|
|
@@ -945,7 +1041,7 @@ function _assertPopArmKeys(pop, mode) {
|
|
|
945
1041
|
});
|
|
946
1042
|
}
|
|
947
1043
|
|
|
948
|
-
function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
|
|
1044
|
+
function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts, possessionSpki) {
|
|
949
1045
|
if (pop != null && (typeof pop !== "object" || _isBuffer(pop))) throw _err("crmf/bad-input", "spec.pop must be an object (e.g. { type: 'signature' } or { type: 'raVerified', raVerified: true })");
|
|
950
1046
|
var mode = (pop && pop.type) || (signingKey != null ? "signature" : null);
|
|
951
1047
|
if (pop != null) _assertPopArmKeys(pop, mode);
|
|
@@ -961,7 +1057,13 @@ function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
|
|
|
961
1057
|
}
|
|
962
1058
|
if (signingKey == null) throw _err("crmf/bad-input", "a signature proof of possession requires the requester's private key");
|
|
963
1059
|
if (template.spki == null) throw _err("crmf/bad-input", "a signature proof of possession requires certTemplate.publicKey");
|
|
964
|
-
|
|
1060
|
+
/** @internal Normally the requested key signs, which is what makes a signature POP a proof of
|
|
1061
|
+
* possession OF that key. With an RFC 9883 statement of possession the signature is made by the key
|
|
1062
|
+
* of the certificate the statement names, because the requested key is a key-establishment key and
|
|
1063
|
+
* cannot sign. The relaxation is scoped to a request carrying the control, so a request without one
|
|
1064
|
+
* is still held to signing with the key it asks to have certified. */
|
|
1065
|
+
var provingSpki = possessionSpki == null ? template.spki : possessionSpki;
|
|
1066
|
+
var scheme = signScheme.resolveSignScheme(_b.certLikeFromSpki(provingSpki), { combinedRsaSig: true, pss: opts.pss, digestAlgorithm: opts.digestAlgorithm }, true, _signE);
|
|
965
1067
|
var signedRegion, poposkInputField = null;
|
|
966
1068
|
if (template.complete) {
|
|
967
1069
|
signedRegion = certReqDer;
|
|
@@ -973,7 +1075,7 @@ function _buildProofOfPossession(pop, certReqDer, template, signingKey, opts) {
|
|
|
973
1075
|
poposkInputField = b.implicit(0, poposkSeq);
|
|
974
1076
|
}
|
|
975
1077
|
return signScheme.signOverTbs(scheme, signingKey, signedRegion, _signE).then(function (sig) {
|
|
976
|
-
return
|
|
1078
|
+
return _promiseResolve(_Promise, _b.assertSignatureVerifies(signedRegion, sig, provingSpki, scheme)).then(function () {
|
|
977
1079
|
var popoChildren = [];
|
|
978
1080
|
if (poposkInputField) popoChildren.push(poposkInputField);
|
|
979
1081
|
popoChildren.push(scheme.sigAlgId);
|
|
@@ -1068,10 +1170,33 @@ function _buildCertReqMsg(spec, key, opts) {
|
|
|
1068
1170
|
var signingKey = (key && typeof key === "object" && !_isBuffer(key) && !(key instanceof Uint8Array) && key.type == null && "key" in key) ? key.key : key;
|
|
1069
1171
|
var template = _encodeCertTemplate(spec.certTemplate);
|
|
1070
1172
|
var certReqChildren = [b.integer(_certReqId(spec.certReqId)), template.der];
|
|
1071
|
-
|
|
1173
|
+
var encodedControls = [];
|
|
1174
|
+
if (spec.controls != null) certReqChildren.push(_buildAttrTypeAndValues(spec.controls, "crmf/bad-controls", "controls", CONTROL_VALUE, encodedControls));
|
|
1072
1175
|
var certReqDer = b.sequence(certReqChildren);
|
|
1073
1176
|
|
|
1074
|
-
|
|
1177
|
+
/** @internal The key the RFC 9883 control names, when the request carries one: that key makes the
|
|
1178
|
+
* proof of possession rather than the requested key. The certificate is required in the control for
|
|
1179
|
+
* the same reason it is in the PKCS#10 spec field -- this verb has to know which key signs. */
|
|
1180
|
+
var possessionSpki = null;
|
|
1181
|
+
if (spec.controls != null && !intrinsic.isArray(spec.controls) && spec.controls.statementOfPossession != null) {
|
|
1182
|
+
var sopCert = spec.controls.statementOfPossession.certificate;
|
|
1183
|
+
if (sopCert == null) {
|
|
1184
|
+
throw _err("crmf/bad-controls",
|
|
1185
|
+
"statementOfPossession needs the certificate: this verb resolves the proof-of-possession algorithm from its key and checks the signature against it");
|
|
1186
|
+
}
|
|
1187
|
+
possessionSpki = x509.parse(_b.reqDer(sopCert, "statementOfPossession certificate")).subjectPublicKeyInfo.bytes;
|
|
1188
|
+
} else if (spec.controls != null && intrinsic.isArray(spec.controls)) {
|
|
1189
|
+
/** @internal The ARRAY form carries controls already encoded, and it reaches the same resolution. Read
|
|
1190
|
+
* only through the object form, a pre-encoded statement was invisible here and the signing algorithm
|
|
1191
|
+
* was resolved from the REQUESTED key instead of from the signature certificate: an X25519 request
|
|
1192
|
+
* carrying a perfectly good encoded statement was refused `crmf/unsupported-algorithm`, while the
|
|
1193
|
+
* object form of the same request succeeded. One control, two spellings, and the rule has to hold for
|
|
1194
|
+
* both. The entries are the ones the builder above decoded, so the statement is found without a
|
|
1195
|
+
* second walk over the same bytes, and read back through the one statement reader. */
|
|
1196
|
+
possessionSpki = _encodedPossessionSpki(encodedControls);
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
return _promiseResolve(_Promise, _buildProofOfPossession(spec.pop, certReqDer, template, signingKey, opts, possessionSpki)).then(function (popoDer) {
|
|
1075
1200
|
var msgChildren = [certReqDer];
|
|
1076
1201
|
if (popoDer) msgChildren.push(popoDer);
|
|
1077
1202
|
if (spec.regInfo != null) msgChildren.push(_buildAttrTypeAndValues(spec.regInfo, "crmf/bad-reg-info", "regInfo", REGINFO_VALUE));
|
|
@@ -1093,7 +1218,7 @@ function _build(spec, key, opts) {
|
|
|
1093
1218
|
specs = [spec];
|
|
1094
1219
|
}
|
|
1095
1220
|
if (!specs.length) throw _err("crmf/bad-input", "at least one certificate request message is required (RFC 4211 sec. 3)");
|
|
1096
|
-
return
|
|
1221
|
+
return _promiseAll(_Promise, specs.map(function (s) { return _buildCertReqMsg(s, key, opts); })).then(function (msgs) {
|
|
1097
1222
|
var der = b.sequence(msgs);
|
|
1098
1223
|
if (opts.pem != null) {
|
|
1099
1224
|
if (typeof opts.pem !== "string" || !opts.pem) throw _err("crmf/bad-input", "opts.pem must be a non-empty PEM label string");
|
|
@@ -1137,7 +1262,7 @@ function _verifyOne(msg) {
|
|
|
1137
1262
|
};
|
|
1138
1263
|
function settle(extra) {
|
|
1139
1264
|
var v = guard.verdict.of(base, extra);
|
|
1140
|
-
return
|
|
1265
|
+
return _promiseResolve(_Promise, guard.verdict.of(v, { valid: v.verified === true }));
|
|
1141
1266
|
}
|
|
1142
1267
|
|
|
1143
1268
|
var popo = msg.popo;
|
|
@@ -1158,6 +1283,33 @@ function _verifyOne(msg) {
|
|
|
1158
1283
|
var preimage = pin ? pin.signedBytes : msg.certReq.certReqBytes;
|
|
1159
1284
|
var spki = pin ? pin.publicKey : base.publicKey;
|
|
1160
1285
|
var bound = !pin;
|
|
1286
|
+
|
|
1287
|
+
/** @internal RFC 9883 sec. 5, which states the CRMF conventions: a request carrying a statement of
|
|
1288
|
+
* possession has "the signature over the poposkInput MUST validate with the public key in the
|
|
1289
|
+
* signature certificate", so it is signed by the key of the
|
|
1290
|
+
* certificate the statement names, not by the key being requested, so the proof is checked against
|
|
1291
|
+
* that key. It therefore proves possession of the SIGNATURE key and not of the requested one, which
|
|
1292
|
+
* is why `subjectBound` is false here however complete the template is: the requested
|
|
1293
|
+
* key-establishment key cannot sign, and nothing in this message demonstrates possession of it. What
|
|
1294
|
+
* the statement buys is a signature the CA can attribute to the same entity, and the remaining
|
|
1295
|
+
* checks RFC 9883 states are `pki.possession.verifyRequest`'s, over a PKCS#10 request. */
|
|
1296
|
+
var sop = _possessionControlOf(msg.certReq);
|
|
1297
|
+
if (sop !== null) {
|
|
1298
|
+
if (sop.certificate === null) {
|
|
1299
|
+
return settle({ verified: false, method: "signature", cryptographicallyVerified: false,
|
|
1300
|
+
subjectBound: false,
|
|
1301
|
+
reason: "the statementOfPossession control omits its certificate, so the key that signed this request is not in the message (RFC 9883 sec. 3)" });
|
|
1302
|
+
}
|
|
1303
|
+
var sopSpki;
|
|
1304
|
+
try { sopSpki = x509.parse(sop.certificate).subjectPublicKeyInfo.bytes; }
|
|
1305
|
+
catch (_e) {
|
|
1306
|
+
return settle({ verified: false, method: "signature", cryptographicallyVerified: false,
|
|
1307
|
+
subjectBound: false,
|
|
1308
|
+
reason: "the statementOfPossession control's certificate is not readable, so the key that signed this request cannot be recovered" });
|
|
1309
|
+
}
|
|
1310
|
+
spki = sopSpki;
|
|
1311
|
+
bound = false;
|
|
1312
|
+
}
|
|
1161
1313
|
return crmfVerify.verifyPopSignature(popo, spki, preimage).then(function (ok) {
|
|
1162
1314
|
return guard.verdict.of(base, {
|
|
1163
1315
|
valid: ok === true, verified: ok === true, method: "signature", cryptographicallyVerified: ok === true,
|
|
@@ -1216,7 +1368,7 @@ function _verifyOne(msg) {
|
|
|
1216
1368
|
function verifyPop(messages) { return guard.async.deferred(function () { return _verifyPop(messages); }); }
|
|
1217
1369
|
function _verifyPop(messages) {
|
|
1218
1370
|
var parsed = _coerceMessages(messages);
|
|
1219
|
-
return
|
|
1371
|
+
return _promiseAll(_Promise, parsed.messages.map(_verifyOne)).then(function (out) {
|
|
1220
1372
|
var allVerified = out.length > 0 && out.every(function (m) { return m.verified === true; });
|
|
1221
1373
|
return guard.verdict.of({ valid: allVerified, verified: allVerified, messages: out });
|
|
1222
1374
|
});
|