@blamejs/pki 0.6.13 → 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +22 -1
- package/README.md +1 -1
- package/index.js +1 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +59 -233
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/cmp-build.js
CHANGED
|
@@ -20,16 +20,6 @@
|
|
|
20
20
|
* @spec RFC 9810
|
|
21
21
|
* @card Build a CMP PKIMessage with signature or PBMAC1 protection.
|
|
22
22
|
*/
|
|
23
|
-
//
|
|
24
|
-
// RFC 9810 Appendix A is DEFINITIONS EXPLICIT TAGS: every context tag in the PKIMessage envelope
|
|
25
|
-
// (each PKIHeader [0..8] optional, every PKIBody [n] arm, protection [0], extraCerts [1]) is an EXPLICIT
|
|
26
|
-
// wrapper (asn1.build.explicit), the exact inverse of the RFC 4211 CRMF interior (IMPLICIT TAGS). The two
|
|
27
|
-
// IMPLICIT islands are foreign imports produced wholesale by pki.crmf.build (the CertReqMessages /
|
|
28
|
-
// CertTemplate interior) and embedded verbatim, never re-encoded here. Protection is computed over the
|
|
29
|
-
// byte-exact DER of the virtual ProtectedPart ::= SEQUENCE { header, body }, built from the same header
|
|
30
|
-
// and body TLVs placed in the envelope. The signature scheme resolves from the sender key through the
|
|
31
|
-
// shared sign-scheme registry; the Name / GeneralName / SPKI encoders and the post-sign self-check are the
|
|
32
|
-
// shared lib/pki-build primitives, bound to the cmp namespace.
|
|
33
23
|
|
|
34
24
|
var asn1 = require("./asn1-der");
|
|
35
25
|
var schema = require("./schema-engine");
|
|
@@ -38,11 +28,7 @@ var cmp = require("./schema-cmp");
|
|
|
38
28
|
var crmf = require("./crmf-sign");
|
|
39
29
|
var schemaCrmf = require("./schema-crmf");
|
|
40
30
|
var intrinsic = require("./guard-intrinsic");
|
|
41
|
-
// Own-key membership through an operation taken at load: it decides which proof-of-possession
|
|
42
|
-
// methods couple the message to a protocol version, and an inherited name is one no RFC defined.
|
|
43
31
|
var _hasOwn = intrinsic.hasOwn;
|
|
44
|
-
// The same load-bound treatment for the operations that shape a revocation request and a support
|
|
45
|
-
// message: each decides what reaches the wire, so a replaced built-in must not get a say.
|
|
46
32
|
var _isArray = intrinsic.isArray;
|
|
47
33
|
var _isBuffer = intrinsic.isBuffer;
|
|
48
34
|
var _charAt = intrinsic.uncurry(String.prototype.charAt);
|
|
@@ -56,12 +42,10 @@ var _forEach = intrinsic.forEach;
|
|
|
56
42
|
var _push = intrinsic.push;
|
|
57
43
|
var _stringify = intrinsic.stringify;
|
|
58
44
|
var _bigInt = intrinsic.BigInt;
|
|
59
|
-
// The constructor taken at load: re-materializing an instant that is about to be encoded must not
|
|
60
|
-
// go through a Date a caller could have replaced after this module was read.
|
|
61
45
|
var _Date = intrinsic.Date;
|
|
62
46
|
var csr = require("./schema-csr");
|
|
63
|
-
require("./path-validate");
|
|
64
|
-
var csrVerify = require("./csr-verify");
|
|
47
|
+
require("./path-validate");
|
|
48
|
+
var csrVerify = require("./csr-verify");
|
|
65
49
|
var crl = require("./schema-crl");
|
|
66
50
|
var x509 = require("./schema-x509");
|
|
67
51
|
var signScheme = require("./sign-scheme");
|
|
@@ -92,23 +76,17 @@ var KNOWN_HEADER_KEYS = {
|
|
|
92
76
|
transactionID: 1, senderNonce: 1, recipNonce: 1, freeText: 1, generalInfo: 1,
|
|
93
77
|
};
|
|
94
78
|
var KNOWN_BODY_KEYS = {
|
|
95
|
-
ir: 1, cr: 1, kur: 1, krr: 1, ccr: 1, p10cr: 1, certConf: 1, pollReq: 1, genm: 1, rr: 1,
|
|
96
|
-
ip: 1, cp: 1, kup: 1, ccp: 1, krp: 1, rp: 1, genp: 1, error: 1, pollRep: 1, pkiconf: 1,
|
|
97
|
-
nested: 1,
|
|
79
|
+
ir: 1, cr: 1, kur: 1, krr: 1, ccr: 1, p10cr: 1, certConf: 1, pollReq: 1, genm: 1, rr: 1,
|
|
80
|
+
ip: 1, cp: 1, kup: 1, ccp: 1, krp: 1, rp: 1, genp: 1, error: 1, pollRep: 1, pkiconf: 1,
|
|
81
|
+
nested: 1,
|
|
98
82
|
};
|
|
99
|
-
// The PKIBody arm -> its EXPLICIT context tag number (schema-cmp.js BODY_ARMS). rr is [11], not [15].
|
|
100
83
|
var BODY_TAG = {
|
|
101
84
|
ir: 0, cr: 2, p10cr: 4, kur: 7, krr: 9, ccr: 13, rr: 11, genm: 21, certConf: 24, pollReq: 25,
|
|
102
85
|
ip: 1, cp: 3, kup: 8, krp: 10, rp: 12, ccp: 14, pkiconf: 19, genp: 22, error: 23, pollRep: 26,
|
|
103
|
-
nested: 20,
|
|
86
|
+
nested: 20,
|
|
104
87
|
};
|
|
105
|
-
// arms whose content is a CertReqMessages via pki.crmf.build (RFC 9810 sec. 5.3.7 krr, sec. 5.3.11 ccr).
|
|
106
88
|
var CRMF_BODY = { ir: 1, cr: 1, kur: 1, krr: 1, ccr: 1 };
|
|
107
|
-
var CERT_REP_ARM = { ip: 1, cp: 1, kup: 1, ccp: 1 };
|
|
108
|
-
// Protection has two mutually exclusive forms, and the signature parameters are read only by the
|
|
109
|
-
// signature one: under PBMAC1 a requested pss or digestAlgorithm selects nothing and the message
|
|
110
|
-
// goes out MAC'd under the default PRF, byte-identical to the call that never named them. The union
|
|
111
|
-
// stays in force where the form is ambiguous, so _resolveProtection keeps its both/neither verdicts.
|
|
89
|
+
var CERT_REP_ARM = { ip: 1, cp: 1, kup: 1, ccp: 1 };
|
|
112
90
|
var KNOWN_OPTS_KEYS = { key: 1, cert: 1, mac: 1, extraCerts: 1, pem: 1, pss: 1, digestAlgorithm: 1 };
|
|
113
91
|
var KNOWN_OPTS_SIG_KEYS = { key: 1, cert: 1, extraCerts: 1, pem: 1, pss: 1, digestAlgorithm: 1 };
|
|
114
92
|
var KNOWN_OPTS_MAC_KEYS = { mac: 1, extraCerts: 1, pem: 1 };
|
|
@@ -117,16 +95,14 @@ var KNOWN_MAC_KEYS = { secret: 1, salt: 1, iterationCount: 1, prf: 1, keyLength:
|
|
|
117
95
|
|
|
118
96
|
var PBMAC1_DEFAULT_ITER = 100000;
|
|
119
97
|
var PBMAC1_DEFAULT_SALT_BYTES = 16;
|
|
120
|
-
var PBMAC1_MIN_SALT = 8;
|
|
121
|
-
var PBMAC1_DEFAULT_KEYLEN = 32;
|
|
122
|
-
var PBMAC1_MIN_KEYLEN = 20;
|
|
123
|
-
var PBMAC1_MAX_KEYLEN = 1024;
|
|
124
|
-
var PBMAC1_MIN_ITER = 1000;
|
|
98
|
+
var PBMAC1_MIN_SALT = 8;
|
|
99
|
+
var PBMAC1_DEFAULT_KEYLEN = 32;
|
|
100
|
+
var PBMAC1_MIN_KEYLEN = 20;
|
|
101
|
+
var PBMAC1_MAX_KEYLEN = 1024;
|
|
102
|
+
var PBMAC1_MIN_ITER = 1000;
|
|
125
103
|
var PBMAC1_PRF = { "SHA-256": "hmacWithSHA256", "SHA-384": "hmacWithSHA384", "SHA-512": "hmacWithSHA512" };
|
|
126
104
|
var PBMAC1_MAC_OID = { "SHA-256": "hmacWithSHA256", "SHA-384": "hmacWithSHA384", "SHA-512": "hmacWithSHA512" };
|
|
127
|
-
var PBMAC1_PRF_HLEN = { "SHA-256": 32, "SHA-384": 48, "SHA-512": 64 };
|
|
128
|
-
// PKIFailureInfo named bits (RFC 9810 sec. 5.2.3), position = bit index, mirroring schema-cmp's decode list;
|
|
129
|
-
// the build -> parse round-trip cross-checks the positions against the parser.
|
|
105
|
+
var PBMAC1_PRF_HLEN = { "SHA-256": 32, "SHA-384": 48, "SHA-512": 64 };
|
|
130
106
|
var FAIL_INFO_NAMES = ["badAlg", "badMessageCheck", "badRequest", "badTime", "badCertId", "badDataFormat",
|
|
131
107
|
"wrongAuthority", "incorrectData", "missingTimeStamp", "badPOP", "certRevoked", "certConfirmed",
|
|
132
108
|
"wrongIntegrity", "badRecipientNonce", "timeNotAvailable", "unacceptedPolicy", "unacceptedExtension",
|
|
@@ -134,17 +110,14 @@ var FAIL_INFO_NAMES = ["badAlg", "badMessageCheck", "badRequest", "badTime", "ba
|
|
|
134
110
|
"unsupportedVersion", "notAuthorized", "systemUnavail", "systemFailure", "duplicateCertReq"];
|
|
135
111
|
var FAIL_INFO_INDEX = {};
|
|
136
112
|
FAIL_INFO_NAMES.forEach(function (n, i) { FAIL_INFO_INDEX[n] = i; });
|
|
137
|
-
// CertStatus.hashAlg names the hash used to compute certHash, restricted to hash algorithms and not any OID.
|
|
138
113
|
var CERT_CONF_HASH_ALGS = { sha1: 1, sha256: 1, sha384: 1, sha512: 1, "sha3-256": 1, "sha3-512": 1 };
|
|
139
114
|
|
|
140
|
-
// ---- small shared encoders (byte-exact inverses of schema-cmp.js readers) ----
|
|
141
115
|
|
|
142
116
|
function _reqOctets(v, what) {
|
|
143
117
|
var buf = _b.reqDer(v, what);
|
|
144
118
|
return b.octetString(buf);
|
|
145
119
|
}
|
|
146
120
|
|
|
147
|
-
// PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String: non-empty, every element UTF8String.
|
|
148
121
|
function _encodePkiFreeText(strings, code, what) {
|
|
149
122
|
if (!Array.isArray(strings) || !strings.length) throw _err(code, what + " must be a non-empty array of strings");
|
|
150
123
|
return b.sequence(strings.map(function (s) {
|
|
@@ -153,9 +126,6 @@ function _encodePkiFreeText(strings, code, what) {
|
|
|
153
126
|
}));
|
|
154
127
|
}
|
|
155
128
|
|
|
156
|
-
// A recognized fixed-syntax id-it value (RFC 9810 sec. 5.1.1.1/.2/.4): implicitConfirm -> NULL,
|
|
157
|
-
// confirmWaitTime -> GeneralizedTime, certProfile -> non-empty SEQUENCE OF UTF8String. An unknown id-it
|
|
158
|
-
// carries a pre-encoded infoValue DER (or none).
|
|
159
129
|
function _encodeInfoValue(itav) {
|
|
160
130
|
var name = itav.infoType;
|
|
161
131
|
if (name === "implicitConfirm") {
|
|
@@ -163,7 +133,7 @@ function _encodeInfoValue(itav) {
|
|
|
163
133
|
return b.nullValue();
|
|
164
134
|
}
|
|
165
135
|
if (name === "confirmWaitTime") {
|
|
166
|
-
guard.time.assertValid(itav.infoValue, _err, "cmp/bad-info-value", "confirmWaitTime infoValue");
|
|
136
|
+
guard.time.assertValid(itav.infoValue, _err, "cmp/bad-info-value", "confirmWaitTime infoValue");
|
|
167
137
|
return b.generalizedTime(itav.infoValue);
|
|
168
138
|
}
|
|
169
139
|
if (name === "certProfile") {
|
|
@@ -173,7 +143,6 @@ function _encodeInfoValue(itav) {
|
|
|
173
143
|
return b.raw(_b.reqDer(itav.infoValue, "infoValue (a pre-encoded DER value)"));
|
|
174
144
|
}
|
|
175
145
|
|
|
176
|
-
// InfoTypeAndValue ::= SEQUENCE { infoType OID, infoValue ANY DEFINED BY infoType OPTIONAL }.
|
|
177
146
|
function _encodeInfoTypeAndValue(itav) {
|
|
178
147
|
if (!itav || typeof itav !== "object" || Buffer.isBuffer(itav)) throw _err("cmp/bad-info-value", "an InfoTypeAndValue must be an object { infoType, infoValue? }");
|
|
179
148
|
if (typeof itav.infoType !== "string" || O(itav.infoType) == null) throw _err("cmp/bad-name", "unknown infoType " + JSON.stringify(itav.infoType));
|
|
@@ -183,20 +152,14 @@ function _encodeInfoTypeAndValue(itav) {
|
|
|
183
152
|
return b.sequence(children);
|
|
184
153
|
}
|
|
185
154
|
|
|
186
|
-
// generalInfo [8] / genm content: SEQUENCE SIZE (1..MAX) OF InfoTypeAndValue.
|
|
187
155
|
function _encodeGeneralInfo(itavs, code, what) {
|
|
188
156
|
if (!Array.isArray(itavs) || !itavs.length) throw _err(code, what + " must be a non-empty array of InfoTypeAndValue");
|
|
189
157
|
return b.sequence(itavs.map(_encodeInfoTypeAndValue));
|
|
190
158
|
}
|
|
191
159
|
|
|
192
|
-
// ---- PKIHeader ----
|
|
193
160
|
|
|
194
|
-
// An AlgorithmIdentifier { algorithm OID, parameters ANY OPTIONAL } for a bare/absent-params digest.
|
|
195
161
|
function _algIdNoParams(name) { return b.sequence([b.oid(O(name))]); }
|
|
196
162
|
|
|
197
|
-
// PKIHeader ::= SEQUENCE { pvno INTEGER, sender GeneralName, recipient GeneralName, EXPLICIT [0..8] }.
|
|
198
|
-
// protectionAlgDer is the DERIVED AlgorithmIdentifier ([1]), present iff the message is protected. Returns
|
|
199
|
-
// the exact headerTLV, built ONCE and reused in both the envelope and the ProtectedPart (RFC 9810 5.1.3).
|
|
200
163
|
function _encodeHeader(headerSpec, protectionAlgDer, pvno) {
|
|
201
164
|
if (!headerSpec || typeof headerSpec !== "object" || Buffer.isBuffer(headerSpec)) throw _err("cmp/bad-input", "message.header must be an object");
|
|
202
165
|
guard.identifier.assertKnownKeys(headerSpec, KNOWN_HEADER_KEYS, _err, "cmp/bad-input", "unknown header field ");
|
|
@@ -208,10 +171,9 @@ function _encodeHeader(headerSpec, protectionAlgDer, pvno) {
|
|
|
208
171
|
_b.encodeGeneralName(headerSpec.sender),
|
|
209
172
|
_b.encodeGeneralName(headerSpec.recipient),
|
|
210
173
|
];
|
|
211
|
-
// EXPLICIT [0..8] optionals, ascending, at most once each.
|
|
212
174
|
if (headerSpec.messageTime != null) {
|
|
213
|
-
guard.time.assertValid(headerSpec.messageTime, _err, "cmp/bad-input", "header.messageTime");
|
|
214
|
-
children.push(b.explicit(0, b.generalizedTime(headerSpec.messageTime)));
|
|
175
|
+
guard.time.assertValid(headerSpec.messageTime, _err, "cmp/bad-input", "header.messageTime");
|
|
176
|
+
children.push(b.explicit(0, b.generalizedTime(headerSpec.messageTime)));
|
|
215
177
|
}
|
|
216
178
|
if (protectionAlgDer != null) children.push(b.explicit(1, b.raw(protectionAlgDer)));
|
|
217
179
|
if (headerSpec.senderKID != null) children.push(b.explicit(2, _reqOctets(headerSpec.senderKID, "header.senderKID")));
|
|
@@ -224,13 +186,9 @@ function _encodeHeader(headerSpec, protectionAlgDer, pvno) {
|
|
|
224
186
|
return b.sequence(children);
|
|
225
187
|
}
|
|
226
188
|
|
|
227
|
-
// ---- PKIBody arm content encoders ----
|
|
228
189
|
|
|
229
|
-
// A certReqId is an UNBOUNDED INTEGER (RFC 9483 -1 sentinel, no upper bound): accept a safe-integer number
|
|
230
|
-
// or a bigint (a large value beyond 2^53), reject a non-integer / other type. Returns a BigInt for b.integer.
|
|
231
190
|
function _reqIdInt(v, code, what) { return guard.range.authoredInteger(v, _err, code, what); }
|
|
232
191
|
|
|
233
|
-
// PKIFailureInfo ::= BIT STRING (named bits, RFC 9810 sec. 5.2.3): a minimal NamedBitList from bit names.
|
|
234
192
|
function _encodeFailInfo(names, code) {
|
|
235
193
|
if (!Array.isArray(names)) throw _err(code, "statusInfo.failInfo must be an array of PKIFailureInfo bit names");
|
|
236
194
|
return b.namedBitString(names.map(function (n) {
|
|
@@ -239,25 +197,21 @@ function _encodeFailInfo(names, code) {
|
|
|
239
197
|
}));
|
|
240
198
|
}
|
|
241
199
|
|
|
242
|
-
// PKIStatusInfo ::= SEQUENCE { status PKIStatus INTEGER, statusString PKIFreeText OPTIONAL, failInfo BIT STRING
|
|
243
|
-
// OPTIONAL }. `code` is the caller's typed error (default cmp/bad-status-info; certConf passes cmp/bad-cert-status).
|
|
244
200
|
function _encodePkiStatusInfo(si, code) {
|
|
245
201
|
code = code || "cmp/bad-status-info";
|
|
246
202
|
if (!si || typeof si !== "object" || Buffer.isBuffer(si)) throw _err(code, "a PKIStatusInfo must be an object { status, statusString?, failInfo? }");
|
|
247
203
|
if (typeof si.status !== "number" || !Number.isInteger(si.status) || si.status < 0 || si.status > 6) throw _err(code, "statusInfo.status must be a PKIStatus 0..6 (RFC 9810 sec. 5.2.3)");
|
|
248
204
|
var children = [b.integer(BigInt(si.status))];
|
|
249
205
|
if (si.statusString != null) children.push(_encodePkiFreeText(si.statusString, code, "statusInfo.statusString"));
|
|
250
|
-
if (si.failInfo != null) children.push(_encodeFailInfo(si.failInfo, code));
|
|
206
|
+
if (si.failInfo != null) children.push(_encodeFailInfo(si.failInfo, code));
|
|
251
207
|
return b.sequence(children);
|
|
252
208
|
}
|
|
253
209
|
|
|
254
|
-
// CertStatus ::= SEQUENCE { certHash OCTET STRING, certReqId INTEGER, statusInfo PKIStatusInfo OPTIONAL,
|
|
255
|
-
// hashAlg [0] EXPLICIT AlgorithmIdentifier OPTIONAL }. hashAlg present => cmp2021 (pvno bump).
|
|
256
210
|
function _encodeCertStatus(cs, state) {
|
|
257
211
|
if (!cs || typeof cs !== "object" || Buffer.isBuffer(cs)) throw _err("cmp/bad-cert-status", "each CertStatus must be an object");
|
|
258
212
|
var children = [
|
|
259
213
|
_reqOctets(cs.certHash, "certConf certHash"),
|
|
260
|
-
b.integer(_reqIdInt(cs.certReqId, "cmp/bad-cert-status", "CertStatus certReqId")),
|
|
214
|
+
b.integer(_reqIdInt(cs.certReqId, "cmp/bad-cert-status", "CertStatus certReqId")),
|
|
261
215
|
];
|
|
262
216
|
if (cs.statusInfo != null) children.push(_encodePkiStatusInfo(cs.statusInfo, "cmp/bad-cert-status"));
|
|
263
217
|
if (cs.hashAlg != null) {
|
|
@@ -268,13 +222,11 @@ function _encodeCertStatus(cs, state) {
|
|
|
268
222
|
return b.sequence(children);
|
|
269
223
|
}
|
|
270
224
|
|
|
271
|
-
// CertConfirmContent ::= SEQUENCE OF CertStatus (empty legal, no SIZE floor).
|
|
272
225
|
function _encodeCertConfirmContent(list, state) {
|
|
273
226
|
if (!Array.isArray(list)) throw _err("cmp/bad-cert-status", "certConf must be an array of CertStatus");
|
|
274
227
|
return b.sequence(list.map(function (cs) { return _encodeCertStatus(cs, state); }));
|
|
275
228
|
}
|
|
276
229
|
|
|
277
|
-
// PollReqContent ::= SEQUENCE OF SEQUENCE { certReqId INTEGER } (-1 legal).
|
|
278
230
|
function _encodePollReqContent(list) {
|
|
279
231
|
if (!Array.isArray(list)) throw _err("cmp/bad-poll-req", "pollReq must be an array of { certReqId }");
|
|
280
232
|
return b.sequence(list.map(function (pr) {
|
|
@@ -283,26 +235,19 @@ function _encodePollReqContent(list) {
|
|
|
283
235
|
}));
|
|
284
236
|
}
|
|
285
237
|
|
|
286
|
-
// GenMsgContent ::= SEQUENCE OF InfoTypeAndValue (empty legal).
|
|
287
238
|
function _encodeGenMsgContent(list) {
|
|
288
239
|
if (!Array.isArray(list)) throw _err("cmp/bad-info-type-and-value", "genm must be an array of InfoTypeAndValue");
|
|
289
240
|
return b.sequence(list.map(_encodeInfoTypeAndValue));
|
|
290
241
|
}
|
|
291
242
|
|
|
292
|
-
// RevReqContent ::= SEQUENCE OF RevDetails { certDetails CertTemplate, crlEntryDetails Extensions OPTIONAL }.
|
|
293
243
|
function _encodeRevDetails(rd) {
|
|
294
244
|
if (!rd || typeof rd !== "object" || Buffer.isBuffer(rd)) throw _err("cmp/bad-rev-req", "each RevDetails must be an object { certDetails, crlEntryDetails? }");
|
|
295
245
|
guard.identifier.assertKnownKeys(rd, KNOWN_REV_DETAILS_KEYS, _err, "cmp/bad-rev-req", "unknown RevDetails field ");
|
|
296
246
|
if (rd.certDetails == null) throw _err("cmp/bad-rev-req", "RevDetails.certDetails (a CertTemplate) is required");
|
|
297
247
|
var certDetails;
|
|
298
248
|
if (guard.bytes.isByteSource(rd.certDetails)) {
|
|
299
|
-
// The pre-encoded escape hatch: normalized through the byte guard so every BufferSource the guard
|
|
300
|
-
// accepts (Buffer, any typed-array view, DataView, raw ArrayBuffer) is taken, not a narrowed subset
|
|
301
|
-
// that would drop a caller's container into the { issuer, serialNumber } branch below.
|
|
302
249
|
certDetails = b.raw(guard.bytes.snapshotSource(rd.certDetails, CmpError, "cmp/bad-rev-req", "RevDetails.certDetails (a pre-encoded CertTemplate DER)"));
|
|
303
250
|
} else {
|
|
304
|
-
// A revocation must identify the certificate to revoke by issuer + serialNumber (RFC 9810 App. D sec.
|
|
305
|
-
// 5.3.2); the pre-encoded-CertTemplate hatch above is the escape for an advanced identification.
|
|
306
251
|
if (rd.certDetails.issuer == null || rd.certDetails.serialNumber == null) throw _err("cmp/bad-rev-req", "a revocation certDetails must identify the certificate by issuer and serialNumber");
|
|
307
252
|
certDetails = b.raw(crmf.buildCertTemplate(rd.certDetails));
|
|
308
253
|
}
|
|
@@ -311,39 +256,14 @@ function _encodeRevDetails(rd) {
|
|
|
311
256
|
return b.sequence(children);
|
|
312
257
|
}
|
|
313
258
|
|
|
314
|
-
// crlEntryDetails ::= Extensions. A revocation request states WHY, so the structured form
|
|
315
|
-
// { reason } encodes the single reasonCode extension RFC 9483 sec. 4.2 requires. A caller
|
|
316
|
-
// needing another entry extension passes a pre-encoded Extensions DER instead.
|
|
317
|
-
//
|
|
318
|
-
// The reasonCode is emitted even at unspecified(0). RFC 5280 sec. 5.3.1 omits that value from a
|
|
319
|
-
// CRL entry, where absence and unspecified carry the same meaning; sec. 4.2 governs a different
|
|
320
|
-
// structure and says the field is REQUIRED and "MUST be 0 (unspecified)" when the reason is
|
|
321
|
-
// unknown or is not to be published, so the omit-on-zero rule does not travel here.
|
|
322
259
|
var KNOWN_REV_DETAILS_KEYS = { certDetails: 1, crlEntryDetails: 1 };
|
|
323
260
|
var KNOWN_CRL_ENTRY_DETAIL_KEYS = { reason: 1 };
|
|
324
261
|
var CRL_REASON_BY_NAME = intrinsic.create(null);
|
|
325
262
|
_forEach(_keys(constants.NAMES.CRL_REASON), function (v) { CRL_REASON_BY_NAME[constants.NAMES.CRL_REASON[v]] = intrinsic.Number(v); });
|
|
326
263
|
function _encodeCrlEntryDetails(spec) {
|
|
327
|
-
// A pre-encoded Extensions DER is classified through the byte guard and normalized through it, so
|
|
328
|
-
// every BufferSource form is taken as bytes. A narrowed check would drop a raw ArrayBuffer / DataView
|
|
329
|
-
// into the { reason } branch below, where an absent reason silently becomes a generated unspecified(0)
|
|
330
|
-
// reasonCode -- a different revocation reason than the caller encoded.
|
|
331
264
|
if (guard.bytes.isByteSource(spec)) return guard.bytes.snapshotSource(spec, CmpError, "cmp/bad-rev-req", "RevDetails.crlEntryDetails (a pre-encoded Extensions DER)");
|
|
332
|
-
// After the byte guard, the only valid structured form is a plain { reason? } record. Anything else --
|
|
333
|
-
// an array, a Date/Map/Set or other exotic, a boxed primitive -- carries no `reason`, so the fallback
|
|
334
|
-
// below would read undefined and emit a generated unspecified(0) reasonCode: a real revocation reason
|
|
335
|
-
// the caller never wrote, from a value outside the documented { reason } | DER union. assertKnownKeys
|
|
336
|
-
// does not catch these (an empty array or a keyless exotic has no unknown key to reject), so the value
|
|
337
|
-
// is held to being a plain record through guard.identifier.assertPlainRecord rather than a bare
|
|
338
|
-
// typeof-object test.
|
|
339
265
|
guard.identifier.assertPlainRecord(spec, _err, "cmp/bad-rev-req", "RevDetails.crlEntryDetails ({ reason } or a pre-encoded Extensions DER)");
|
|
340
266
|
guard.identifier.assertKnownKeys(spec, KNOWN_CRL_ENTRY_DETAIL_KEYS, _err, "cmp/bad-rev-req", "unknown crlEntryDetails field ");
|
|
341
|
-
// Read `reason` ONCE: a caller getter or Proxy could otherwise answer the validation with one value
|
|
342
|
-
// (a known reason) and the encode with another (null -> a generated unspecified(0)), so the request
|
|
343
|
-
// would carry a different revocation reason than the one that was checked.
|
|
344
|
-
// Read `reason` ONCE, consistent with the module's read-once discipline: build() already deep-copies
|
|
345
|
-
// the caller's message at entry, so a getter or Proxy cannot vary between the reads here, but reading
|
|
346
|
-
// one local keeps validation and encode on the same value with no dependence on that upstream copy.
|
|
347
267
|
var reason = spec.reason;
|
|
348
268
|
if (reason != null && (typeof reason !== "string" || !_hasOwn(CRL_REASON_BY_NAME, reason))) {
|
|
349
269
|
throw _err("cmp/bad-rev-req", "unknown CRLReason " + _stringify(reason) + " (RFC 5280 sec. 5.3.1)");
|
|
@@ -352,74 +272,32 @@ function _encodeCrlEntryDetails(spec) {
|
|
|
352
272
|
return b.sequence([_b.ext(O("reasonCode"), false, b.enumerated(_bigInt(code)))]);
|
|
353
273
|
}
|
|
354
274
|
|
|
355
|
-
// ---- support-message request values (RFC 9480 sec. 2.17, profiled by RFC 9483 sec. 4.3.4) ----
|
|
356
|
-
//
|
|
357
|
-
// CRLSource ::= CHOICE { dpn [0] DistributionPointName, issuer [1] GeneralNames }
|
|
358
|
-
// CRLStatus ::= SEQUENCE { source CRLSource, thisUpdate Time OPTIONAL }
|
|
359
|
-
// CRLStatusListValue ::= SEQUENCE SIZE (1..MAX) OF CRLStatus
|
|
360
|
-
//
|
|
361
|
-
// The CMP module is EXPLICIT TAGS, so CRLSource's own [0] / [1] wrap their alternative. The
|
|
362
|
-
// DistributionPointName inside the dpn arm keeps the IMPLICIT tagging of the module it is
|
|
363
|
-
// imported from, which is why its fullName [0] is a bare context-constructed GeneralNames.
|
|
364
275
|
var KNOWN_CRL_STATUS_KEYS = { dpn: 1, issuer: 1, thisUpdate: 1 };
|
|
365
276
|
var KNOWN_DPN_KEYS = { fullName: 1 };
|
|
366
277
|
function buildCrlStatusList(spec) {
|
|
367
278
|
if (!spec || typeof spec !== "object" || _isBuffer(spec)) throw _err("cmp/bad-input", "a crlUpdate request must be an object { dpn | issuer, thisUpdate? }");
|
|
368
279
|
guard.identifier.assertKnownKeys(spec, KNOWN_CRL_STATUS_KEYS, _err, "cmp/bad-input", "unknown crlUpdate field ");
|
|
369
|
-
// Each caller field is read ONCE, here, and every use below is of the value that was read. A
|
|
370
|
-
// second read of the same field can answer differently, and what is encoded then differs from
|
|
371
|
-
// what the answer is held to.
|
|
372
280
|
var dpnSpec = spec.dpn, issuerSpec = spec.issuer;
|
|
373
281
|
if (dpnSpec == null && issuerSpec == null) {
|
|
374
282
|
throw _err("cmp/bad-input", "a crlUpdate identifies the requested CRL by a dpn (a distribution point name), an issuer (the CA that issues it), or both (RFC 9483 sec. 4.3.4)");
|
|
375
283
|
}
|
|
376
|
-
// A distribution point name is a pointer the responder resolves internally, so on its own it
|
|
377
|
-
// leaves nothing to hold the answer to: a CRL claiming to be its issuer's complete list states no
|
|
378
|
-
// scope at all (RFC 5280 sec. 5.2.5 permits that), and the request named no issuer to compare it
|
|
379
|
-
// with, so any well-formed CRL from any CA would answer. The issuer is therefore required
|
|
380
|
-
// alongside a dpn, naming the CA whose CRL the caller will accept. sec. 4.3.4's own note says
|
|
381
|
-
// where a distribution point name comes from, and both sources -- a certificate's
|
|
382
|
-
// cRLDistributionPoints, a held CRL's issuingDistributionPoint -- carry the issuer with it.
|
|
383
284
|
if (dpnSpec != null && issuerSpec == null) {
|
|
384
285
|
throw _err("cmp/bad-input", "a crlUpdate naming a dpn must also name the issuer whose CRL is acceptable: a distribution point name alone leaves the response unbound, since a complete CRL states no scope to compare it against (RFC 9483 sec. 4.3.4, RFC 5280 sec. 5.2.5). Drive an unbound request with pki.cmp.build + pki.cmp.transfer.");
|
|
385
286
|
}
|
|
386
287
|
var source, issuerNameDer = null, dpnNames = null;
|
|
387
|
-
// The issuer, when given, is kept whether or not it goes on the wire: a caller who names one is
|
|
388
|
-
// saying which CA's CRL they will accept, and pki.cmp.session holds the answer to it. Taking the
|
|
389
|
-
// encoded Name from here means the comparison reads the name the caller gave rather than
|
|
390
|
-
// re-interpreting the spec at a later boundary.
|
|
391
288
|
if (issuerSpec != null) issuerNameDer = _b.encodeName(issuerSpec);
|
|
392
289
|
if (dpnSpec != null) {
|
|
393
|
-
// CRLSource is a CHOICE, so exactly one alternative goes on the wire, and sec. 4.3.4 settles
|
|
394
|
-
// which: the dpn "if the CRL distribution point name is available", otherwise the issuer. A
|
|
395
|
-
// caller giving both is answered by the dpn, the more specific pointer, with the issuer held
|
|
396
|
-
// back to bind the response.
|
|
397
290
|
if (typeof dpnSpec !== "object" || _isBuffer(dpnSpec)) throw _err("cmp/bad-input", "crlUpdate.dpn must be { fullName: [<GeneralName>...] }");
|
|
398
291
|
guard.identifier.assertKnownKeys(dpnSpec, KNOWN_DPN_KEYS, _err, "cmp/bad-input", "unknown crlUpdate.dpn field ");
|
|
399
|
-
// The nameRelativeToCRLIssuer arm of DistributionPointName is not built, matching the same
|
|
400
|
-
// decision in pki.crl.sign for issuingDistributionPoint and freshestCRL. Re-open it in all
|
|
401
|
-
// three together, since a partial relative-name surface would encode names one verb can
|
|
402
|
-
// produce and its sibling cannot.
|
|
403
292
|
var fullName = dpnSpec.fullName;
|
|
404
293
|
if (fullName == null) throw _err("cmp/bad-input", "crlUpdate.dpn requires fullName (the nameRelativeToCRLIssuer arm is not built; pki.crl.sign draws the same line for issuingDistributionPoint)");
|
|
405
294
|
var entries = _isArray(fullName) ? fullName : [fullName];
|
|
406
295
|
if (!entries.length) throw _err("cmp/bad-input", "crlUpdate.dpn.fullName must carry at least one GeneralName");
|
|
407
|
-
// The encoded GeneralNames are kept for the same reason as the issuer Name. RFC 5280 sec. 5.2.5
|
|
408
|
-
// pins the comparison to the identical encoding, so what is kept is the bytes that went on the
|
|
409
|
-
// wire rather than the spec they were built from.
|
|
410
296
|
dpnNames = _map(entries, function (e) { return _b.encodeGeneralName(e); });
|
|
411
297
|
source = b.explicit(0, b.contextConstructed(0, _bufferConcat(dpnNames)));
|
|
412
298
|
} else {
|
|
413
|
-
// sec. 4.3.4: the issuer choice "MUST contain the issuer DN in the directoryName field of a
|
|
414
|
-
// GeneralName element". The directoryName [4] EXPLICIT wraps the SAME encoded Name saved above for
|
|
415
|
-
// the response binding (issuerNameDer), not a second read of issuerSpec: a caller's issuer can be an
|
|
416
|
-
// accessor, and re-encoding it here would put one name on the wire while the answer is held to
|
|
417
|
-
// another, so a CRL from the name that was validated could answer a request that named a different one.
|
|
418
299
|
source = b.explicit(1, b.sequence([b.explicit(4, b.raw(issuerNameDer))]));
|
|
419
300
|
}
|
|
420
|
-
// Read ONCE. A caller's thisUpdate can be an accessor, so encoding from one read and validating
|
|
421
|
-
// against a second could judge the answer against a cutoff the request never carried. The instant
|
|
422
|
-
// that went on the wire is returned with the bytes, and it is that value the response is held to.
|
|
423
301
|
var status = [source], askedInstant = null, asked = spec.thisUpdate;
|
|
424
302
|
if (asked != null) {
|
|
425
303
|
askedInstant = guard.time.instantOf(asked, _err, "cmp/bad-input", "crlUpdate.thisUpdate");
|
|
@@ -433,20 +311,12 @@ function buildCrlStatusList(spec) {
|
|
|
433
311
|
};
|
|
434
312
|
}
|
|
435
313
|
|
|
436
|
-
// The distributionPoint [0] of a CRL's issuingDistributionPoint extension (RFC 5280 sec. 5.2.5),
|
|
437
|
-
// decoded to the DistributionPointName form guard.name.dpnCorresponds compares, or null when the
|
|
438
|
-
// extension names no point -- a complete CRL, whose scope is its issuer's whole population rather
|
|
439
|
-
// than one point of it. A structural fault THROWS instead of reading as "names no point": a session
|
|
440
|
-
// binds a requested distribution point to this value, and an unreadable extension resolving to
|
|
441
|
-
// "nothing to compare" would skip the binding it is read for.
|
|
442
314
|
var IDP_SCHEMA = pkix.issuingDistributionPoint("cmp/bad-info-value");
|
|
443
315
|
function decodeIdpDistributionPoint(valueBytes) {
|
|
444
316
|
var m;
|
|
445
317
|
try { m = schema.walk(IDP_SCHEMA, asn1.decode(valueBytes), NS); }
|
|
446
318
|
catch (e) { throw _err("cmp/bad-info-value", "the returned CRL carries a malformed issuingDistributionPoint extension (RFC 5280 sec. 5.2.5)", e); }
|
|
447
319
|
if (!m.fields.distributionPoint.present) return null;
|
|
448
|
-
// distributionPoint [0] EXPLICIT-wraps the DistributionPointName CHOICE (a context tag on a
|
|
449
|
-
// CHOICE-typed field is always EXPLICIT), so the name is its single child.
|
|
450
320
|
var wrap = m.fields.distributionPoint.node;
|
|
451
321
|
if (!wrap.children || wrap.children.length !== 1) {
|
|
452
322
|
throw _err("cmp/bad-info-value", "an issuingDistributionPoint distributionPoint [0] must wrap exactly one DistributionPointName (RFC 5280 sec. 5.2.5)");
|
|
@@ -459,20 +329,16 @@ function _encodeRevReqContent(list) {
|
|
|
459
329
|
return b.sequence(list.map(_encodeRevDetails));
|
|
460
330
|
}
|
|
461
331
|
|
|
462
|
-
// ---- response-side (CA/responder) PKIBody arm content encoders ----
|
|
463
332
|
|
|
464
|
-
// CertOrEncCert ::= CHOICE { certificate [0] EXPLICIT CMPCertificate, encryptedCert [1] EXPLICIT EncryptedKey }.
|
|
465
333
|
function _encodeCertOrEncCert(coec, state) {
|
|
466
334
|
if (!coec || typeof coec !== "object" || Buffer.isBuffer(coec)) throw _err("cmp/bad-cert-response", "certifiedKeyPair must carry certificate or encryptedCert");
|
|
467
335
|
if (coec.certificate != null && coec.encryptedCert != null) throw _err("cmp/bad-cert-response", "certOrEncCert is a CHOICE: supply exactly one of certificate or encryptedCert, not both");
|
|
468
336
|
if (coec.certificate != null) {
|
|
469
337
|
var certDer = _b.reqDerSequence(coec.certificate, "certifiedKeyPair.certificate (a Certificate DER)");
|
|
470
338
|
try { x509.parse(certDer); } catch (e) { if (e instanceof CmpError) throw e; throw _err("cmp/bad-cert-response", "certifiedKeyPair.certificate is not a valid X.509 certificate", e); }
|
|
471
|
-
return b.explicit(0, b.raw(certDer));
|
|
339
|
+
return b.explicit(0, b.raw(certDer));
|
|
472
340
|
}
|
|
473
341
|
if (coec.encryptedCert != null) {
|
|
474
|
-
// encryptedCert [1] wraps an EncryptedKey (pre-encoded DER hatch in v1). Like the privateKey, only the
|
|
475
|
-
// EnvelopedData [0] form is cmp2021; the deprecated EncryptedValue (a universal SEQUENCE) is cmp2000.
|
|
476
342
|
var ec = _b.reqDer(coec.encryptedCert, "certifiedKeyPair.encryptedCert (a pre-encoded EncryptedKey DER)");
|
|
477
343
|
var ecNode;
|
|
478
344
|
try { ecNode = asn1.decode(ec); } catch (e) { throw _err("cmp/bad-cert-response", "certifiedKeyPair.encryptedCert is not valid DER", e); }
|
|
@@ -482,15 +348,12 @@ function _encodeCertOrEncCert(coec, state) {
|
|
|
482
348
|
throw _err("cmp/bad-cert-response", "certifiedKeyPair must carry certificate or encryptedCert");
|
|
483
349
|
}
|
|
484
350
|
|
|
485
|
-
// CertifiedKeyPair ::= SEQUENCE { certOrEncCert, privateKey [0] EXPLICIT OPTIONAL, publicationInfo [1] EXPLICIT OPTIONAL }.
|
|
486
351
|
function _encodeCertifiedKeyPair(ckp, state) {
|
|
487
352
|
if (!ckp || typeof ckp !== "object" || Buffer.isBuffer(ckp)) throw _err("cmp/bad-cert-response", "certifiedKeyPair must be an object");
|
|
488
|
-
var children = [_encodeCertOrEncCert(ckp, state)];
|
|
353
|
+
var children = [_encodeCertOrEncCert(ckp, state)];
|
|
489
354
|
if (ckp.privateKey != null) {
|
|
490
355
|
var pk = _b.reqDer(ckp.privateKey, "certifiedKeyPair.privateKey (a pre-encoded EncryptedKey DER)");
|
|
491
356
|
children.push(b.explicit(0, b.raw(pk)));
|
|
492
|
-
// EncryptedKey ::= CHOICE { encryptedValue EncryptedValue (a universal SEQUENCE, the deprecated cmp2000
|
|
493
|
-
// form), envelopedData [0] (a cmp2021 feature). Only the envelopedData form bumps pvno to cmp2021(3).
|
|
494
357
|
var pkNode;
|
|
495
358
|
try { pkNode = asn1.decode(pk); } catch (e) { throw _err("cmp/bad-cert-response", "certifiedKeyPair.privateKey is not valid DER", e); }
|
|
496
359
|
if (pkNode.tagClass === "context" && pkNode.tagNumber === 0) state.usesCmp2021 = true;
|
|
@@ -499,13 +362,10 @@ function _encodeCertifiedKeyPair(ckp, state) {
|
|
|
499
362
|
return b.sequence(children);
|
|
500
363
|
}
|
|
501
364
|
|
|
502
|
-
// CertResponse ::= SEQUENCE { certReqId INTEGER, status PKIStatusInfo, certifiedKeyPair OPTIONAL, rspInfo OCTET STRING OPTIONAL }.
|
|
503
365
|
function _encodeCertResponse(cr, state) {
|
|
504
366
|
if (!cr || typeof cr !== "object" || Buffer.isBuffer(cr)) throw _err("cmp/bad-cert-response", "each CertResponse must be an object");
|
|
505
367
|
if (cr.status == null) throw _err("cmp/bad-cert-response", "CertResponse.status (a PKIStatusInfo) is required");
|
|
506
368
|
var hasCkp = cr.certifiedKeyPair != null;
|
|
507
|
-
// RFC 9810 sec. 5.3.4: failInfo XOR certifiedKeyPair; a certifiedKeyPair is allowed only under a granting
|
|
508
|
-
// status (accepted 0 / grantedWithMods 1).
|
|
509
369
|
if (hasCkp && cr.status.failInfo != null) throw _err("cmp/bad-cert-response", "a CertResponse must not carry both failInfo and certifiedKeyPair");
|
|
510
370
|
if (hasCkp && cr.status.status !== 0 && cr.status.status !== 1) throw _err("cmp/bad-cert-response", "a CertResponse certifiedKeyPair is allowed only under a granting status (accepted / grantedWithMods)");
|
|
511
371
|
var children = [b.integer(_reqIdInt(cr.certReqId, "cmp/bad-cert-response", "CertResponse certReqId")), _encodePkiStatusInfo(cr.status, "cmp/bad-cert-response")];
|
|
@@ -514,12 +374,11 @@ function _encodeCertResponse(cr, state) {
|
|
|
514
374
|
return b.sequence(children);
|
|
515
375
|
}
|
|
516
376
|
|
|
517
|
-
// CertRepMessage ::= SEQUENCE { caPubs [1] EXPLICIT SEQUENCE OF CMPCertificate OPTIONAL, response SEQUENCE OF CertResponse }.
|
|
518
377
|
function _encodeCertRepMessage(spec, state, arm) {
|
|
519
378
|
if (!spec || typeof spec !== "object" || Buffer.isBuffer(spec)) throw _err("cmp/bad-cert-rep", arm + " must be an object { caPubs?, response }");
|
|
520
379
|
if (!Array.isArray(spec.response)) throw _err("cmp/bad-cert-rep", arm + ".response must be an array of CertResponse");
|
|
521
380
|
var children = [];
|
|
522
|
-
if (spec.caPubs != null) {
|
|
381
|
+
if (spec.caPubs != null) {
|
|
523
382
|
if (!Array.isArray(spec.caPubs) || !spec.caPubs.length) throw _err("cmp/bad-cert-rep", arm + ".caPubs must be a non-empty array of certificate DERs");
|
|
524
383
|
children.push(b.explicit(1, b.sequence(spec.caPubs.map(function (c) { return _certRaw(c, "cmp/bad-cert-rep", "a caPubs entry"); }))));
|
|
525
384
|
}
|
|
@@ -527,14 +386,12 @@ function _encodeCertRepMessage(spec, state, arm) {
|
|
|
527
386
|
return b.sequence(children);
|
|
528
387
|
}
|
|
529
388
|
|
|
530
|
-
// A raw Certificate DER, validated via the real parser before it is embedded.
|
|
531
389
|
function _certRaw(c, code, what) {
|
|
532
390
|
var der = _b.reqDerSequence(c, what + " (a Certificate DER)");
|
|
533
391
|
try { x509.parse(der); } catch (e) { if (e instanceof CmpError) throw e; throw _err(code, what + " is not a valid X.509 certificate", e); }
|
|
534
392
|
return b.raw(der);
|
|
535
393
|
}
|
|
536
394
|
|
|
537
|
-
// RevRepContent ::= SEQUENCE { status SEQ OF PKIStatusInfo (min 1), revCerts [0] EXPLICIT SEQ OF CertId OPTIONAL, crls [1] EXPLICIT SEQ OF CertificateList OPTIONAL }.
|
|
538
395
|
function _encodeCertIdRr(cid) {
|
|
539
396
|
if (!cid || typeof cid !== "object" || Buffer.isBuffer(cid) || cid.issuer == null || cid.serialNumber == null) throw _err("cmp/bad-rev-rep", "each revCerts CertId must be { issuer, serialNumber }");
|
|
540
397
|
return b.sequence([_b.encodeGeneralName(cid.issuer), b.integer(_reqIdInt(cid.serialNumber, "cmp/bad-rev-rep", "CertId serialNumber"))]);
|
|
@@ -551,7 +408,7 @@ function _encodeRevRepContent(spec) {
|
|
|
551
408
|
if (!Array.isArray(spec.crls) || !spec.crls.length) throw _err("cmp/bad-rev-rep", "rp.crls must be a non-empty array of CertificateList DERs");
|
|
552
409
|
children.push(b.explicit(1, b.sequence(spec.crls.map(function (c) {
|
|
553
410
|
var der = _b.reqDerSequence(c, "crls entry (a CertificateList DER)");
|
|
554
|
-
try { crl.parse(der); }
|
|
411
|
+
try { crl.parse(der); }
|
|
555
412
|
catch (e) { if (e instanceof CmpError) throw e; throw _err("cmp/bad-rev-rep", "a crls entry is not a valid CRL (CertificateList)", e); }
|
|
556
413
|
return b.raw(der);
|
|
557
414
|
}))));
|
|
@@ -559,7 +416,6 @@ function _encodeRevRepContent(spec) {
|
|
|
559
416
|
return b.sequence(children);
|
|
560
417
|
}
|
|
561
418
|
|
|
562
|
-
// ErrorMsgContent ::= SEQUENCE { pKIStatusInfo, errorCode INTEGER OPTIONAL, errorDetails PKIFreeText OPTIONAL }.
|
|
563
419
|
function _encodeErrorMsgContent(spec) {
|
|
564
420
|
if (!spec || typeof spec !== "object" || Buffer.isBuffer(spec)) throw _err("cmp/bad-error", "error must be an object { pKIStatusInfo, errorCode?, errorDetails? }");
|
|
565
421
|
if (spec.pKIStatusInfo == null) throw _err("cmp/bad-error", "error.pKIStatusInfo is required");
|
|
@@ -569,7 +425,6 @@ function _encodeErrorMsgContent(spec) {
|
|
|
569
425
|
return b.sequence(children);
|
|
570
426
|
}
|
|
571
427
|
|
|
572
|
-
// PollRepContent ::= SEQUENCE OF SEQUENCE { certReqId INTEGER, checkAfter INTEGER (seconds, >= 0), reason PKIFreeText OPTIONAL }.
|
|
573
428
|
function _encodePollRepContent(list) {
|
|
574
429
|
if (!Array.isArray(list) || !list.length) throw _err("cmp/bad-poll-rep", "pollRep must be a non-empty array of { certReqId, checkAfter, reason? }");
|
|
575
430
|
return b.sequence(list.map(function (pr) {
|
|
@@ -581,7 +436,6 @@ function _encodePollRepContent(list) {
|
|
|
581
436
|
}));
|
|
582
437
|
}
|
|
583
438
|
|
|
584
|
-
// KeyRecRepContent ::= SEQUENCE { status PKIStatusInfo, newSigCert [0] EXPLICIT CMPCertificate OPTIONAL, caCerts [1] EXPLICIT SEQ OF CMPCertificate OPTIONAL, keyPairHist [2] EXPLICIT SEQ OF CertifiedKeyPair OPTIONAL }.
|
|
585
439
|
function _encodeKeyRecRepContent(spec, state) {
|
|
586
440
|
if (!spec || typeof spec !== "object" || Buffer.isBuffer(spec)) throw _err("cmp/bad-key-rec-rep", "krp must be an object { status, newSigCert?, caCerts?, keyPairHist? }");
|
|
587
441
|
if (spec.status == null) throw _err("cmp/bad-key-rec-rep", "krp.status is required");
|
|
@@ -598,17 +452,9 @@ function _encodeKeyRecRepContent(spec, state) {
|
|
|
598
452
|
return b.sequence(children);
|
|
599
453
|
}
|
|
600
454
|
|
|
601
|
-
// Does a built CertReqMessages carry a proof of possession that RFC 9810 sec. 5.2.8.3 couples to
|
|
602
|
-
// cmp2021(3)? Stated over the RFC's pair rather than over the arms this toolkit happens to emit, so
|
|
603
|
-
// the coupling still holds if agreeMAC is built later.
|
|
604
455
|
var CMP2021_POP_METHODS = { agreeMAC: 1, encryptedKey: 1 };
|
|
605
456
|
function _popNeedsCmp2021(crmfDer) {
|
|
606
457
|
var messages;
|
|
607
|
-
// Re-reading a CertReqMessages this process just produced. A fault here is an internal
|
|
608
|
-
// inconsistency, not a caller error, so it is named in this module's namespace: a crmf/* parse code
|
|
609
|
-
// surfacing from cmp.build would send the operator looking at a request they wrote correctly. A
|
|
610
|
-
// CRMF fault raised while BUILDING that body still passes through untranslated, where it does name
|
|
611
|
-
// the caller's own input.
|
|
612
458
|
try { messages = schemaCrmf.parse(crmfDer).messages; }
|
|
613
459
|
catch (e) {
|
|
614
460
|
throw _err("cmp/bad-body", "the certificate request body this message just built did not parse back: " +
|
|
@@ -621,37 +467,18 @@ function _popNeedsCmp2021(crmfDer) {
|
|
|
621
467
|
return false;
|
|
622
468
|
}
|
|
623
469
|
|
|
624
|
-
// nested [20] NestedMessageContent ::= PKIMessages ::= SEQUENCE SIZE (1..MAX) OF PKIMessage (RFC 9810 sec.
|
|
625
|
-
// 5.1.3.5 "Multiple Protection"): an RA forwards or batches complete, independently-protected PKIMessages,
|
|
626
|
-
// wrapping them UNCHANGED under its OWN protection. Each entry is a full PKIMessage DER, checked by the real
|
|
627
|
-
// parser so a non-PKIMessage fails closed here rather than wrapping as opaque bytes. The check is the toolkit's
|
|
628
|
-
// PKIMessage parse: it validates the envelope and every arm the parser decodes and, exactly as a parse
|
|
629
|
-
// anywhere else does, surfaces the raw arms (p10cr, the announcements, a further nested) without re-validating
|
|
630
|
-
// their content -- which sec. 5.1.3.5 leaves to the recipient, since the RA forwards each message unchanged.
|
|
631
|
-
// The wrapper's protection covers this SEQUENCE, not the inner messages, and the parser never auto-recurses a
|
|
632
|
-
// nested body (the operator re-feeds each inner message).
|
|
633
470
|
function _encodeNestedContent(list) {
|
|
634
471
|
if (!intrinsic.isArray(list) || list.length === 0) {
|
|
635
472
|
throw _err("cmp/bad-input", "nested must be a non-empty array of PKIMessage DER (RFC 9810 sec. 5.1.3.5 PKIMessages is SIZE (1..MAX))");
|
|
636
473
|
}
|
|
637
|
-
// reqDenseArray rejects a sparse hole or a nullish entry up front: Array.prototype.map SKIPS a hole,
|
|
638
|
-
// which would otherwise reach Buffer.concat as a native ERR_INVALID_ARG_TYPE and, on a huge sparse
|
|
639
|
-
// length, traverse the empty slots. With the array proven dense, the map validates each entry in turn.
|
|
640
474
|
return b.sequence(intrinsic.map(_b.reqDenseArray(list, "nested"), function (m, i) {
|
|
641
|
-
// reqDerSequence requires a DER SEQUENCE up front: cmp.parse also unwraps PEM from a byte source, so a
|
|
642
|
-
// PEM-armored entry (a Buffer straight from fs.readFileSync) would otherwise pass the parse yet be
|
|
643
|
-
// embedded by b.raw as ASCII armor and rejected as cmp/bad-der by the outer message's own parse, only
|
|
644
|
-
// after protection is computed. With DER assured, the bytes cmp.parse checks are the bytes embedded.
|
|
645
475
|
var der = _b.reqDerSequence(m, "nested[" + i + "] (a PKIMessage DER)");
|
|
646
|
-
|
|
647
|
-
// the offending index (the underlying reason rides along as the cause), never the parser's internal code.
|
|
648
|
-
try { cmp.parse(der); } // check each embedded message parses as a PKIMessage (fail-closed on a non-PKIMessage)
|
|
476
|
+
try { cmp.parse(der); }
|
|
649
477
|
catch (e) { throw _err("cmp/bad-input", "nested[" + i + "] does not parse as a PKIMessage", e); }
|
|
650
478
|
return b.raw(der);
|
|
651
479
|
}));
|
|
652
480
|
}
|
|
653
481
|
|
|
654
|
-
// Dispatch the single-key body object to its EXPLICIT-tagged arm. Returns { bodyTLV, usesCmp2021 }.
|
|
655
482
|
function _encodeBody(bodySpec, key, opts) {
|
|
656
483
|
if (!bodySpec || typeof bodySpec !== "object" || Buffer.isBuffer(bodySpec)) throw _err("cmp/bad-input", "message.body must be a single-key object");
|
|
657
484
|
var keys = Object.keys(bodySpec);
|
|
@@ -661,9 +488,6 @@ function _encodeBody(bodySpec, key, opts) {
|
|
|
661
488
|
var tag = BODY_TAG[arm], state = { usesCmp2021: false };
|
|
662
489
|
|
|
663
490
|
if (CRMF_BODY[arm]) {
|
|
664
|
-
// The CRMF proof of possession is signed with the REQUESTED key's private half, which is distinct from
|
|
665
|
-
// the message-protection key. Take it from a `key` field on the request spec; default to the protection
|
|
666
|
-
// key only for a self-request (the client certifying the very key it protects with).
|
|
667
491
|
var reqSpec = bodySpec[arm], popKey = key;
|
|
668
492
|
if (reqSpec && typeof reqSpec === "object" && !Buffer.isBuffer(reqSpec) && "key" in reqSpec) {
|
|
669
493
|
popKey = reqSpec.key;
|
|
@@ -671,12 +495,6 @@ function _encodeBody(bodySpec, key, opts) {
|
|
|
671
495
|
delete reqSpec.key;
|
|
672
496
|
}
|
|
673
497
|
return crmf.build(reqSpec, popKey, {}).then(function (crmfDer) {
|
|
674
|
-
// RFC 9810 sec. 5.2.8.3: "When using agreeMAC or encryptedKey choices, the pvno cmp2021(3)
|
|
675
|
-
// MUST be used." Read off the message that was actually emitted rather than off the spec that
|
|
676
|
-
// asked for it, so the announced version answers for the bytes on the wire. The parser
|
|
677
|
-
// enforces the same rule on the way in, and before this the two disagreed: a request carrying
|
|
678
|
-
// an encryptedKey proof went out announcing cmp2000(2), and this toolkit's own reader refused
|
|
679
|
-
// the message this toolkit had just built.
|
|
680
498
|
return { bodyTLV: b.explicit(tag, b.raw(crmfDer)), usesCmp2021: _popNeedsCmp2021(crmfDer) };
|
|
681
499
|
});
|
|
682
500
|
}
|
|
@@ -684,13 +502,8 @@ function _encodeBody(bodySpec, key, opts) {
|
|
|
684
502
|
if (arm === "p10cr") {
|
|
685
503
|
var csrDer = _b.reqDerSequence(bodySpec.p10cr, "p10cr (a CertificationRequest DER)");
|
|
686
504
|
var parsedCsr;
|
|
687
|
-
try { parsedCsr = csr.parse(csrDer); }
|
|
505
|
+
try { parsedCsr = csr.parse(csrDer); }
|
|
688
506
|
catch (e) { if (e instanceof CmpError) throw e; throw _err("cmp/bad-input", "p10cr is not a valid PKCS#10 CertificationRequest", e); }
|
|
689
|
-
// Verify the embedded request's proof-of-possession before it is protected and sent: a p10cr carries
|
|
690
|
-
// a PKCS#10 CertificationRequest (RFC 9810 sec. 5.3.3, whose structure is RFC 2986), and its
|
|
691
|
-
// self-signature under its own subject public key is that request's proof-of-possession. A CSR whose
|
|
692
|
-
// self-signature does not verify is one the CA will reject, so refuse it rather than send it -- through
|
|
693
|
-
// the shared inbound-PoP seam pki.csr.verify uses.
|
|
694
507
|
return _p10crBody(csrDer, parsedCsr, tag, state);
|
|
695
508
|
}
|
|
696
509
|
if (arm === "certConf") {
|
|
@@ -701,11 +514,11 @@ function _encodeBody(bodySpec, key, opts) {
|
|
|
701
514
|
inner = _encodeGenMsgContent(bodySpec.genm);
|
|
702
515
|
} else if (arm === "rr") {
|
|
703
516
|
inner = _encodeRevReqContent(bodySpec.rr);
|
|
704
|
-
} else if (CERT_REP_ARM[arm]) {
|
|
517
|
+
} else if (CERT_REP_ARM[arm]) {
|
|
705
518
|
inner = _encodeCertRepMessage(bodySpec[arm], state, arm);
|
|
706
519
|
} else if (arm === "rp") {
|
|
707
520
|
inner = _encodeRevRepContent(bodySpec.rp);
|
|
708
|
-
} else if (arm === "genp") {
|
|
521
|
+
} else if (arm === "genp") {
|
|
709
522
|
inner = _encodeGenMsgContent(bodySpec.genp);
|
|
710
523
|
} else if (arm === "error") {
|
|
711
524
|
inner = _encodeErrorMsgContent(bodySpec.error);
|
|
@@ -715,16 +528,13 @@ function _encodeBody(bodySpec, key, opts) {
|
|
|
715
528
|
inner = _encodeKeyRecRepContent(bodySpec.krp, state);
|
|
716
529
|
} else if (arm === "nested") {
|
|
717
530
|
inner = _encodeNestedContent(bodySpec.nested);
|
|
718
|
-
} else {
|
|
531
|
+
} else {
|
|
719
532
|
if (bodySpec.pkiconf !== null && bodySpec.pkiconf !== true) throw _err("cmp/bad-input", "pkiconf takes null or true (PKIConfirmContent is NULL)");
|
|
720
533
|
inner = b.nullValue();
|
|
721
534
|
}
|
|
722
535
|
return Promise.resolve({ bodyTLV: b.explicit(tag, inner), usesCmp2021: state.usesCmp2021 });
|
|
723
536
|
}
|
|
724
537
|
|
|
725
|
-
// `await`, not a `.then` chain: the PoP verdict is read through the native promise job, not the
|
|
726
|
-
// replaceable Promise.prototype.then a co-resident could swap to force `ok` true and slip an
|
|
727
|
-
// unverified request past. The caller has already validated csrDer's structure synchronously.
|
|
728
538
|
async function _p10crBody(csrDer, parsedCsr, tag, state) {
|
|
729
539
|
if (!(await csrVerify.verifyCsrSignature(parsedCsr))) {
|
|
730
540
|
throw _err("cmp/bad-popo", "the p10cr CertificationRequest failed its proof-of-possession: its PKCS#10 signature does not verify under its own subject public key, so a CA would reject it");
|
|
@@ -732,11 +542,8 @@ async function _p10crBody(csrDer, parsedCsr, tag, state) {
|
|
|
732
542
|
return { bodyTLV: b.explicit(tag, b.raw(csrDer)), usesCmp2021: state.usesCmp2021 };
|
|
733
543
|
}
|
|
734
544
|
|
|
735
|
-
// ---- protection ----
|
|
736
545
|
|
|
737
546
|
|
|
738
|
-
// Resolve the protection selector to { protectionAlgDer, computeBits(protectedPartDer)->Promise<Buffer> },
|
|
739
|
-
// senderSpki, senderScheme } before the header is built (protectionAlg is opts-derived, not header-derived).
|
|
740
547
|
function _resolveProtection(opts) {
|
|
741
548
|
var hasSig = opts.key != null || opts.cert != null;
|
|
742
549
|
var hasMac = opts.mac != null;
|
|
@@ -764,7 +571,6 @@ function _resolveProtection(opts) {
|
|
|
764
571
|
};
|
|
765
572
|
}
|
|
766
573
|
|
|
767
|
-
// PBMAC1
|
|
768
574
|
var m = opts.mac;
|
|
769
575
|
if (!m || typeof m !== "object" || Buffer.isBuffer(m)) throw _err("cmp/bad-input", "opts.mac must be an object { secret, salt?, iterationCount?, prf?, keyLength? }");
|
|
770
576
|
guard.identifier.assertKnownKeys(m, KNOWN_MAC_KEYS, _err, "cmp/bad-input", "unknown opts.mac field ");
|
|
@@ -774,28 +580,17 @@ function _resolveProtection(opts) {
|
|
|
774
580
|
if (!Buffer.isBuffer(secret) || !secret.length) throw _err("cmp/bad-input", "opts.mac.secret must be a non-empty string or Buffer");
|
|
775
581
|
}
|
|
776
582
|
var secretBuf = Buffer.isBuffer(secret) ? secret : Buffer.from(secret, "utf8");
|
|
777
|
-
// Default only when prf is absent (null/undefined), the same test the iterationCount and keyLength
|
|
778
|
-
// siblings below use. `m.prf || "SHA-256"` would default on any falsy value, so a supplied "", false, or
|
|
779
|
-
// 0 -- none a valid PRF name -- would silently encode HMAC-SHA-256 instead of being refused.
|
|
780
583
|
var prf = m.prf != null ? m.prf : "SHA-256";
|
|
781
584
|
if (!PBMAC1_PRF[prf]) throw _err("cmp/bad-input", "opts.mac.prf must be SHA-256 / SHA-384 / SHA-512");
|
|
782
585
|
var iterationCount = m.iterationCount != null ? m.iterationCount : PBMAC1_DEFAULT_ITER;
|
|
783
586
|
if (typeof iterationCount !== "number" || !Number.isInteger(iterationCount) || iterationCount < PBMAC1_MIN_ITER) throw _err("cmp/bad-input", "opts.mac.iterationCount must be at least " + PBMAC1_MIN_ITER + " (RFC 8018 sec. 4.2)");
|
|
784
|
-
// Bound the PBKDF2 work factors before deriving: a huge iterationCount or keyLength is self-inflicted work.
|
|
785
587
|
if (iterationCount > constants.LIMITS.PBKDF2_MAX_ITERATIONS) throw _err("cmp/bad-input", "opts.mac.iterationCount exceeds the PBKDF2 work-factor cap " + constants.LIMITS.PBKDF2_MAX_ITERATIONS);
|
|
786
588
|
var keyLength = m.keyLength != null ? m.keyLength : PBMAC1_DEFAULT_KEYLEN;
|
|
787
|
-
// RFC 9579 sec. 9 floor (>= 20 bytes): produce only messages the verifier (pki.cmp.verify, same floor) can
|
|
788
|
-
// accept, so a shorter derived key is refused at production time and never emitted as an unverifiable message.
|
|
789
589
|
if (typeof keyLength !== "number" || !Number.isInteger(keyLength) || keyLength < PBMAC1_MIN_KEYLEN) throw _err("cmp/bad-input", "opts.mac.keyLength must be an integer >= " + PBMAC1_MIN_KEYLEN + " bytes (RFC 9579 sec. 9)");
|
|
790
590
|
if (keyLength > PBMAC1_MAX_KEYLEN) throw _err("cmp/bad-input", "opts.mac.keyLength exceeds the cap " + PBMAC1_MAX_KEYLEN + " bytes");
|
|
791
|
-
// Bound the COMBINED work: a keyLength spanning multiple PRF blocks costs iterationCount HMACs per block, so
|
|
792
|
-
// the product can exceed the per-block iteration cap. Cap it against the same ceiling pki.cmp.verify enforces,
|
|
793
|
-
// so build never emits a message its verify-inverse would refuse as over-budget (RFC 8018 sec. 5.2).
|
|
794
591
|
var blocks = Math.ceil(keyLength / PBMAC1_PRF_HLEN[prf]);
|
|
795
592
|
if (iterationCount * blocks > constants.LIMITS.PBKDF2_MAX_ITERATIONS) throw _err("cmp/bad-input", "opts.mac combined work (iterationCount " + iterationCount + " x " + blocks + " derived blocks) exceeds the PBKDF2 work-factor cap " + constants.LIMITS.PBKDF2_MAX_ITERATIONS);
|
|
796
593
|
var salt = m.salt != null ? _b.reqDer(m.salt, "opts.mac.salt") : Buffer.from(webcrypto.webcrypto.getRandomValues(new Uint8Array(PBMAC1_DEFAULT_SALT_BYTES)));
|
|
797
|
-
// RFC 8018 sec. 4.1 (64-bit) floor: an empty or short salt loses precomputation resistance, so produce only
|
|
798
|
-
// messages pki.cmp.verify (same floor) accepts, refusing a below-minimum salt at construction time.
|
|
799
594
|
if (salt.length < PBMAC1_MIN_SALT) throw _err("cmp/bad-input", "opts.mac.salt must be at least " + PBMAC1_MIN_SALT + " octets (RFC 8018 sec. 4.1)");
|
|
800
595
|
if (salt.length > constants.LIMITS.PBKDF2_MAX_SALT) throw _err("cmp/bad-input", "opts.mac.salt exceeds " + constants.LIMITS.PBKDF2_MAX_SALT + " bytes");
|
|
801
596
|
var macDesc = { salt: salt, iterationCount: iterationCount, keyLength: keyLength, prfName: PBMAC1_PRF[prf], macName: PBMAC1_MAC_OID[prf] };
|
|
@@ -811,12 +606,8 @@ function _resolveProtection(opts) {
|
|
|
811
606
|
};
|
|
812
607
|
}
|
|
813
608
|
|
|
814
|
-
// ---- orchestrator ----
|
|
815
609
|
|
|
816
610
|
function build(message, opts) {
|
|
817
|
-
// Every caller-owned argument copied at entry and released when the call settles; see the note
|
|
818
|
-
// on the same call in x509-sign. `opts.mac.secret` is why the copy has to be deep: it sits a
|
|
819
|
-
// level below the options object and is read by the PBMAC1 derivation after the first turn.
|
|
820
611
|
return guard.bytes.fixedCall(CmpError, "cmp/bad-input", [
|
|
821
612
|
[message, "the PKIMessage spec"], [opts, "pki.cmp.build options"],
|
|
822
613
|
], _build);
|
|
@@ -843,25 +634,22 @@ async function _build(message, opts) {
|
|
|
843
634
|
var prot = _resolveProtection(opts);
|
|
844
635
|
var senderKey = opts.key;
|
|
845
636
|
|
|
846
|
-
// `await`, not a `.then` chain: an embedded p10cr/crm proof-of-possession is enforced by _encodeBody
|
|
847
|
-
// REJECTING, so the verdict must reach here through the native promise job, not a Promise.prototype.then
|
|
848
|
-
// a co-resident could swap to fire this callback with a forged body and slip an unverified request past.
|
|
849
637
|
var bodyResult = await _encodeBody(message.body, senderKey, opts);
|
|
850
638
|
var bodyTLV = bodyResult.bodyTLV;
|
|
851
639
|
var pvno = message.header.pvno != null ? message.header.pvno : 2;
|
|
852
640
|
if (typeof pvno !== "number" || !Number.isInteger(pvno)) throw _err("cmp/bad-input", "header.pvno must be an integer");
|
|
853
|
-
if (bodyResult.usesCmp2021 && pvno < 3) pvno = 3;
|
|
641
|
+
if (bodyResult.usesCmp2021 && pvno < 3) pvno = 3;
|
|
854
642
|
|
|
855
|
-
var headerTLV = _encodeHeader(message.header, prot.protectionAlgDer, pvno);
|
|
856
|
-
var protectedPartDer = b.sequence([headerTLV, bodyTLV]);
|
|
643
|
+
var headerTLV = _encodeHeader(message.header, prot.protectionAlgDer, pvno);
|
|
644
|
+
var protectedPartDer = b.sequence([headerTLV, bodyTLV]);
|
|
857
645
|
|
|
858
646
|
var protectionBits = await prot.computeBits(protectedPartDer);
|
|
859
|
-
var msgChildren = [headerTLV, bodyTLV, b.explicit(0, protectionBits)];
|
|
647
|
+
var msgChildren = [headerTLV, bodyTLV, b.explicit(0, protectionBits)];
|
|
860
648
|
var extraCertsDers = _collectExtraCerts(opts, prot.certDer);
|
|
861
649
|
if (extraCertsDers.length) msgChildren.push(b.explicit(1, b.sequence(extraCertsDers)));
|
|
862
650
|
var der = b.sequence(msgChildren);
|
|
863
651
|
|
|
864
|
-
cmp.parse(der);
|
|
652
|
+
cmp.parse(der);
|
|
865
653
|
if (opts.pem != null) {
|
|
866
654
|
if (opts.pem === true) return cmp.pemEncode(der, "CMP");
|
|
867
655
|
if (typeof opts.pem !== "string" || !opts.pem) throw _err("cmp/bad-input", "opts.pem must be true or a non-empty PEM label string");
|
|
@@ -877,7 +665,7 @@ function _collectExtraCerts(opts, protCertDer) {
|
|
|
877
665
|
if (!Array.isArray(opts.extraCerts)) throw _err("cmp/bad-extra-certs", "opts.extraCerts must be an array of certificate DERs");
|
|
878
666
|
opts.extraCerts.forEach(function (c) {
|
|
879
667
|
var der = _b.reqDerSequence(c, "extraCerts entry (a Certificate DER)");
|
|
880
|
-
try { x509.parse(der); }
|
|
668
|
+
try { x509.parse(der); }
|
|
881
669
|
catch (e) { if (e instanceof CmpError) throw e; throw _err("cmp/bad-extra-certs", "an extraCerts entry is not a valid X.509 certificate", e); }
|
|
882
670
|
out.push(b.raw(der));
|
|
883
671
|
});
|
|
@@ -941,66 +729,40 @@ function _collectExtraCerts(opts, protCertDer) {
|
|
|
941
729
|
* pki.schema.cmp.parse(der).body.arm; // "p10cr"
|
|
942
730
|
*/
|
|
943
731
|
|
|
944
|
-
// ---- the thin RFC 9811 HTTP transfer verb: POST a DER PKIMessage over the shared pki.transport ----
|
|
945
|
-
//
|
|
946
|
-
// RFC 9811 defines exactly ONE client operation -- POST a DER PKIMessage, receive a DER PKIMessage -- so
|
|
947
|
-
// every CMP exchange (ir / p10cr / certConf / pollReq / rr / genm ...) rides this single stateless verb;
|
|
948
|
-
// the caller builds+protects the message upstream with pki.cmp.build and hands the finished bytes here,
|
|
949
|
-
// which are POSTed VERBATIM (the protection covers them). No socket is opened by this codec -- the verb
|
|
950
|
-
// composes pki.schema.cmp.parse over pki.transport, exactly as pki.est / pki.acme wire their message layers.
|
|
951
732
|
|
|
952
733
|
var DEFAULT_TRANSFER_TIMEOUT = constants.TIME.seconds(30);
|
|
953
734
|
var MAX_TRANSFER_TIMEOUT = constants.TIME.seconds(600);
|
|
954
735
|
var PKIXCMP = "application/pkixcmp";
|
|
955
|
-
var PKIXCMP_POLL = "application/pkixcmp-poll";
|
|
736
|
+
var PKIXCMP_POLL = "application/pkixcmp-poll";
|
|
956
737
|
var KNOWN_TRANSFER_OPTS = { transport: 1, tls: 1, headers: 1, timeout: 1, maxResponseBytes: 1 };
|
|
957
738
|
|
|
958
|
-
// The DER of a caller-supplied PKIMessage: a DER Buffer / Uint8Array re-viewed through guard.bytes.view
|
|
959
|
-
// (sent VERBATIM -- protection covers these exact bytes), a PEM "CMP" string decoded, else a config-time
|
|
960
|
-
// cmp/bad-input before the wire. The re-view fails a DETACHED backing buffer (a transferred / structuredClone'd
|
|
961
|
-
// view reads as zero-length) as cmp/bad-input rather than silently POSTing an empty body in its place.
|
|
962
739
|
function _cmpMessageDer(input) {
|
|
963
740
|
var der;
|
|
964
741
|
if (guard.bytes.isByteSource(input)) der = guard.bytes.source(input, CmpError, "cmp/bad-input", "the CMP message DER");
|
|
965
742
|
else if (typeof input === "string") {
|
|
966
|
-
// Normalize a PEM decode failure (no block, wrong armor label, malformed base64) to the stable
|
|
967
|
-
// cmp/bad-input at this input boundary, rather than leaking the PEM layer's PemError to the caller.
|
|
968
743
|
try { der = cmp.pemDecode(input); }
|
|
969
744
|
catch (e) { throw _err("cmp/bad-input", "the CMP message is not a valid PEM CMP block", e); }
|
|
970
745
|
} else throw _err("cmp/bad-input", "a CMP message must be a DER Buffer/Uint8Array or a PEM CMP string");
|
|
971
|
-
// Validate the bytes ARE a PKIMessage before the wire (transfer accepts a DER PKIMessage), so malformed
|
|
972
|
-
// input fails locally as cmp/bad-input rather than crossing the network seam. parse validates only -- the
|
|
973
|
-
// ORIGINAL der is returned for the verbatim POST (protection covers the exact bytes; nothing re-serialized).
|
|
974
746
|
try { cmp.parse(der); }
|
|
975
747
|
catch (e) { throw _err("cmp/bad-input", "the CMP message is not a valid PKIMessage", e); }
|
|
976
748
|
return der;
|
|
977
749
|
}
|
|
978
750
|
|
|
979
|
-
// Map opts.tls (operator-facing) to the transport request.tls shape. rejectUnauthorized is NOT here --
|
|
980
|
-
// the transport forces it on unconditionally; the verb never disables server verification.
|
|
981
751
|
function _tlsForTransfer(opts) {
|
|
982
752
|
var t = opts.tls || {};
|
|
983
753
|
return { anchors: t.anchors, useSystemStore: t.useSystemStore, cert: t.cert, key: t.key, minVersion: t.minVersion, servername: t.servername, checkServerIdentity: t.checkServerIdentity };
|
|
984
754
|
}
|
|
985
755
|
|
|
986
|
-
// The response content-type token (before any ;-parameters), lowercased; is it a CMP media type?
|
|
987
756
|
function _ctypeToken(headers) { return String((headers || {})["content-type"] || "").split(";")[0].trim().toLowerCase(); }
|
|
988
757
|
function _isPkixcmp(headers) { var t = _ctypeToken(headers); return t === PKIXCMP || t === PKIXCMP_POLL; }
|
|
989
758
|
|
|
990
|
-
// Classify the CMP HTTP response (RFC 9811 sec. 3.1/3.2/4). 200 + a pkixcmp body -> parse + resolve;
|
|
991
|
-
// another 2xx -> reject (a request response MUST be 200, sec. 3.1); 3xx -> not followed (sec. 3.1/5);
|
|
992
|
-
// 4xx/5xx WITH a well-formed CMP body -> FORWARD the integrity-protected verdict (sec. 1.2/3.1), else
|
|
993
|
-
// cmp/http-error. The HTTP status is surfaced as DATA, never used to set the transaction verdict (sec. 5).
|
|
994
|
-
// `body` is always a Buffer (the verb normalizes it before the size cap).
|
|
995
759
|
function _classifyCmpResponse(status, headers, body, tls) {
|
|
996
760
|
var ctype = _ctypeToken(headers);
|
|
997
761
|
if (status === 200) {
|
|
998
762
|
if (!_isPkixcmp(headers)) throw _err("cmp/bad-content-type", "a 200 CMP response must be application/pkixcmp (RFC 9811 sec. 3.2), got " + JSON.stringify(ctype || null));
|
|
999
763
|
if (body.length === 0) throw _err("cmp/empty-response", "a 200 CMP response carried an empty body (RFC 9811 sec. 3.3)");
|
|
1000
|
-
return { response: cmp.parse(body), responseBytes: body, status: 200, contentType: ctype, tls: tls };
|
|
764
|
+
return { response: cmp.parse(body), responseBytes: body, status: 200, contentType: ctype, tls: tls };
|
|
1001
765
|
}
|
|
1002
|
-
// Below 200, every branch requires a valid HTTP status INTEGER -- a missing / non-numeric / out-of-range
|
|
1003
|
-
// status from an injected transport must not fall through to the forward branch as if it were a 4xx/5xx.
|
|
1004
766
|
var httpStatus = (typeof status === "number" && Number.isSafeInteger(status) && status >= 100 && status <= 599) ? status : null;
|
|
1005
767
|
if (httpStatus !== null && httpStatus >= 300 && httpStatus < 400) throw _err("cmp/redirect-not-followed", "a CMP " + httpStatus + " redirect is not followed (RFC 9811 sec. 3.1, sec. 5); reconfigure the endpoint URL");
|
|
1006
768
|
if (httpStatus !== null && httpStatus >= 400 && httpStatus <= 599) {
|
|
@@ -1008,15 +770,11 @@ function _classifyCmpResponse(status, headers, body, tls) {
|
|
|
1008
770
|
var forwarded;
|
|
1009
771
|
try { forwarded = cmp.parse(body); }
|
|
1010
772
|
catch (e) { throw _err("cmp/http-error", "the CMP server returned HTTP " + httpStatus + " with an undecodable body", e); }
|
|
1011
|
-
// Only a CMP `error` message is a coherent verdict to forward on an HTTP failure. A 4xx/5xx carrying a
|
|
1012
|
-
// non-error arm (a granting `ip`, a `pkiconf`) is contradictory -- the HTTP layer says failure while the
|
|
1013
|
-
// CMP body says success -- so it is NOT accepted as a usable response; the HTTP failure stands.
|
|
1014
773
|
if (forwarded.body.arm !== "error") throw _err("cmp/http-error", "the CMP server returned HTTP " + httpStatus + " with a non-error '" + forwarded.body.arm + "' body; an HTTP failure forwards only a CMP error message (RFC 9811 sec. 1.2/3.1)");
|
|
1015
774
|
return { response: forwarded, responseBytes: body, status: httpStatus, contentType: ctype, tls: tls };
|
|
1016
775
|
}
|
|
1017
776
|
throw _err("cmp/http-error", "the CMP server returned HTTP " + httpStatus + " with no forwardable CMP error body");
|
|
1018
777
|
}
|
|
1019
|
-
// Any other status -- a non-200 2xx, a 1xx informational, or a non-numeric / out-of-range value.
|
|
1020
778
|
throw _err("cmp/unexpected-status", "a CMP response must be HTTP 200 (or a 4xx/5xx carrying a CMP error), not " + JSON.stringify(status) + " (RFC 9811 sec. 3.1)");
|
|
1021
779
|
}
|
|
1022
780
|
|
|
@@ -1027,9 +785,9 @@ function transfer(url, message, opts) {
|
|
|
1027
785
|
function _transfer(url, message, opts) {
|
|
1028
786
|
opts = opts || {};
|
|
1029
787
|
guard.identifier.assertKnownKeys(opts, KNOWN_TRANSFER_OPTS, _err, "cmp/bad-input", "unknown opts field ");
|
|
1030
|
-
var der = _cmpMessageDer(message);
|
|
788
|
+
var der = _cmpMessageDer(message);
|
|
1031
789
|
var parsedUrl;
|
|
1032
|
-
try { parsedUrl = new URL(String(url)); }
|
|
790
|
+
try { parsedUrl = new URL(String(url)); }
|
|
1033
791
|
catch (e) { throw _err("cmp/bad-url", "the CMP URL did not parse: " + String(url), e); }
|
|
1034
792
|
var transport = opts.transport;
|
|
1035
793
|
if (!transport) {
|
|
@@ -1040,11 +798,6 @@ function _transfer(url, message, opts) {
|
|
|
1040
798
|
}
|
|
1041
799
|
var timeout = guard.limits.cap(opts.timeout, "timeout", DEFAULT_TRANSFER_TIMEOUT, { E: _err, code: "cmp/bad-input", min: 1, max: MAX_TRANSFER_TIMEOUT });
|
|
1042
800
|
var maxResponseBytes = guard.limits.cap(opts.maxResponseBytes, "maxResponseBytes", constants.LIMITS.HTTP_MAX_RESPONSE_BYTES, { E: _err, code: "cmp/bad-input", min: 1, max: constants.LIMITS.HTTP_MAX_RESPONSE_BYTES });
|
|
1043
|
-
// Build the request headers: forward custom opts.headers but STRIP the request-framing headers
|
|
1044
|
-
// (content-length, transfer-encoding) and ANY content-type case variant -- the verb sets the content-type
|
|
1045
|
-
// and the transport computes Content-Length from the exact body, so a caller cannot desync the request
|
|
1046
|
-
// framing (HTTP request smuggling via a short/long content-length) or override the media type through
|
|
1047
|
-
// header-name casing (a "Content-Type" that Object.assign would keep alongside the lowercase one).
|
|
1048
801
|
var headers = {};
|
|
1049
802
|
Object.keys(opts.headers || {}).forEach(function (k) {
|
|
1050
803
|
var lk = k.toLowerCase();
|
|
@@ -1056,10 +809,6 @@ function _transfer(url, message, opts) {
|
|
|
1056
809
|
res = res || {};
|
|
1057
810
|
var h = {};
|
|
1058
811
|
Object.keys(res.headers || {}).forEach(function (k) { h[k.toLowerCase()] = res.headers[k]; });
|
|
1059
|
-
// Normalize the response body to a Buffer preserving its exact bytes: a binary view (Buffer / Uint8Array
|
|
1060
|
-
// from an injected transport) is re-viewed through the byte guard, as the request path does; only a
|
|
1061
|
-
// genuine string body falls back to a UTF-8 encode (the width a real socket already counts) so the size
|
|
1062
|
-
// cap and the parser see the same bytes -- a typed array is not stringified to "48,130,..." garbage.
|
|
1063
812
|
var body = guard.bytes.isByteSource(res.body)
|
|
1064
813
|
? guard.bytes.source(res.body, CmpError, "cmp/bad-response", "the CMP response body")
|
|
1065
814
|
: Buffer.from(String(res.body == null ? "" : res.body), "utf8");
|
|
@@ -1068,9 +817,6 @@ function _transfer(url, message, opts) {
|
|
|
1068
817
|
});
|
|
1069
818
|
}
|
|
1070
819
|
|
|
1071
|
-
// A single safe path segment for a well-known URI: no separator, dot-segment, or reserved/space char that
|
|
1072
|
-
// would retarget the resource; percent-encoded on the way out. Fails closed (guards never guess a value).
|
|
1073
|
-
// /[/?#\s\\]/ : does `s` contain a path separator, query/fragment marker, whitespace, or a backslash?
|
|
1074
820
|
function _hasUnsafeSegChar(s) {
|
|
1075
821
|
for (var i = 0; i < s.length; i++) {
|
|
1076
822
|
var c = _charCodeAt(s, i);
|
|
@@ -1081,18 +827,13 @@ function _hasUnsafeSegChar(s) {
|
|
|
1081
827
|
function _wellKnownSeg(v, name) {
|
|
1082
828
|
var s = String(v);
|
|
1083
829
|
if (s === "" || s === "." || s === ".." || _hasUnsafeSegChar(s)) throw _err("cmp/bad-url", "the CMP " + name + " must be a single safe path segment (no '/', dot-segment, or reserved char): " + JSON.stringify(s));
|
|
1084
|
-
// encodeURIComponent throws a raw URIError (no .code) on an unpaired UTF-16 surrogate; normalize it to the
|
|
1085
|
-
// helper's cmp/bad-url contract so a malformed-config segment never escapes as an untyped error.
|
|
1086
830
|
try { return encodeURIComponent(s); }
|
|
1087
831
|
catch (e) { throw _err("cmp/bad-url", "the CMP " + name + " is not encodable (e.g. an unpaired surrogate): " + JSON.stringify(s), e); }
|
|
1088
832
|
}
|
|
1089
833
|
|
|
1090
834
|
var KNOWN_WELLKNOWN_OPTS = { label: 1, operation: 1 };
|
|
1091
835
|
|
|
1092
|
-
// ---- URL raw-path helpers by character scan (no regex) --------------------------------------------
|
|
1093
836
|
function _isAsciiLetter(ch) { return (ch >= "A" && ch <= "Z") || (ch >= "a" && ch <= "z"); }
|
|
1094
|
-
// The "scheme://authority" prefix removed (matching /^[a-z]+:\/\/[^/?#]*/i: a leading letter run,
|
|
1095
|
-
// "://", then a run of characters other than "/", "?" or "#"), else the string unchanged.
|
|
1096
837
|
function _stripSchemeAuthority(s) {
|
|
1097
838
|
var p = 0, n = s.length;
|
|
1098
839
|
while (p < n && _isAsciiLetter(_charAt(s, p))) p += 1;
|
|
@@ -1101,7 +842,6 @@ function _stripSchemeAuthority(s) {
|
|
|
1101
842
|
while (p < n) { var ch = _charAt(s, p); if (ch === "/" || ch === "?" || ch === "#") break; p += 1; }
|
|
1102
843
|
return _strSlice(s, p);
|
|
1103
844
|
}
|
|
1104
|
-
// Everything before the first "?" or "#" (matching split(/[?#]/)[0]).
|
|
1105
845
|
function _beforeQueryOrFragment(s) {
|
|
1106
846
|
for (var i = 0; i < s.length; i++) { var ch = _charAt(s, i); if (ch === "?" || ch === "#") return _strSlice(s, 0, i); }
|
|
1107
847
|
return s;
|
|
@@ -1109,28 +849,15 @@ function _beforeQueryOrFragment(s) {
|
|
|
1109
849
|
|
|
1110
850
|
function wellKnownUrl(base, opts) {
|
|
1111
851
|
opts = opts || {};
|
|
1112
|
-
// Reject an unknown option key (a typo like { lable } / { operaton }), as build/transfer do -- a silently
|
|
1113
|
-
// ignored option would build the DEFAULT endpoint and send the request to a different CA/profile.
|
|
1114
852
|
guard.identifier.assertKnownKeys(opts, KNOWN_WELLKNOWN_OPTS, _err, "cmp/bad-input", function (k) {
|
|
1115
853
|
return "unknown wellKnownUrl option " + JSON.stringify(k) + " (expected label / operation)";
|
|
1116
854
|
});
|
|
1117
855
|
var u;
|
|
1118
856
|
try { u = new URL(String(base)); }
|
|
1119
857
|
catch (e) { throw _err("cmp/bad-url", "the CMP base URL did not parse: " + String(base), e); }
|
|
1120
|
-
// Require an http(s) authority: a non-HTTP scheme (file:, data:, a custom scheme) has an opaque origin
|
|
1121
|
-
// ("null"), which would build a nonsensical "null/.well-known/cmp". A well-known URI is an HTTP concept
|
|
1122
|
-
// (RFC 8615); https vs http is left to the transport (Build decision 6), but the scheme must be one of them.
|
|
1123
858
|
if (u.protocol !== "https:" && u.protocol !== "http:") throw _err("cmp/bad-url", "the CMP base URL must be http or https (a well-known URI is authority-rooted over HTTP), got " + u.protocol + ": " + JSON.stringify(String(base)));
|
|
1124
859
|
if (u.search || u.hash) throw _err("cmp/bad-url", "the CMP base URL must not carry a query or fragment component (RFC 9811 sec. 3.4)");
|
|
1125
|
-
// A backslash has no place in an http(s) authority: WHATWG rewrites it to a path separator (so
|
|
1126
|
-
// `https://ca.example\tenant` is really the path `/tenant`), but the raw-path regex below excludes only
|
|
1127
|
-
// `/?#` and would swallow it as authority, slipping a supplied path past the check. Reject it outright.
|
|
1128
860
|
if (_strIndexOf(String(base), "\\") !== -1) throw _err("cmp/bad-url", "the CMP base URL must not contain a backslash (WHATWG rewrites it to a path separator): " + JSON.stringify(String(base)));
|
|
1129
|
-
// An RFC 8615 well-known URI is AUTHORITY-ROOTED (/.well-known/...), so a base carrying ANY path is
|
|
1130
|
-
// rejected fail-closed -- an operator whose CMP endpoint sits under a path passes that full operation URL
|
|
1131
|
-
// to transfer() directly. Inspect the RAW path in the source string, NOT url.pathname: WHATWG normalizes
|
|
1132
|
-
// dot-segments (`/tenant/..`, `/%2e`) to "/" before the parse, which would slip a supplied path past a
|
|
1133
|
-
// pathname check and silently target the authority-root profile instead of reporting the misconfiguration.
|
|
1134
861
|
var rawBasePath = _beforeQueryOrFragment(_stripSchemeAuthority(String(base)));
|
|
1135
862
|
if (rawBasePath !== "" && rawBasePath !== "/") throw _err("cmp/bad-url", "an RFC 8615 well-known URI is authority-rooted; the CMP base URL must have no path component (pass a full operation URL to transfer instead): " + JSON.stringify(String(base)));
|
|
1136
863
|
var segs = [".well-known", "cmp"];
|
|
@@ -1210,17 +937,14 @@ function wellKnownUrl(base, opts) {
|
|
|
1210
937
|
*/
|
|
1211
938
|
module.exports = {
|
|
1212
939
|
build: build, transfer: transfer, wellKnownUrl: wellKnownUrl,
|
|
1213
|
-
buildCrlStatusList: buildCrlStatusList, // @internal
|
|
1214
|
-
// @internal
|
|
1215
|
-
// session compares root-rollover keys through the same definition the builders already use.
|
|
940
|
+
buildCrlStatusList: buildCrlStatusList, // @internal
|
|
941
|
+
// @internal
|
|
1216
942
|
samePublicKey: function (a, b) { return _b.samePublicKey(a, b); },
|
|
1217
|
-
// @internal
|
|
1218
|
-
// keyUsage through the same definition every format uses instead of walking the bytes again.
|
|
943
|
+
// @internal
|
|
1219
944
|
decodeCertExtension: function (dottedOid, valueBytes) {
|
|
1220
945
|
var dec = EXT_DECODERS[dottedOid];
|
|
1221
946
|
return dec ? dec(valueBytes) : null;
|
|
1222
947
|
},
|
|
1223
|
-
// @internal
|
|
1224
|
-
// session needs the point it names to bind a crlUpdate answer to the point that was requested.
|
|
948
|
+
// @internal
|
|
1225
949
|
decodeIdpDistributionPoint: decodeIdpDistributionPoint,
|
|
1226
950
|
};
|