@blamejs/pki 0.1.22 → 0.1.24
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 +47 -0
- package/README.md +18 -6
- package/index.js +22 -12
- package/lib/asn1-der.js +126 -68
- package/lib/constants.js +46 -21
- package/lib/ct.js +54 -39
- package/lib/est.js +717 -0
- package/lib/framework-error.js +74 -34
- package/lib/oid.js +116 -60
- package/lib/path-validate.js +302 -135
- package/lib/schema-all.js +65 -41
- package/lib/schema-attrcert.js +101 -64
- package/lib/schema-cmp.js +152 -131
- package/lib/schema-cms.js +621 -163
- package/lib/schema-crl.js +43 -21
- package/lib/schema-crmf.js +136 -79
- package/lib/schema-csr.js +78 -15
- package/lib/schema-csrattrs.js +371 -0
- package/lib/schema-engine.js +92 -43
- package/lib/schema-ocsp.js +101 -55
- package/lib/schema-pkcs12.js +80 -64
- package/lib/schema-pkcs8.js +12 -12
- package/lib/schema-pkix.js +295 -105
- package/lib/schema-smime.js +39 -39
- package/lib/schema-tsp.js +108 -59
- package/lib/schema-x509.js +36 -25
- package/lib/webcrypto.js +161 -26
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/schema-cmp.js
CHANGED
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @intro
|
|
12
12
|
* Certificate Management Protocol handling per RFC 9810 (which obsoletes
|
|
13
|
-
* RFC 4210 and RFC 9480). `parse` decodes a `PKIMessage`
|
|
13
|
+
* RFC 4210 and RFC 9480). `parse` decodes a `PKIMessage` -- the protected
|
|
14
14
|
* transport envelope CMP enrollment, revocation, confirmation, and support
|
|
15
|
-
* exchanges ride
|
|
15
|
+
* exchanges ride -- into its header (version, sender / recipient
|
|
16
16
|
* GeneralNames including the NULL-DN anonymous form, nonces, transaction
|
|
17
17
|
* id, free text, general info), its body, its protection bits, and its
|
|
18
18
|
* extra certificates.
|
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
* CRMF parser; response, revocation, confirmation, error, general-message,
|
|
23
23
|
* and polling arms decode structurally (an encrypted certificate's
|
|
24
24
|
* EnvelopedData decodes via the CMS parser); every other defined arm is
|
|
25
|
-
* recognized and surfaced raw
|
|
25
|
+
* recognized and surfaced raw -- `nested` messages are never auto-recursed.
|
|
26
26
|
* Protection is surfaced, not verified: the exact `headerBytes` and
|
|
27
27
|
* `bodyBytes` wire slices are exposed so an external verifier reconstructs
|
|
28
28
|
* the virtual ProtectedPart `SEQUENCE { header, body }` and checks the MAC
|
|
29
29
|
* or signature itself. DER-only, fail-closed.
|
|
30
30
|
*
|
|
31
31
|
* @card
|
|
32
|
-
* Parse DER / PEM RFC 9810 CMP PKIMessages
|
|
32
|
+
* Parse DER / PEM RFC 9810 CMP PKIMessages -- header, 27-arm body (requests
|
|
33
33
|
* via CRMF, encrypted certs via CMS, the rest structural or raw),
|
|
34
34
|
* protection inputs surfaced byte-exact for external verification,
|
|
35
35
|
* fail-closed.
|
|
@@ -55,21 +55,10 @@ var OID_PKI_ARCHIVE_OPTIONS = oid.byName("pkiArchiveOptions");
|
|
|
55
55
|
|
|
56
56
|
// ---- leaves -----------------------------------------------------------
|
|
57
57
|
|
|
58
|
-
// CMP times are GeneralizedTime only
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
return asn1.read.time(n);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
// A PKIFreeText element MUST be a UTF8String (§5.1.1).
|
|
67
|
-
var UTF8_TEXT = schema.decode(function (n, ctx) {
|
|
68
|
-
if (n.tagClass !== "universal" || n.tagNumber !== TAGS.UTF8_STRING) {
|
|
69
|
-
throw ctx.E("cmp/bad-freetext", "PKIFreeText elements must be UTF8String (RFC 9810 §5.1.1)");
|
|
70
|
-
}
|
|
71
|
-
return asn1.read.string(n);
|
|
72
|
-
});
|
|
58
|
+
// CMP times are GeneralizedTime only; PKIFreeText elements are UTF8String
|
|
59
|
+
// (RFC 9810 sec. 5.1.1). Both ride the shared pkix leaf factories.
|
|
60
|
+
var GENERALIZED_TIME = pkix.generalizedTime(NS, { code: "cmp/bad-time", message: "CMP times must be GeneralizedTime (RFC 9810 sec. 5.1.1)" });
|
|
61
|
+
var UTF8_TEXT = pkix.utf8Text(NS, { code: "cmp/bad-freetext", message: "PKIFreeText elements must be UTF8String (RFC 9810 sec. 5.1.1)" });
|
|
73
62
|
|
|
74
63
|
// PKIFreeText ::= SEQUENCE SIZE (1..MAX) OF UTF8String.
|
|
75
64
|
var PKI_FREE_TEXT = schema.seqOf(UTF8_TEXT, {
|
|
@@ -77,17 +66,17 @@ var PKI_FREE_TEXT = schema.seqOf(UTF8_TEXT, {
|
|
|
77
66
|
build: function (m) { return m.items.map(function (it) { return it.value; }); },
|
|
78
67
|
});
|
|
79
68
|
|
|
80
|
-
// PKIStatus ::= INTEGER {0..6} (
|
|
69
|
+
// PKIStatus ::= INTEGER {0..6} (sec. 5.2.3) -- a value whitelist, surfaced named.
|
|
81
70
|
var PKI_STATUS_NAMES = ["accepted", "grantedWithMods", "rejection", "waiting",
|
|
82
71
|
"revocationWarning", "revocationNotification", "keyUpdateWarning"];
|
|
83
72
|
var PKI_STATUS = schema.decode(function (n, ctx) {
|
|
84
73
|
var v = asn1.read.integer(n);
|
|
85
|
-
if (v < 0n || v > 6n) throw ctx.E("cmp/bad-status", "undefined PKIStatus " + v.toString() + " (RFC 9810
|
|
74
|
+
if (v < 0n || v > 6n) throw ctx.E("cmp/bad-status", "undefined PKIStatus " + v.toString() + " (RFC 9810 sec. 5.2.3)");
|
|
86
75
|
var code = Number(v);
|
|
87
76
|
return { code: code, name: PKI_STATUS_NAMES[code] };
|
|
88
77
|
});
|
|
89
78
|
|
|
90
|
-
// PKIFailureInfo ::= BIT STRING named bits 0..26 (
|
|
79
|
+
// PKIFailureInfo ::= BIT STRING named bits 0..26 (sec. 5.2.3). X.690 sec. 11.2.2:
|
|
91
80
|
// a named-bit BIT STRING drops trailing zero bits, so the final carried bit
|
|
92
81
|
// must be 1. Bits past 26 are extensible and surface as numeric indexes.
|
|
93
82
|
var FAIL_INFO_NAMES = ["badAlg", "badMessageCheck", "badRequest", "badTime", "badCertId",
|
|
@@ -107,31 +96,22 @@ var PKI_FAILURE_INFO = schema.decode(function (n, ctx) {
|
|
|
107
96
|
return { bits: bits, raw: bs };
|
|
108
97
|
});
|
|
109
98
|
|
|
110
|
-
// PollRep checkAfter is a delay in seconds
|
|
99
|
+
// PollRep checkAfter is a delay in seconds -- negative poisons scheduling, and
|
|
111
100
|
// the value surfaces as an exact number or not at all.
|
|
112
101
|
var CHECK_AFTER = schema.decode(function (n, ctx) {
|
|
113
102
|
var v = asn1.read.integer(n);
|
|
114
|
-
if (v < 0n || v > 2147483647n) throw ctx.E("cmp/bad-poll-rep", "checkAfter must be a non-negative delay in seconds (RFC 9810
|
|
103
|
+
if (v < 0n || v > 2147483647n) throw ctx.E("cmp/bad-poll-rep", "checkAfter must be a non-negative delay in seconds (RFC 9810 sec. 5.3.22)");
|
|
115
104
|
return Number(v);
|
|
116
105
|
});
|
|
117
106
|
|
|
118
|
-
// A raw certificate / CRL / publication-info element
|
|
107
|
+
// A raw certificate / CRL / publication-info element -- surfaced byte-exact for
|
|
119
108
|
// downstream parsing, but structurally a universal SEQUENCE: a CMPCertificate
|
|
120
109
|
// is a Certificate, and CertificateList / PKIPublicationInfo are SEQUENCEs, so
|
|
121
110
|
// a primitive (an INTEGER) or wrong-tag element is not a valid structure and
|
|
122
111
|
// rejects rather than handing arbitrary bytes to certificate processing. The
|
|
123
112
|
// `code` names the containing structure (a shared leaf cannot know its context).
|
|
124
113
|
function rawSequence(code) {
|
|
125
|
-
return
|
|
126
|
-
// A Certificate, CertificateList, and PKIPublicationInfo are each a
|
|
127
|
-
// NON-EMPTY universal SEQUENCE — an empty SEQUENCE (0x30 0x00) has the
|
|
128
|
-
// right tag but is none of them, so require at least one child rather than
|
|
129
|
-
// surface a degenerate structure as certificate/CRL bytes.
|
|
130
|
-
if (!(n.tagClass === "universal" && n.tagNumber === TAGS.SEQUENCE && n.children && n.children.length >= 1)) {
|
|
131
|
-
throw ctx.E(code, "expected a non-empty universal SEQUENCE (Certificate / CertificateList / PKIPublicationInfo)");
|
|
132
|
-
}
|
|
133
|
-
return n.bytes;
|
|
134
|
-
});
|
|
114
|
+
return pkix.rawNonEmptySequence(NS, { code: code, message: "expected a non-empty universal SEQUENCE (Certificate / CertificateList / PKIPublicationInfo)" });
|
|
135
115
|
}
|
|
136
116
|
|
|
137
117
|
var GENERAL_NAME = pkix.generalName(NS, { decodeValue: true });
|
|
@@ -141,7 +121,7 @@ var EXTENSIONS = pkix.extensions(NS);
|
|
|
141
121
|
// ---- header ------------------------------------------------------------
|
|
142
122
|
|
|
143
123
|
// InfoTypeAndValue ::= SEQUENCE { infoType OID, infoValue ANY OPTIONAL }
|
|
144
|
-
// (
|
|
124
|
+
// (sec. 5.1.1, sec. 5.3.19). Recognized id-it types with a fixed value syntax are
|
|
145
125
|
// validated (the CMS signed-attr value-syntax posture); all others surface
|
|
146
126
|
// raw with the registry name resolving.
|
|
147
127
|
var INFO_TYPE_AND_VALUE = schema.seq([
|
|
@@ -152,13 +132,13 @@ var INFO_TYPE_AND_VALUE = schema.seq([
|
|
|
152
132
|
build: function (m, ctx) {
|
|
153
133
|
var t = m.fields.infoType.value;
|
|
154
134
|
var valueNode = m.fields.infoValue.present ? m.fields.infoValue.value : null;
|
|
155
|
-
// A recognized id-it type with a FIXED value syntax (RFC 9810
|
|
156
|
-
// carry its infoValue
|
|
135
|
+
// A recognized id-it type with a FIXED value syntax (RFC 9810 sec. 5.1.1) MUST
|
|
136
|
+
// carry its infoValue -- implicitConfirm a NULL, confirmWaitTime a
|
|
157
137
|
// GeneralizedTime, certProfile a SEQUENCE OF UTF8String. A missing value is
|
|
158
138
|
// accepted only for the open / query id-it types (a bare OID in a genm body),
|
|
159
139
|
// never for a type whose value syntax the RFC defines.
|
|
160
140
|
if ((t === OID_IMPLICIT_CONFIRM || t === OID_CONFIRM_WAIT_TIME || t === OID_CERT_PROFILE) && valueNode === null) {
|
|
161
|
-
throw ctx.E("cmp/bad-info-value", "a recognized fixed-syntax id-it (implicitConfirm / confirmWaitTime / certProfile) must carry its infoValue (RFC 9810
|
|
141
|
+
throw ctx.E("cmp/bad-info-value", "a recognized fixed-syntax id-it (implicitConfirm / confirmWaitTime / certProfile) must carry its infoValue (RFC 9810 sec. 5.1.1)");
|
|
162
142
|
}
|
|
163
143
|
if (valueNode !== null) {
|
|
164
144
|
// A recognized id-it value is validated by CONTENT, not just tag: run the
|
|
@@ -167,30 +147,30 @@ var INFO_TYPE_AND_VALUE = schema.seq([
|
|
|
167
147
|
// rejects rather than being surfaced as valid CMP.
|
|
168
148
|
if (t === OID_IMPLICIT_CONFIRM) {
|
|
169
149
|
if (!(valueNode.tagClass === "universal" && valueNode.tagNumber === TAGS.NULL)) {
|
|
170
|
-
throw ctx.E("cmp/bad-info-value", "an implicitConfirm value must be NULL (RFC 9810
|
|
150
|
+
throw ctx.E("cmp/bad-info-value", "an implicitConfirm value must be NULL (RFC 9810 sec. 5.1.1.1)");
|
|
171
151
|
}
|
|
172
152
|
try { asn1.read.nullValue(valueNode); }
|
|
173
|
-
catch (e) { throw ctx.E("cmp/bad-info-value", "an implicitConfirm value must be an empty NULL (RFC 9810
|
|
153
|
+
catch (e) { throw ctx.E("cmp/bad-info-value", "an implicitConfirm value must be an empty NULL (RFC 9810 sec. 5.1.1.1)", e); }
|
|
174
154
|
}
|
|
175
155
|
if (t === OID_CONFIRM_WAIT_TIME) {
|
|
176
156
|
if (!(valueNode.tagClass === "universal" && valueNode.tagNumber === TAGS.GENERALIZED_TIME)) {
|
|
177
|
-
throw ctx.E("cmp/bad-info-value", "a confirmWaitTime value must be a GeneralizedTime (RFC 9810
|
|
157
|
+
throw ctx.E("cmp/bad-info-value", "a confirmWaitTime value must be a GeneralizedTime (RFC 9810 sec. 5.1.1.2)");
|
|
178
158
|
}
|
|
179
159
|
try { asn1.read.time(valueNode); }
|
|
180
|
-
catch (e) { throw ctx.E("cmp/bad-info-value", "a confirmWaitTime GeneralizedTime is malformed (RFC 9810
|
|
160
|
+
catch (e) { throw ctx.E("cmp/bad-info-value", "a confirmWaitTime GeneralizedTime is malformed (RFC 9810 sec. 5.1.1.2)", e); }
|
|
181
161
|
}
|
|
182
162
|
if (t === OID_CERT_PROFILE) {
|
|
183
163
|
if (!(valueNode.tagClass === "universal" && valueNode.tagNumber === TAGS.SEQUENCE &&
|
|
184
164
|
valueNode.children && valueNode.children.length >= 1)) {
|
|
185
|
-
throw ctx.E("cmp/bad-info-value", "a certProfile value must be a non-empty SEQUENCE OF UTF8String (RFC 9810
|
|
165
|
+
throw ctx.E("cmp/bad-info-value", "a certProfile value must be a non-empty SEQUENCE OF UTF8String (RFC 9810 sec. 5.1.1.4)");
|
|
186
166
|
}
|
|
187
167
|
for (var i = 0; i < valueNode.children.length; i++) {
|
|
188
168
|
var el = valueNode.children[i];
|
|
189
169
|
if (!(el.tagClass === "universal" && el.tagNumber === TAGS.UTF8_STRING)) {
|
|
190
|
-
throw ctx.E("cmp/bad-info-value", "a certProfile element must be a UTF8String (RFC 9810
|
|
170
|
+
throw ctx.E("cmp/bad-info-value", "a certProfile element must be a UTF8String (RFC 9810 sec. 5.1.1.4)");
|
|
191
171
|
}
|
|
192
172
|
try { asn1.read.string(el); }
|
|
193
|
-
catch (e) { throw ctx.E("cmp/bad-info-value", "a certProfile element must be a valid UTF8String (RFC 9810
|
|
173
|
+
catch (e) { throw ctx.E("cmp/bad-info-value", "a certProfile element must be a valid UTF8String (RFC 9810 sec. 5.1.1.4)", e); }
|
|
194
174
|
}
|
|
195
175
|
}
|
|
196
176
|
}
|
|
@@ -204,7 +184,7 @@ var GENERAL_INFO = schema.seqOf(INFO_TYPE_AND_VALUE, {
|
|
|
204
184
|
});
|
|
205
185
|
|
|
206
186
|
// PKIHeader ::= SEQUENCE { pvno, sender, recipient, then EXPLICIT [0]..[8]
|
|
207
|
-
// optionals, ascending, at-most-once } (
|
|
187
|
+
// optionals, ascending, at-most-once } (sec. 5.1.1).
|
|
208
188
|
var PKI_HEADER = schema.seq([
|
|
209
189
|
schema.field("pvno", pkix.versionReader(NS, { "1": 1, "2": 2, "3": 3 })),
|
|
210
190
|
schema.field("sender", GENERAL_NAME),
|
|
@@ -243,7 +223,7 @@ var PKI_HEADER = schema.seq([
|
|
|
243
223
|
|
|
244
224
|
// ---- decoded body arms ---------------------------------------------------
|
|
245
225
|
|
|
246
|
-
// PKIStatusInfo ::= SEQUENCE { status, statusString OPTIONAL, failInfo OPTIONAL } (
|
|
226
|
+
// PKIStatusInfo ::= SEQUENCE { status, statusString OPTIONAL, failInfo OPTIONAL } (sec. 5.2.3).
|
|
247
227
|
var PKI_STATUS_INFO = schema.seq([
|
|
248
228
|
schema.field("status", PKI_STATUS),
|
|
249
229
|
schema.optional("statusString", PKI_FREE_TEXT, { whenUniversal: [TAGS.SEQUENCE] }),
|
|
@@ -260,7 +240,7 @@ var PKI_STATUS_INFO = schema.seq([
|
|
|
260
240
|
});
|
|
261
241
|
|
|
262
242
|
// ErrorMsgContent ::= SEQUENCE { pKIStatusInfo, errorCode INTEGER OPTIONAL,
|
|
263
|
-
// errorDetails PKIFreeText OPTIONAL } (
|
|
243
|
+
// errorDetails PKIFreeText OPTIONAL } (sec. 5.3.21).
|
|
264
244
|
var ERROR_MSG_CONTENT = schema.seq([
|
|
265
245
|
schema.field("pKIStatusInfo", PKI_STATUS_INFO),
|
|
266
246
|
schema.optional("errorCode", schema.integerLeaf(), { whenUniversal: [TAGS.INTEGER] }),
|
|
@@ -277,7 +257,7 @@ var ERROR_MSG_CONTENT = schema.seq([
|
|
|
277
257
|
});
|
|
278
258
|
|
|
279
259
|
// CertStatus ::= SEQUENCE { certHash, certReqId, statusInfo OPTIONAL,
|
|
280
|
-
// hashAlg [0] OPTIONAL } (
|
|
260
|
+
// hashAlg [0] OPTIONAL } (sec. 5.3.18). certReqId accepts the -1 sentinel.
|
|
281
261
|
var CERT_STATUS = schema.seq([
|
|
282
262
|
schema.field("certHash", schema.octetString()),
|
|
283
263
|
schema.field("certReqId", schema.integerLeaf()),
|
|
@@ -297,22 +277,22 @@ var CERT_STATUS = schema.seq([
|
|
|
297
277
|
},
|
|
298
278
|
});
|
|
299
279
|
|
|
300
|
-
// CertConfirmContent ::= SEQUENCE OF CertStatus
|
|
301
|
-
// (reject-all,
|
|
280
|
+
// CertConfirmContent ::= SEQUENCE OF CertStatus -- an EMPTY sequence is legal
|
|
281
|
+
// (reject-all, sec. 5.3.18), so no SIZE floor.
|
|
302
282
|
var CERT_CONFIRM_CONTENT = schema.seqOf(CERT_STATUS, {
|
|
303
283
|
code: "cmp/bad-cert-status", what: "CertConfirmContent",
|
|
304
284
|
build: function (m) { return m.items.map(function (it) { return it.value.result; }); },
|
|
305
285
|
});
|
|
306
286
|
|
|
307
|
-
// GenMsgContent / GenRepContent ::= SEQUENCE OF InfoTypeAndValue
|
|
308
|
-
// bound, empty legal (
|
|
287
|
+
// GenMsgContent / GenRepContent ::= SEQUENCE OF InfoTypeAndValue -- no SIZE
|
|
288
|
+
// bound, empty legal (sec. 5.3.19/.20).
|
|
309
289
|
var GEN_MSG_CONTENT = schema.seqOf(INFO_TYPE_AND_VALUE, {
|
|
310
290
|
code: "cmp/bad-info-type-and-value", what: "GenMsgContent",
|
|
311
291
|
build: function (m) { return m.items.map(function (it) { return it.value.result; }); },
|
|
312
292
|
});
|
|
313
293
|
|
|
314
294
|
// RevDetails ::= SEQUENCE { certDetails CertTemplate, crlEntryDetails
|
|
315
|
-
// Extensions OPTIONAL } (
|
|
295
|
+
// Extensions OPTIONAL } (sec. 5.3.9) -- the CertTemplate interior (an IMPLICIT
|
|
316
296
|
// TAGS module) is owned by the CRMF parser and walked NS-bound there.
|
|
317
297
|
var CERT_TEMPLATE_LEAF = schema.decode(function (n) { return crmf.walkCertTemplate(n); });
|
|
318
298
|
var REV_DETAILS = schema.seq([
|
|
@@ -344,7 +324,7 @@ var CERT_ID = schema.seq([
|
|
|
344
324
|
});
|
|
345
325
|
|
|
346
326
|
// RevRepContent ::= SEQUENCE { status SIZE(1..MAX), revCerts [0] OPTIONAL,
|
|
347
|
-
// crls [1] OPTIONAL } (
|
|
327
|
+
// crls [1] OPTIONAL } (sec. 5.3.10).
|
|
348
328
|
var REV_REP_CONTENT = schema.seq([
|
|
349
329
|
schema.field("status", schema.seqOf(PKI_STATUS_INFO, {
|
|
350
330
|
min: 1, code: "cmp/bad-rev-rep", what: "RevRep status",
|
|
@@ -372,49 +352,59 @@ var REV_REP_CONTENT = schema.seq([
|
|
|
372
352
|
});
|
|
373
353
|
|
|
374
354
|
// EncryptedKey ::= CHOICE { encryptedValue EncryptedValue (deprecated),
|
|
375
|
-
// envelopedData [0] EnvelopedData } (
|
|
355
|
+
// envelopedData [0] EnvelopedData } (sec. 5.2.2). EncryptedKey is imported from
|
|
376
356
|
// the RFC 4211 CRMF module, which is IMPLICIT TAGS, so `envelopedData [0]` is
|
|
377
|
-
// an IMPLICIT context tag REPLACING the EnvelopedData SEQUENCE tag
|
|
357
|
+
// an IMPLICIT context tag REPLACING the EnvelopedData SEQUENCE tag -- the [0]
|
|
378
358
|
// node's children ARE the EnvelopedData fields, no inner wrapper. Retag it to
|
|
379
359
|
// the universal SEQUENCE the CMS walker expects, exactly as the sibling CRMF
|
|
380
360
|
// `encryptedKey [4] EnvelopedData` POP arm does (schema-crmf.js). The
|
|
381
|
-
// deprecated encryptedValue arm surfaces RAW
|
|
361
|
+
// deprecated encryptedValue arm surfaces RAW -- never field-walked, so nothing
|
|
382
362
|
// dereferences its optional interior (absent algorithm parameters crash
|
|
383
363
|
// consumers that walk it blindly).
|
|
384
364
|
var ENCRYPTED_KEY = schema.choice([
|
|
385
365
|
{ when: { tagClass: "universal", tagNumber: TAGS.SEQUENCE },
|
|
386
|
-
schema: schema.decode(function (n) {
|
|
366
|
+
schema: schema.decode(function (n, ctx) {
|
|
367
|
+
// An RFC 4211 sec. 2 EncryptedValue REQUIRES encValue (its final BIT STRING
|
|
368
|
+
// field), so an EMPTY SEQUENCE has the right tag but is not any valid
|
|
369
|
+
// EncryptedValue -- require at least one child rather than surface a
|
|
370
|
+
// degenerate structure as encrypted certificate/key bytes (the same
|
|
371
|
+
// non-empty rule every raw-SEQUENCE surface in this module applies).
|
|
372
|
+
if (!(n.children && n.children.length >= 1)) {
|
|
373
|
+
throw ctx.E("cmp/bad-cert-response", "an EncryptedValue must be a non-empty SEQUENCE (RFC 4211 sec. 2)");
|
|
374
|
+
}
|
|
375
|
+
return { encryptedValue: n.bytes };
|
|
376
|
+
}) },
|
|
387
377
|
{ when: { tagClass: "context", tagNumber: 0 },
|
|
388
378
|
schema: schema.decode(function (n, ctx) {
|
|
389
379
|
var env;
|
|
390
|
-
// Wrap every EnvelopedData failure
|
|
391
|
-
// from the composed walker
|
|
380
|
+
// Wrap every EnvelopedData failure -- a non-decodable retarget OR a CmsError
|
|
381
|
+
// from the composed walker -- as the CMP domain error (with the original as
|
|
392
382
|
// the cause), so cmp.parse never escapes with a cms/* error for a malformed
|
|
393
383
|
// PKIMessage (the same wrapping the sibling CRMF encryptedKey POP arm does).
|
|
394
384
|
try { env = cms.walkEnvelopedData(asn1.decode(asn1.sequenceTlv(n))); }
|
|
395
385
|
catch (e) {
|
|
396
|
-
throw ctx.E("cmp/bad-cert-response", "envelopedData [0] must be a well-formed IMPLICIT EnvelopedData (RFC 9810
|
|
386
|
+
throw ctx.E("cmp/bad-cert-response", "envelopedData [0] must be a well-formed IMPLICIT EnvelopedData (RFC 9810 sec. 5.2.2)", e);
|
|
397
387
|
}
|
|
398
|
-
// RFC 9810
|
|
388
|
+
// RFC 9810 sec. 5.2.2 -- a CMP EncryptedKey EnvelopedData is addressed to
|
|
399
389
|
// exactly one recipient (the end entity for a delivered cert / key). CMS
|
|
400
390
|
// permits RecipientInfos SET SIZE 1..MAX, so the single-recipient bound is
|
|
401
391
|
// CMP's: more than one recipient is a malformed EncryptedKey.
|
|
402
392
|
if (env.recipientInfos.length !== 1) {
|
|
403
|
-
throw ctx.E("cmp/bad-cert-response", "an EncryptedKey EnvelopedData must contain exactly one RecipientInfo (RFC 9810
|
|
393
|
+
throw ctx.E("cmp/bad-cert-response", "an EncryptedKey EnvelopedData must contain exactly one RecipientInfo (RFC 9810 sec. 5.2.2)");
|
|
404
394
|
}
|
|
405
395
|
// The ciphertext IS the encrypted certificate / private key here, so an
|
|
406
|
-
// EnvelopedData with no ciphertext to decrypt
|
|
407
|
-
// permits it) OR present-but-empty
|
|
396
|
+
// EnvelopedData with no ciphertext to decrypt -- absent (detached, CMS
|
|
397
|
+
// permits it) OR present-but-empty -- must reject (RFC 9810 sec. 5.2.2/sec. 5.3.4;
|
|
408
398
|
// the same rule the CRMF encryptedKey POP arm enforces).
|
|
409
399
|
var ct = env.encryptedContentInfo.encryptedContent;
|
|
410
400
|
if (ct === null || ct.length === 0) {
|
|
411
|
-
throw ctx.E("cmp/bad-cert-response", "an EncryptedKey envelopedData must carry non-empty attached ciphertext
|
|
401
|
+
throw ctx.E("cmp/bad-cert-response", "an EncryptedKey envelopedData must carry non-empty attached ciphertext -- its encryptedContent is the certificate/key payload (RFC 9810 sec. 5.2.2)");
|
|
412
402
|
}
|
|
413
403
|
return { envelopedData: env };
|
|
414
404
|
}) },
|
|
415
405
|
], { code: "cmp/bad-cert-response", what: "EncryptedKey" });
|
|
416
406
|
|
|
417
|
-
// CertOrEncCert ::= CHOICE { certificate [0], encryptedCert [1] } (
|
|
407
|
+
// CertOrEncCert ::= CHOICE { certificate [0], encryptedCert [1] } (sec. 5.3.4).
|
|
418
408
|
var CERT_OR_ENC_CERT = schema.choice([
|
|
419
409
|
{ when: { tagClass: "context", tagNumber: 0 },
|
|
420
410
|
schema: schema.explicit(0, schema.decode(function (n, ctx) {
|
|
@@ -428,7 +418,7 @@ var CERT_OR_ENC_CERT = schema.choice([
|
|
|
428
418
|
], { code: "cmp/bad-cert-response", what: "CertOrEncCert" });
|
|
429
419
|
|
|
430
420
|
// CertifiedKeyPair ::= SEQUENCE { certOrEncCert, privateKey [0] OPTIONAL,
|
|
431
|
-
// publicationInfo [1] OPTIONAL } (
|
|
421
|
+
// publicationInfo [1] OPTIONAL } (sec. 5.3.4).
|
|
432
422
|
var CERTIFIED_KEY_PAIR = schema.seq([
|
|
433
423
|
schema.field("certOrEncCert", CERT_OR_ENC_CERT),
|
|
434
424
|
schema.trailing([
|
|
@@ -448,7 +438,7 @@ var CERTIFIED_KEY_PAIR = schema.seq([
|
|
|
448
438
|
});
|
|
449
439
|
|
|
450
440
|
// CertResponse ::= SEQUENCE { certReqId INTEGER (the -1 sentinel is legal),
|
|
451
|
-
// status PKIStatusInfo, certifiedKeyPair OPTIONAL, rspInfo OPTIONAL } (
|
|
441
|
+
// status PKIStatusInfo, certifiedKeyPair OPTIONAL, rspInfo OPTIONAL } (sec. 5.3.4).
|
|
452
442
|
var CERT_RESPONSE = schema.seq([
|
|
453
443
|
schema.field("certReqId", schema.integerLeaf()),
|
|
454
444
|
schema.field("status", PKI_STATUS_INFO),
|
|
@@ -459,24 +449,24 @@ var CERT_RESPONSE = schema.seq([
|
|
|
459
449
|
build: function (m, ctx) {
|
|
460
450
|
var status = m.fields.status.value.result;
|
|
461
451
|
var certifiedKeyPair = m.fields.certifiedKeyPair.present ? m.fields.certifiedKeyPair.value.result : null;
|
|
462
|
-
// RFC 9810
|
|
452
|
+
// RFC 9810 sec. 5.3.4 -- only one of the failInfo (in PKIStatusInfo) and
|
|
463
453
|
// certifiedKeyPair fields can be present in a CertResponse: a rejection
|
|
464
454
|
// carries no certificate. Accepting both lets a caller keying off
|
|
465
455
|
// certifiedKeyPair process a certificate from a failed response.
|
|
466
456
|
if (status.failInfo !== null && certifiedKeyPair !== null) {
|
|
467
457
|
throw ctx.E("cmp/bad-cert-response",
|
|
468
|
-
"a CertResponse must not carry both failInfo and certifiedKeyPair (RFC 9810
|
|
458
|
+
"a CertResponse must not carry both failInfo and certifiedKeyPair (RFC 9810 sec. 5.3.4)");
|
|
469
459
|
}
|
|
470
460
|
// A certifiedKeyPair is present ONLY when the status grants the certificate:
|
|
471
|
-
// accepted (0) or grantedWithMods (1). Any other status
|
|
472
|
-
// the revocation / keyUpdate warnings
|
|
461
|
+
// accepted (0) or grantedWithMods (1). Any other status -- rejection, waiting,
|
|
462
|
+
// the revocation / keyUpdate warnings -- denies or defers the request, so a
|
|
473
463
|
// certificate under it is a malformed response even when no explicit failInfo
|
|
474
464
|
// bit is set (a rejection is commonly signalled by status alone). Keying the
|
|
475
465
|
// rule off failInfo presence alone would let a bare-rejection status ship a
|
|
476
|
-
// certificate (RFC 9810
|
|
466
|
+
// certificate (RFC 9810 sec. 5.3.4).
|
|
477
467
|
if (certifiedKeyPair !== null && status.status.code !== 0 && status.status.code !== 1) {
|
|
478
468
|
throw ctx.E("cmp/bad-cert-response",
|
|
479
|
-
"a CertResponse certifiedKeyPair is allowed only under a granting status (accepted or grantedWithMods) (RFC 9810
|
|
469
|
+
"a CertResponse certifiedKeyPair is allowed only under a granting status (accepted or grantedWithMods) (RFC 9810 sec. 5.3.4)");
|
|
480
470
|
}
|
|
481
471
|
return {
|
|
482
472
|
certReqId: m.fields.certReqId.value,
|
|
@@ -488,7 +478,7 @@ var CERT_RESPONSE = schema.seq([
|
|
|
488
478
|
});
|
|
489
479
|
|
|
490
480
|
// CertRepMessage ::= SEQUENCE { caPubs [1] OPTIONAL, response SEQUENCE OF
|
|
491
|
-
// CertResponse } (
|
|
481
|
+
// CertResponse } (sec. 5.3.4) -- the optional precedes the required field (the
|
|
492
482
|
// x509-version shape). caPubs surface raw: the parser confers NO trust.
|
|
493
483
|
var CERT_REP_MESSAGE = schema.seq([
|
|
494
484
|
schema.optional("caPubs", schema.seqOf(rawSequence("cmp/bad-cert-rep"), {
|
|
@@ -511,9 +501,9 @@ var CERT_REP_MESSAGE = schema.seq([
|
|
|
511
501
|
|
|
512
502
|
// KeyRecRepContent ::= SEQUENCE { status PKIStatusInfo, newSigCert [0]
|
|
513
503
|
// Certificate OPTIONAL, caCerts [1] SEQUENCE OF Certificate OPTIONAL,
|
|
514
|
-
// keyPairHist [2] SEQUENCE OF CertifiedKeyPair OPTIONAL } (
|
|
504
|
+
// keyPairHist [2] SEQUENCE OF CertifiedKeyPair OPTIONAL } (sec. 5.3.8). The
|
|
515
505
|
// keyPairHist CertifiedKeyPairs can carry the same EnvelopedData form as the
|
|
516
|
-
// enrollment responses, so krp is decoded (not surfaced raw)
|
|
506
|
+
// enrollment responses, so krp is decoded (not surfaced raw) -- the operator sees
|
|
517
507
|
// the status / key history and the cross-field EnvelopedData version check runs.
|
|
518
508
|
var KEY_REC_REP_CONTENT = schema.seq([
|
|
519
509
|
schema.field("status", PKI_STATUS_INFO),
|
|
@@ -540,7 +530,7 @@ var KEY_REC_REP_CONTENT = schema.seq([
|
|
|
540
530
|
},
|
|
541
531
|
});
|
|
542
532
|
|
|
543
|
-
// PollReqContent / PollRepContent (
|
|
533
|
+
// PollReqContent / PollRepContent (sec. 5.3.22) -- certReqId may be -1.
|
|
544
534
|
var POLL_REQ_ENTRY = schema.seq([
|
|
545
535
|
schema.field("certReqId", schema.integerLeaf()),
|
|
546
536
|
], {
|
|
@@ -572,12 +562,12 @@ var POLL_REP_CONTENT = schema.seqOf(POLL_REP_ENTRY, {
|
|
|
572
562
|
|
|
573
563
|
// ---- the 27-arm body dispatch ----------------------------------------------
|
|
574
564
|
|
|
575
|
-
// Tag -> arm name, verbatim from the PKIXCMP-2023 module (
|
|
565
|
+
// Tag -> arm name, verbatim from the PKIXCMP-2023 module (sec. 5.1.2). Every
|
|
576
566
|
// defined arm is at minimum recognized; a subset decodes structurally.
|
|
577
567
|
var BODY_ARMS = ["ir", "ip", "cr", "cp", "p10cr", "popdecc", "popdecr", "kur", "kup",
|
|
578
568
|
"krr", "krp", "rr", "rp", "ccr", "ccp", "ckuann", "cann", "rann", "crlann",
|
|
579
569
|
"pkiconf", "nested", "genm", "genp", "error", "certConf", "pollReq", "pollRep"];
|
|
580
|
-
// Arms carrying RFC 4211 CertReqMessages
|
|
570
|
+
// Arms carrying RFC 4211 CertReqMessages -- walked by the CRMF parser.
|
|
581
571
|
var CRMF_ARMS = { 0: true, 2: true, 7: true, 9: true, 13: true };
|
|
582
572
|
// Arms with a structural schema here.
|
|
583
573
|
var DECODED_ARMS = {
|
|
@@ -590,29 +580,30 @@ var DECODED_ARMS = {
|
|
|
590
580
|
};
|
|
591
581
|
|
|
592
582
|
// The open-CHOICE dispatch: tag -> arm name; walk where a schema exists;
|
|
593
|
-
// surface every other defined arm raw as { arm, tag, bytes }
|
|
583
|
+
// surface every other defined arm raw as { arm, tag, bytes } -- `decoded` is
|
|
594
584
|
// ABSENT (not null) on raw arms, so its presence discriminates decoded-empty
|
|
595
585
|
// (pkiconf decodes to null) from recognized-undecoded. `nested` [20] is never
|
|
596
586
|
// auto-recursed: a self-nesting tower amplifies per-level walk products, so
|
|
597
587
|
// the operator re-feeds its bytes to parse explicitly.
|
|
598
588
|
var BODY = schema.decode(function (n, ctx) {
|
|
599
589
|
if (n.tagClass !== "context" || !n.children) {
|
|
600
|
-
throw ctx.E("cmp/bad-body", "PKIBody must be a constructed context-tagged CHOICE arm (RFC 9810
|
|
590
|
+
throw ctx.E("cmp/bad-body", "PKIBody must be a constructed context-tagged CHOICE arm (RFC 9810 sec. 5.1.2)");
|
|
601
591
|
}
|
|
602
592
|
if (n.tagNumber < 0 || n.tagNumber > 26) {
|
|
603
|
-
throw ctx.E("cmp/bad-body", "PKIBody tag [" + n.tagNumber + "] is not a defined arm (RFC 9810
|
|
593
|
+
throw ctx.E("cmp/bad-body", "PKIBody tag [" + n.tagNumber + "] is not a defined arm (RFC 9810 sec. 5.1.2)");
|
|
604
594
|
}
|
|
605
595
|
if (n.children.length !== 1) {
|
|
606
|
-
throw ctx.E("cmp/bad-body", "a PKIBody arm wraps exactly one value (X.690
|
|
596
|
+
throw ctx.E("cmp/bad-body", "a PKIBody arm wraps exactly one value (X.690 sec. 8.14)");
|
|
607
597
|
}
|
|
608
598
|
var inner = n.children[0];
|
|
609
599
|
var out = { arm: BODY_ARMS[n.tagNumber], tag: n.tagNumber, bytes: inner.bytes };
|
|
610
600
|
if (CRMF_ARMS[n.tagNumber]) {
|
|
611
601
|
// ccr [13] cross-certification: RFC 9810 Appendix D.6 profiles its template
|
|
612
|
-
// with signingAlg present and
|
|
602
|
+
// with signingAlg present and version "v1 or v3" (both overriding RFC 4211
|
|
603
|
+
// sec. 5 for this one arm), and allows exactly one CertReqMsg (multiple
|
|
613
604
|
// cross-certificates go in separate PKIMessages).
|
|
614
605
|
var isCcr = n.tagNumber === 13;
|
|
615
|
-
out.decoded = crmf.walkCertReqMessages(inner, { allowSigningAlg: isCcr });
|
|
606
|
+
out.decoded = crmf.walkCertReqMessages(inner, { allowSigningAlg: isCcr, allowV1Version: isCcr });
|
|
616
607
|
if (isCcr && out.decoded.messages.length !== 1) {
|
|
617
608
|
throw ctx.E("cmp/bad-body", "a cross-certification request (ccr) must contain exactly one CertReqMsg (RFC 9810 Appendix D.6)");
|
|
618
609
|
}
|
|
@@ -620,14 +611,14 @@ var BODY = schema.decode(function (n, ctx) {
|
|
|
620
611
|
var match = schema.walk(DECODED_ARMS[n.tagNumber], inner, ctx);
|
|
621
612
|
out.decoded = match.result;
|
|
622
613
|
} else if (n.tagNumber === 19) {
|
|
623
|
-
// PKIConfirmContent ::= NULL (
|
|
614
|
+
// PKIConfirmContent ::= NULL (sec. 5.3.16).
|
|
624
615
|
if (!(inner.tagClass === "universal" && inner.tagNumber === TAGS.NULL)) {
|
|
625
|
-
throw ctx.E("cmp/bad-pkiconf", "PKIConfirmContent must be NULL (RFC 9810
|
|
616
|
+
throw ctx.E("cmp/bad-pkiconf", "PKIConfirmContent must be NULL (RFC 9810 sec. 5.3.16)");
|
|
626
617
|
}
|
|
627
|
-
// A NULL is well-formed only with empty content (X.690
|
|
618
|
+
// A NULL is well-formed only with empty content (X.690 sec. 8.8.2); the tag check
|
|
628
619
|
// alone would surface a non-empty NULL as a valid confirmation.
|
|
629
620
|
try { asn1.read.nullValue(inner); }
|
|
630
|
-
catch (e) { throw ctx.E("cmp/bad-pkiconf", "PKIConfirmContent must be an empty NULL (RFC 9810
|
|
621
|
+
catch (e) { throw ctx.E("cmp/bad-pkiconf", "PKIConfirmContent must be an empty NULL (RFC 9810 sec. 5.3.16)", e); }
|
|
631
622
|
out.decoded = null;
|
|
632
623
|
}
|
|
633
624
|
return out;
|
|
@@ -635,20 +626,26 @@ var BODY = schema.decode(function (n, ctx) {
|
|
|
635
626
|
|
|
636
627
|
// ---- the message envelope -----------------------------------------------------
|
|
637
628
|
|
|
638
|
-
// PKIProtection ::= BIT STRING
|
|
629
|
+
// PKIProtection ::= BIT STRING -- raw bits for the external verifier. Every
|
|
630
|
+
// RFC 9481 MSG_SIG_ALG / MSG_MAC_ALG output is an octet string, so the BIT
|
|
631
|
+
// STRING must be octet-aligned; with no in-tree CMP verify layer this is
|
|
632
|
+
// enforced at parse (the posture the OCSP signature shares), fail-closed.
|
|
639
633
|
var PROTECTION = schema.decode(function (n, ctx) {
|
|
640
634
|
var bs;
|
|
641
635
|
try { bs = asn1.read.bitString(n); }
|
|
642
|
-
catch (e) { throw ctx.E("cmp/bad-protection", "PKIProtection must be a BIT STRING (RFC 9810
|
|
636
|
+
catch (e) { throw ctx.E("cmp/bad-protection", "PKIProtection must be a BIT STRING (RFC 9810 sec. 5.1.3)", e); }
|
|
637
|
+
if (bs.unusedBits !== 0) {
|
|
638
|
+
throw ctx.E("cmp/bad-protection", "PKIProtection must be octet-aligned (0 unused bits) -- every RFC 9481 protection algorithm emits whole octets");
|
|
639
|
+
}
|
|
643
640
|
return { unusedBits: bs.unusedBits, bytes: bs.bytes };
|
|
644
641
|
});
|
|
645
642
|
|
|
646
643
|
// PKIMessage ::= SEQUENCE { header, body, protection [0] OPTIONAL,
|
|
647
|
-
// extraCerts [1] OPTIONAL } (
|
|
648
|
-
// span structures: protection presence <=> protectionAlg presence (
|
|
644
|
+
// extraCerts [1] OPTIONAL } (sec. 5.1). The build runs the two cross-checks that
|
|
645
|
+
// span structures: protection presence <=> protectionAlg presence (sec. 5.1.1,
|
|
649
646
|
// both directions), and a certConf hashAlg requires pvno cmp2021(3)
|
|
650
|
-
// (
|
|
651
|
-
// computed over (as DER-SEQUENCE(headerBytes || bodyBytes))
|
|
647
|
+
// (sec. 5.3.18). headerBytes / bodyBytes are the exact wire slices protection is
|
|
648
|
+
// computed over (as DER-SEQUENCE(headerBytes || bodyBytes)) -- surfaced raw,
|
|
652
649
|
// never re-encoded.
|
|
653
650
|
var PKI_MESSAGE = schema.seq([
|
|
654
651
|
schema.field("header", PKI_HEADER),
|
|
@@ -669,16 +666,16 @@ var PKI_MESSAGE = schema.seq([
|
|
|
669
666
|
var extraCerts = m.fields.extraCerts.present ? m.fields.extraCerts.value.result : null;
|
|
670
667
|
if ((protection !== null) !== (header.protectionAlg !== null)) {
|
|
671
668
|
throw ctx.E("cmp/protection-alg-mismatch",
|
|
672
|
-
"protection bits and the header protectionAlg must be present together or absent together (RFC 9810
|
|
669
|
+
"protection bits and the header protectionAlg must be present together or absent together (RFC 9810 sec. 5.1.1)");
|
|
673
670
|
}
|
|
674
671
|
if (body.arm === "certConf") {
|
|
675
672
|
for (var i = 0; i < body.decoded.length; i++) {
|
|
676
673
|
if (body.decoded[i].hashAlg !== null && header.pvno !== 3) {
|
|
677
|
-
throw ctx.E("cmp/bad-cert-status", "a certConf hashAlg requires CMP version cmp2021(3) (RFC 9810
|
|
674
|
+
throw ctx.E("cmp/bad-cert-status", "a certConf hashAlg requires CMP version cmp2021(3) (RFC 9810 sec. 5.3.18)");
|
|
678
675
|
}
|
|
679
676
|
}
|
|
680
677
|
}
|
|
681
|
-
// RFC 9810
|
|
678
|
+
// RFC 9810 sec. 5.2.2 / sec. 7 -- EnvelopedData is cmp2021(3) syntax: a response
|
|
682
679
|
// carrying it (an encryptedCert or a privateKey in the envelopedData form)
|
|
683
680
|
// under pvno < 3 is a version mismatch, the same version gate the certConf
|
|
684
681
|
// hashAlg rule applies. The deprecated EncryptedValue form is the pre-2021
|
|
@@ -698,17 +695,17 @@ var PKI_MESSAGE = schema.seq([
|
|
|
698
695
|
(ckps[ck].encryptedCert && ckps[ck].encryptedCert.envelopedData !== undefined) ||
|
|
699
696
|
(ckps[ck].privateKey && ckps[ck].privateKey.envelopedData !== undefined);
|
|
700
697
|
if (usesEnveloped && header.pvno !== 3) {
|
|
701
|
-
throw ctx.E("cmp/bad-version", "a response carrying EnvelopedData (encryptedCert or privateKey) requires CMP version cmp2021(3) (RFC 9810
|
|
698
|
+
throw ctx.E("cmp/bad-version", "a response carrying EnvelopedData (encryptedCert or privateKey) requires CMP version cmp2021(3) (RFC 9810 sec. 5.2.2, sec. 7)");
|
|
702
699
|
}
|
|
703
700
|
}
|
|
704
|
-
// RFC 9810
|
|
701
|
+
// RFC 9810 sec. 5.3.12 -- a cross-certification response (ccp) reuses the
|
|
705
702
|
// CertRepMessage syntax "with the restriction that no encrypted private key
|
|
706
703
|
// can be sent": cross-certification certifies an existing CA's public key,
|
|
707
704
|
// so there is no key generation and CertifiedKeyPair.privateKey has no
|
|
708
705
|
// meaning. Reject a ccp that carries one rather than surface the key
|
|
709
706
|
// material; the field stays legal in the enrollment responses (ip/cp/kup).
|
|
710
707
|
if (body.arm === "ccp" && body.decoded && body.decoded.response) {
|
|
711
|
-
// RFC 9810 Appendix D.6
|
|
708
|
+
// RFC 9810 Appendix D.6 -- a one-way cross-certification response carries
|
|
712
709
|
// exactly one CertResponse (multiple cross-certificates go in separate
|
|
713
710
|
// PKIMessages), the mirror of the ccr one-CertReqMsg rule.
|
|
714
711
|
if (body.decoded.response.length !== 1) {
|
|
@@ -717,14 +714,14 @@ var PKI_MESSAGE = schema.seq([
|
|
|
717
714
|
for (var c = 0; c < body.decoded.response.length; c++) {
|
|
718
715
|
var ckpCcp = body.decoded.response[c].certifiedKeyPair;
|
|
719
716
|
if (ckpCcp && ckpCcp.privateKey !== null) {
|
|
720
|
-
throw ctx.E("cmp/bad-cert-response", "a cross-certification response (ccp) must not carry an encrypted private key (RFC 9810
|
|
717
|
+
throw ctx.E("cmp/bad-cert-response", "a cross-certification response (ccp) must not carry an encrypted private key (RFC 9810 sec. 5.3.12)");
|
|
721
718
|
}
|
|
722
719
|
}
|
|
723
720
|
}
|
|
724
|
-
// RFC 9810
|
|
721
|
+
// RFC 9810 sec. 5.3.11 -- a cross-certification request (ccr) MUST NOT send the
|
|
725
722
|
// private key to the responding CA (the requesting CA generates and holds
|
|
726
|
-
// it). The private-key-carrying POP choices
|
|
727
|
-
// deprecated thisMessage (cmp2000)
|
|
723
|
+
// it). The private-key-carrying POP choices -- encryptedKey (cmp2021) and the
|
|
724
|
+
// deprecated thisMessage (cmp2000) -- are therefore forbidden in a ccr; the
|
|
728
725
|
// MAC (dhMAC / agreeMAC) and indirect (subsequentMessage) methods carry no
|
|
729
726
|
// key. Checked before the version gate so a ccr always gets the ccr verdict.
|
|
730
727
|
if (body.arm === "ccr" && body.decoded && body.decoded.messages) {
|
|
@@ -734,10 +731,10 @@ var PKI_MESSAGE = schema.seq([
|
|
|
734
731
|
// the private key.
|
|
735
732
|
var ccrPopo = ccrMsg.popo;
|
|
736
733
|
if (ccrPopo && (ccrPopo.method === "encryptedKey" || ccrPopo.method === "thisMessage")) {
|
|
737
|
-
throw ctx.E("cmp/bad-body", "a cross-certification request (ccr) must not send the private key
|
|
734
|
+
throw ctx.E("cmp/bad-body", "a cross-certification request (ccr) must not send the private key -- the " + ccrPopo.method + " POP choice is forbidden (RFC 9810 sec. 5.3.11)");
|
|
738
735
|
}
|
|
739
736
|
// Via a control: a pkiArchiveOptions control with the encryptedPrivKey [0]
|
|
740
|
-
// arm is the other private-key transport (
|
|
737
|
+
// arm is the other private-key transport (sec. 5.2.8.3.1), likewise forbidden
|
|
741
738
|
// in a ccr. The control value is the raw PKIArchiveOptions CHOICE; the
|
|
742
739
|
// encryptedPrivKey arm is context tag [0].
|
|
743
740
|
var controls = ccrMsg.certReq.controls;
|
|
@@ -746,15 +743,15 @@ var PKI_MESSAGE = schema.seq([
|
|
|
746
743
|
if (controls[cn].type !== OID_PKI_ARCHIVE_OPTIONS) continue;
|
|
747
744
|
var arch;
|
|
748
745
|
try { arch = asn1.decode(controls[cn].value); }
|
|
749
|
-
catch (e) { throw ctx.E("cmp/bad-body", "a ccr pkiArchiveOptions control did not decode (RFC 9810
|
|
746
|
+
catch (e) { throw ctx.E("cmp/bad-body", "a ccr pkiArchiveOptions control did not decode (RFC 9810 sec. 5.3.11)", e); }
|
|
750
747
|
if (arch.tagClass === "context" && arch.tagNumber === 0) {
|
|
751
|
-
throw ctx.E("cmp/bad-body", "a cross-certification request (ccr) must not send the private key
|
|
748
|
+
throw ctx.E("cmp/bad-body", "a cross-certification request (ccr) must not send the private key -- a pkiArchiveOptions encryptedPrivKey control is forbidden (RFC 9810 sec. 5.3.11, sec. 5.2.8.3.1)");
|
|
752
749
|
}
|
|
753
750
|
}
|
|
754
751
|
}
|
|
755
752
|
}
|
|
756
753
|
}
|
|
757
|
-
// RFC 9810
|
|
754
|
+
// RFC 9810 sec. 5.2.8.3 -- the agreeMAC and encryptedKey POPOPrivKey choices are
|
|
758
755
|
// cmp2021(3) syntax; a request (ir/cr/kur/krr/ccr) whose proof-of-possession
|
|
759
756
|
// uses either under pvno < 3 is a version mismatch, the same gate the
|
|
760
757
|
// response EnvelopedData rule applies. The CRMF walker surfaces the chosen
|
|
@@ -763,9 +760,9 @@ var PKI_MESSAGE = schema.seq([
|
|
|
763
760
|
for (var q = 0; q < body.decoded.messages.length; q++) {
|
|
764
761
|
var popo = body.decoded.messages[q].popo;
|
|
765
762
|
if (popo && (popo.method === "agreeMAC" || popo.method === "encryptedKey") && header.pvno !== 3) {
|
|
766
|
-
throw ctx.E("cmp/bad-version", "a request POP using the agreeMAC or encryptedKey choice requires CMP version cmp2021(3) (RFC 9810
|
|
763
|
+
throw ctx.E("cmp/bad-version", "a request POP using the agreeMAC or encryptedKey choice requires CMP version cmp2021(3) (RFC 9810 sec. 5.2.8.3)");
|
|
767
764
|
}
|
|
768
|
-
//
|
|
765
|
+
// sec. 5.2.8.3.1/sec. 7 -- the same key transport via a pkiArchiveOptions control
|
|
769
766
|
// whose encryptedPrivKey [0] EXPLICIT wraps the envelopedData-form
|
|
770
767
|
// EncryptedKey is cmp2021 syntax too (a ccr rejects the control outright
|
|
771
768
|
// above; the other request arms are version-gated here).
|
|
@@ -776,13 +773,13 @@ var PKI_MESSAGE = schema.seq([
|
|
|
776
773
|
var opt = asn1.decode(archCtrls[ac].value);
|
|
777
774
|
if (opt.tagClass === "context" && opt.tagNumber === 0 && opt.children && opt.children.length >= 1 &&
|
|
778
775
|
opt.children[0].tagClass === "context" && opt.children[0].tagNumber === 0) {
|
|
779
|
-
throw ctx.E("cmp/bad-version", "a pkiArchiveOptions encryptedPrivKey using the EnvelopedData form requires CMP version cmp2021(3) (RFC 9810
|
|
776
|
+
throw ctx.E("cmp/bad-version", "a pkiArchiveOptions encryptedPrivKey using the EnvelopedData form requires CMP version cmp2021(3) (RFC 9810 sec. 5.2.8.3.1, sec. 7)");
|
|
780
777
|
}
|
|
781
778
|
}
|
|
782
779
|
}
|
|
783
780
|
}
|
|
784
781
|
}
|
|
785
|
-
// RFC 9810
|
|
782
|
+
// RFC 9810 sec. 5.1.1.4 -- the certProfile name count is bounded by the message
|
|
786
783
|
// body: a p10cr carries at most one; an ir/cr/kur no more names than its
|
|
787
784
|
// CertReqMsg count, a genm no more than its GenMsgContent InfoTypeAndValue
|
|
788
785
|
// count (the names map positionally to the body entries). An overlong list
|
|
@@ -796,18 +793,37 @@ var PKI_MESSAGE = schema.seq([
|
|
|
796
793
|
else if (body.decoded && body.decoded.messages) bodyCount = body.decoded.messages.length;
|
|
797
794
|
else if (body.arm === "genm" && Array.isArray(body.decoded)) bodyCount = body.decoded.length;
|
|
798
795
|
if (bodyCount !== null && profileCount > bodyCount) {
|
|
799
|
-
throw ctx.E("cmp/bad-info-value", "a certProfile carries " + profileCount + " profile name(s), more than the " + bodyCount + " the " + body.arm + " body permits (RFC 9810
|
|
796
|
+
throw ctx.E("cmp/bad-info-value", "a certProfile carries " + profileCount + " profile name(s), more than the " + bodyCount + " the " + body.arm + " body permits (RFC 9810 sec. 5.1.1.4)");
|
|
800
797
|
}
|
|
801
798
|
}
|
|
802
799
|
}
|
|
803
|
-
// RFC 9810
|
|
800
|
+
// RFC 9810 sec. 5.3.13/sec. 7 -- the cAKeyUpdAnnV3 [0] RootCaKeyUpdateContent CA key
|
|
804
801
|
// update form is cmp2021(3) syntax; a ckuann using it under pvno < 3 is a
|
|
805
802
|
// version mismatch. ckuann surfaces raw, so inspect its top-level tag: the
|
|
806
803
|
// deprecated cAKeyUpdAnnV2 is a universal SEQUENCE, the v3 form is [0].
|
|
807
804
|
if (body.arm === "ckuann" && header.pvno !== 3) {
|
|
808
805
|
var caKeyUpd = asn1.decode(body.bytes);
|
|
809
806
|
if (caKeyUpd.tagClass === "context" && caKeyUpd.tagNumber === 0) {
|
|
810
|
-
throw ctx.E("cmp/bad-version", "a ckuann using the cAKeyUpdAnnV3 [0] RootCaKeyUpdateContent form requires CMP version cmp2021(3) (RFC 9810
|
|
807
|
+
throw ctx.E("cmp/bad-version", "a ckuann using the cAKeyUpdAnnV3 [0] RootCaKeyUpdateContent form requires CMP version cmp2021(3) (RFC 9810 sec. 5.3.13, sec. 7)");
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
// RFC 9810 sec. 5.2.8.3.2/sec. 7 -- Challenge.encryptedRand [0] EnvelopedData is
|
|
811
|
+
// cmp2021(3) syntax (the replacement for the deprecated challenge field).
|
|
812
|
+
// popdecc surfaces raw, so inspect the already-decoded Challenges the same
|
|
813
|
+
// way the ckuann gate inspects its top-level tag: a Challenge carrying a
|
|
814
|
+
// context [0] (its only context-tagged field) under pvno < 3 is a version
|
|
815
|
+
// mismatch; a non-Challenges shape stays on the documented raw posture.
|
|
816
|
+
if (body.arm === "popdecc" && header.pvno !== 3) {
|
|
817
|
+
var challenges = asn1.decode(body.bytes);
|
|
818
|
+
if (challenges.tagClass === "universal" && challenges.tagNumber === TAGS.SEQUENCE && challenges.children) {
|
|
819
|
+
for (var chi = 0; chi < challenges.children.length; chi++) {
|
|
820
|
+
var chKids = challenges.children[chi].children;
|
|
821
|
+
for (var chf = 0; chKids && chf < chKids.length; chf++) {
|
|
822
|
+
if (chKids[chf].tagClass === "context" && chKids[chf].tagNumber === 0) {
|
|
823
|
+
throw ctx.E("cmp/bad-version", "a popdecc Challenge carrying encryptedRand [0] EnvelopedData requires CMP version cmp2021(3) (RFC 9810 sec. 5.2.8.3.2, sec. 7)");
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
}
|
|
811
827
|
}
|
|
812
828
|
}
|
|
813
829
|
return {
|
|
@@ -832,7 +848,7 @@ var PKI_MESSAGE = schema.seq([
|
|
|
832
848
|
* Parse a DER `Buffer` or a PEM string into a structured PKIMessage:
|
|
833
849
|
* `{ header, headerBytes, body, bodyBytes, protection, extraCerts }`.
|
|
834
850
|
* `header` carries `pvno` (1..3), `sender` / `recipient` (validated
|
|
835
|
-
* GeneralNames
|
|
851
|
+
* GeneralNames -- the anonymous NULL-DN form included), and the optional
|
|
836
852
|
* `messageTime`, `protectionAlg`, `senderKID` / `recipKID`,
|
|
837
853
|
* `transactionID`, `senderNonce` / `recipNonce`, `freeText`, and
|
|
838
854
|
* `generalInfo` (recognized id-it types value-checked: implicitConfirm is
|
|
@@ -841,24 +857,29 @@ var PKI_MESSAGE = schema.seq([
|
|
|
841
857
|
* (`ir` / `cr` / `kur` / `krr` / `ccr`) decode via the CRMF parser;
|
|
842
858
|
* `ip` / `cp` / `kup` / `ccp` decode to `{ caPubs, response }` (an encrypted
|
|
843
859
|
* certificate's EnvelopedData decodes via the CMS parser; the deprecated
|
|
844
|
-
* EncryptedValue arm and `caPubs` surface raw
|
|
860
|
+
* EncryptedValue arm and `caPubs` surface raw -- the parser confers no
|
|
845
861
|
* trust); `krp` decodes to `{ status, newSigCert, caCerts, keyPairHist }`;
|
|
846
862
|
* `rr` / `rp`, `genm` / `genp`, `error`, `certConf` (an empty
|
|
847
863
|
* confirmation is the legal reject-all), and `pollReq` / `pollRep` decode
|
|
848
|
-
* structurally; `pkiconf` decodes to `null`; every other defined arm
|
|
864
|
+
* structurally; `pkiconf` decodes to `null`; every other defined arm --
|
|
849
865
|
* `p10cr` (feed `body.bytes` to `pki.schema.csr.parse`), the
|
|
850
866
|
* challenge-response and announcement arms, and `nested` (never
|
|
851
|
-
* auto-recursed)
|
|
867
|
+
* auto-recursed) -- surfaces raw with `decoded` absent. `certReqId` values
|
|
852
868
|
* are BigInt and accept the protocol's -1 sentinel.
|
|
853
869
|
*
|
|
854
|
-
* Protection is surfaced, not verified: `protection` carries the raw
|
|
855
|
-
*
|
|
856
|
-
*
|
|
870
|
+
* Protection is surfaced, not verified: `protection` carries the raw,
|
|
871
|
+
* octet-aligned BIT STRING (unused bits are rejected at parse -- every
|
|
872
|
+
* RFC 9481 protection algorithm emits whole octets), and the MAC or
|
|
873
|
+
* signature is computed over the DER of the virtual
|
|
874
|
+
* `ProtectedPart ::= SEQUENCE { header, body }` -- reconstruct it as a DER
|
|
857
875
|
* SEQUENCE wrapping exactly `headerBytes || bodyBytes`. `extraCerts` are
|
|
858
876
|
* raw DER certificates and are NOT covered by protection.
|
|
859
877
|
*
|
|
860
878
|
* Throws `CmpError` when the bytes are not a well-formed PKIMessage, and
|
|
861
|
-
* `Asn1Error` when the underlying DER is malformed.
|
|
879
|
+
* `Asn1Error` when the underlying DER is malformed. The request-arm interiors
|
|
880
|
+
* (`ir` / `cr` / `kur` / `krr` / `ccr`, and an `rr` entry's `certDetails`) are
|
|
881
|
+
* walked by the CRMF parser, so a malformed interior throws `CrmfError` with a
|
|
882
|
+
* `crmf/*` code -- all `PkiError` subclasses.
|
|
862
883
|
*
|
|
863
884
|
* @example
|
|
864
885
|
* var m = pki.schema.cmp.parse(der);
|
|
@@ -879,7 +900,7 @@ var parse = pkix.makeParser({
|
|
|
879
900
|
* @related pki.schema.cmp.parse
|
|
880
901
|
*
|
|
881
902
|
* Extract the DER bytes from a PEM block (default label `CMP`). CMP is
|
|
882
|
-
* wire-DER over HTTP (RFC 9811)
|
|
903
|
+
* wire-DER over HTTP (RFC 9811) -- the PEM path is a convenience for
|
|
883
904
|
* messages that transit text channels.
|
|
884
905
|
*
|
|
885
906
|
* @example
|
|
@@ -907,7 +928,7 @@ function pemEncode(der, label) { return pkix.pemEncode(der, label || "CMP", PemE
|
|
|
907
928
|
// (PKIBody) is context-class constructed [0..26]. The one overlap in the
|
|
908
929
|
// registry is one-directional: a 2-child PKIMessage with body ir [0] also
|
|
909
930
|
// satisfies the shallow ocsp-request probe, so this detector registers AHEAD
|
|
910
|
-
// of ocsp-request
|
|
931
|
+
// of ocsp-request -- while every real OCSPRequest fails here (its tbsRequest
|
|
911
932
|
// never leads with a bare INTEGER).
|
|
912
933
|
function matches(root) {
|
|
913
934
|
var k = pkix.rootSequenceChildren(root, 2, 4);
|