@blamejs/pki 0.1.22 → 0.1.23
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 +35 -0
- package/README.md +16 -6
- package/index.js +16 -12
- package/lib/asn1-der.js +126 -68
- package/lib/constants.js +46 -21
- package/lib/ct.js +54 -39
- package/lib/framework-error.js +57 -34
- package/lib/oid.js +110 -59
- package/lib/path-validate.js +302 -135
- package/lib/schema-all.js +49 -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 +63 -14
- 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 +283 -100
- 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 +156 -22
- package/package.json +3 -2
- package/sbom.cdx.json +6 -6
package/lib/schema-crmf.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @intro
|
|
12
12
|
* Certificate Request Message Format handling per RFC 4211. `parse` decodes a
|
|
13
|
-
* `CertReqMessages`
|
|
13
|
+
* `CertReqMessages` -- the request body CMP and EST enrollment carry -- into an
|
|
14
14
|
* array of messages, each with its `CertRequest` (certReqId, a `CertTemplate`
|
|
15
15
|
* of the requested certificate fields, and any registration controls), an
|
|
16
16
|
* optional proof-of-possession, and optional registration info.
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* X.680 forces EXPLICIT: `issuer [3]` / `subject [5]` are `Name`, dual-accepted
|
|
22
22
|
* here (the standards-compliant EXPLICIT encoding and the dominant IMPLICIT one
|
|
23
23
|
* real tooling emits); and the `OptionalValidity` times are EXPLICIT. version,
|
|
24
|
-
* when supplied, MUST be 2 (RFC 4211
|
|
24
|
+
* when supplied, MUST be 2 (RFC 4211 sec. 5); certReqId is an unbounded signed
|
|
25
25
|
* INTEGER (the RFC 9483 `-1` sentinel is legal). The `CertRequest` byte range
|
|
26
26
|
* the proof-of-possession signature covers, and each `poposkInput`, are surfaced
|
|
27
27
|
* RAW for a downstream verifier; registration controls / info values and the
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
*
|
|
31
31
|
* @card
|
|
32
32
|
* Parse DER / PEM RFC 4211 CertReqMessages into requested-certificate templates,
|
|
33
|
-
* proof-of-possession, and registration controls
|
|
33
|
+
* proof-of-possession, and registration controls -- dual-accepted names, raw
|
|
34
34
|
* verifier inputs, fail-closed.
|
|
35
35
|
*/
|
|
36
36
|
|
|
@@ -49,8 +49,8 @@ var NS = pkix.makeNS("crmf", CrmfError, oid);
|
|
|
49
49
|
var TAGS = asn1.TAGS;
|
|
50
50
|
|
|
51
51
|
// ---- dual-accept Name (issuer [3] / subject [5]) ---------------------
|
|
52
|
-
// RFC 4211 Appendix B is IMPLICIT TAGS, but Name is a CHOICE so X.680
|
|
53
|
-
// forces EXPLICIT tagging
|
|
52
|
+
// RFC 4211 Appendix B is IMPLICIT TAGS, but Name is a CHOICE so X.680 sec. 31.2.7
|
|
53
|
+
// forces EXPLICIT tagging -- the standards-compliant wire is a context [tag]
|
|
54
54
|
// EXPLICIT wrapper around a universal RDNSequence. The dominant tooling
|
|
55
55
|
// (pyasn1-modules, BouncyCastle) instead encodes IMPLICIT (the [tag] REPLACES the
|
|
56
56
|
// RDNSequence 0x30, so the children ARE the RDN SETs). Both occur in real CMP /
|
|
@@ -62,7 +62,7 @@ function crmfName(tag) {
|
|
|
62
62
|
var INAME = pkix.name(NS, { implicitTag: tag }); // IMPLICIT arm: [tag] children ARE the RDN SETs
|
|
63
63
|
return schema.decode(function (n, ctx) {
|
|
64
64
|
if (n.tagClass !== "context" || n.tagNumber !== tag || !n.children) {
|
|
65
|
-
throw ctx.E("crmf/bad-name", "issuer/subject [" + tag + "] must be a Name (RFC 4211
|
|
65
|
+
throw ctx.E("crmf/bad-name", "issuer/subject [" + tag + "] must be a Name (RFC 4211 sec. 5)");
|
|
66
66
|
}
|
|
67
67
|
if (n.children.length === 0) return schema.walk(INAME, n, ctx).result; // IMPLICIT empty RDNSequence
|
|
68
68
|
var c0 = n.children[0];
|
|
@@ -80,11 +80,11 @@ function crmfName(tag) {
|
|
|
80
80
|
});
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
// ---- OptionalValidity (
|
|
83
|
+
// ---- OptionalValidity (sec. 5) -------------------------------------------
|
|
84
84
|
// validity [4] IMPLICIT SEQUENCE { notBefore [0] Time OPTIONAL, notAfter [1] Time
|
|
85
85
|
// OPTIONAL }. Time is a CHOICE of UTCTime / GeneralizedTime, so [0]/[1] are
|
|
86
86
|
// EXPLICIT (an IMPLICIT tag would erase the UTCTime-vs-GeneralizedTime
|
|
87
|
-
// discriminator). RFC 4211
|
|
87
|
+
// discriminator). RFC 4211 sec. 5 requires at least one of the two to be present.
|
|
88
88
|
var OPTIONAL_VALIDITY = schema.seq([
|
|
89
89
|
schema.trailing([
|
|
90
90
|
{ tag: 0, name: "notBefore", schema: schema.time(NS), explicit: true, emptyCode: "crmf/bad-validity" },
|
|
@@ -95,14 +95,14 @@ var OPTIONAL_VALIDITY = schema.seq([
|
|
|
95
95
|
build: function (m, ctx) {
|
|
96
96
|
var f = m.fields;
|
|
97
97
|
if (!f.notBefore.present && !f.notAfter.present) {
|
|
98
|
-
throw ctx.E("crmf/bad-validity", "OptionalValidity must contain notBefore or notAfter (RFC 4211
|
|
98
|
+
throw ctx.E("crmf/bad-validity", "OptionalValidity must contain notBefore or notAfter (RFC 4211 sec. 5)");
|
|
99
99
|
}
|
|
100
100
|
return { notBefore: f.notBefore.present ? f.notBefore.value : null, notAfter: f.notAfter.present ? f.notAfter.value : null };
|
|
101
101
|
},
|
|
102
102
|
});
|
|
103
103
|
|
|
104
|
-
// ---- ProofOfPossession (
|
|
105
|
-
// raVerified [0] NULL
|
|
104
|
+
// ---- ProofOfPossession (sec. 4) ------------------------------------------
|
|
105
|
+
// raVerified [0] NULL -- the RA has already verified POP out of band.
|
|
106
106
|
var POPO_RAVERIFIED = schema.decode(function (n, ctx) {
|
|
107
107
|
try { asn1.read.nullImplicit(n, 0); }
|
|
108
108
|
catch (e) { throw ctx.E("crmf/bad-popo", "raVerified [0] must be an IMPLICIT NULL", e); }
|
|
@@ -110,7 +110,7 @@ var POPO_RAVERIFIED = schema.decode(function (n, ctx) {
|
|
|
110
110
|
});
|
|
111
111
|
|
|
112
112
|
// When poposkInput is present the POP signature is over the DER of the
|
|
113
|
-
// POPOSigningKeyInput VALUE (RFC 4211
|
|
113
|
+
// POPOSigningKeyInput VALUE (RFC 4211 sec. 4.1), which is a SEQUENCE -- but on the wire
|
|
114
114
|
// poposkInput is an IMPLICIT [0] field, so its bytes lead with the context tag
|
|
115
115
|
// (0xA0) rather than the SEQUENCE tag a verifier hashes. `asn1.sequenceTlv` recovers
|
|
116
116
|
// the SEQUENCE-tagged signed region from the field's content; `signedBytes` is that
|
|
@@ -118,17 +118,31 @@ var POPO_RAVERIFIED = schema.decode(function (n, ctx) {
|
|
|
118
118
|
// encoding its peer produced.
|
|
119
119
|
|
|
120
120
|
// POPOSigningKeyInput ::= SEQUENCE { authInfo CHOICE { sender [0] GeneralName,
|
|
121
|
-
// publicKeyMAC PKMACValue }, publicKey SubjectPublicKeyInfo } (
|
|
121
|
+
// publicKeyMAC PKMACValue }, publicKey SubjectPublicKeyInfo } (sec. 4.1). Structurally
|
|
122
122
|
// validated (not deferred raw), so an empty or malformed poposkInput fails closed
|
|
123
123
|
// rather than surfacing a signed region that was never a well-formed POP input.
|
|
124
|
-
// PKMACValue ::= SEQUENCE { algId AlgorithmIdentifier, value BIT STRING }.
|
|
124
|
+
// PKMACValue ::= SEQUENCE { algId AlgorithmIdentifier, value BIT STRING }. The
|
|
125
|
+
// MAC value is octet-string algorithm output handed to an external verifier;
|
|
126
|
+
// with no in-tree POP verify layer, octet alignment is enforced at parse.
|
|
125
127
|
var PKMAC_VALUE = schema.seq([
|
|
126
128
|
schema.field("algId", pkix.algorithmIdentifier(NS)),
|
|
127
129
|
schema.field("value", schema.bitString()),
|
|
128
|
-
], {
|
|
130
|
+
], {
|
|
131
|
+
assert: "sequence", arity: { exact: 2 }, code: "crmf/bad-popo", what: "PKMACValue",
|
|
132
|
+
build: function (m, ctx) {
|
|
133
|
+
// The parsed BIT STRING structure { unusedBits, bytes } -- a structural
|
|
134
|
+
// octet-alignment check on the unused-bit COUNT, not a comparison of the
|
|
135
|
+
// MAC secret (there is no in-tree POP verify), so no timing oracle exists.
|
|
136
|
+
var macBitString = m.fields.value.value;
|
|
137
|
+
if (macBitString.unusedBits !== 0) {
|
|
138
|
+
throw ctx.E("crmf/bad-popo", "a PKMACValue MAC BIT STRING must be octet-aligned (0 unused bits)");
|
|
139
|
+
}
|
|
140
|
+
return { algId: m.fields.algId.value.result, value: { unusedBits: macBitString.unusedBits, bytes: macBitString.bytes } };
|
|
141
|
+
},
|
|
142
|
+
});
|
|
129
143
|
|
|
130
144
|
// authInfo CHOICE: sender [0] GeneralName is EXPLICIT (GeneralName is itself a
|
|
131
|
-
// CHOICE); publicKeyMAC is a bare PKMACValue SEQUENCE
|
|
145
|
+
// CHOICE); publicKeyMAC is a bare PKMACValue SEQUENCE -- disjoint by tag class.
|
|
132
146
|
var POPOSK_AUTH_INFO = schema.choice([
|
|
133
147
|
{ when: { tagClass: "context", tagNumber: 0 }, schema: schema.explicit(0, pkix.generalName(NS, { code: "crmf/bad-popo" }), { code: "crmf/bad-popo" }) },
|
|
134
148
|
{ when: { tagClass: "universal", tagNumber: TAGS.SEQUENCE }, schema: PKMAC_VALUE },
|
|
@@ -140,11 +154,11 @@ var POPOSK_INPUT = schema.seq([
|
|
|
140
154
|
], { assert: "implicit", implicitTag: 0, arity: { exact: 2 }, code: "crmf/bad-popo", what: "POPOSigningKeyInput" });
|
|
141
155
|
|
|
142
156
|
// POPOSigningKey ::= SEQUENCE { poposkInput [0] POPOSigningKeyInput OPTIONAL,
|
|
143
|
-
// algorithmIdentifier AlgorithmIdentifier, signature BIT STRING }
|
|
157
|
+
// algorithmIdentifier AlgorithmIdentifier, signature BIT STRING } -- IMPLICIT [1].
|
|
144
158
|
var POPO_SIGNING_KEY = schema.seq([
|
|
145
159
|
schema.optional("poposkInput", schema.decode(function (n, ctx) {
|
|
146
160
|
// Validate the full POPOSigningKeyInput structure fail-closed, then surface the
|
|
147
|
-
// raw [0] node plus its decoded publicKey (for the
|
|
161
|
+
// raw [0] node plus its decoded publicKey (for the sec. 4.1 template-match check).
|
|
148
162
|
var m = schema.walk(POPOSK_INPUT, n, ctx);
|
|
149
163
|
return { node: n, publicKey: m.fields.publicKey.value.result.bytes };
|
|
150
164
|
}), { tag: 0 }),
|
|
@@ -152,91 +166,99 @@ var POPO_SIGNING_KEY = schema.seq([
|
|
|
152
166
|
schema.field("signature", schema.bitString()),
|
|
153
167
|
], {
|
|
154
168
|
assert: "implicit", implicitTag: 1, code: "crmf/bad-popo", what: "POPOSigningKey",
|
|
155
|
-
build: function (m) {
|
|
169
|
+
build: function (m, ctx) {
|
|
156
170
|
var pin = m.fields.poposkInput;
|
|
171
|
+
var sig = m.fields.signature.value;
|
|
172
|
+
// The POP signature is octet-string algorithm output an external verifier
|
|
173
|
+
// consumes; with no in-tree POP verify layer, octet alignment is enforced
|
|
174
|
+
// at parse (RFC 4211 sec. 4.1).
|
|
175
|
+
if (sig.unusedBits !== 0) {
|
|
176
|
+
throw ctx.E("crmf/bad-popo", "a POPOSigningKey signature BIT STRING must be octet-aligned (0 unused bits)");
|
|
177
|
+
}
|
|
157
178
|
return {
|
|
158
179
|
type: "signature",
|
|
159
180
|
poposkInput: pin.present ? { bytes: pin.node.bytes, signedBytes: asn1.sequenceTlv(pin.node), publicKey: pin.value.publicKey } : null,
|
|
160
181
|
algorithmIdentifier: m.fields.algorithmIdentifier.value.result,
|
|
161
|
-
signature: { unusedBits:
|
|
182
|
+
signature: { unusedBits: sig.unusedBits, bytes: sig.bytes },
|
|
162
183
|
};
|
|
163
184
|
},
|
|
164
185
|
});
|
|
165
186
|
|
|
166
|
-
// keyEncipherment [2] / keyAgreement [3] POPOPrivKey (
|
|
187
|
+
// keyEncipherment [2] / keyAgreement [3] POPOPrivKey (sec. 4). POPOPrivKey is a
|
|
167
188
|
// 5-arm CHOICE { thisMessage [0], subsequentMessage [1], dhMAC [2], agreeMAC [3],
|
|
168
|
-
// encryptedKey [4] }, so X.680
|
|
169
|
-
// (a CHOICE has no single tag to replace). Validate that shell
|
|
170
|
-
// wrapper around exactly one inner context [0]..[4] alternative
|
|
189
|
+
// encryptedKey [4] }, so X.680 sec. 31.2.7 forces the outer [2]/[3] tag to be EXPLICIT
|
|
190
|
+
// (a CHOICE has no single tag to replace). Validate that shell -- a constructed
|
|
191
|
+
// wrapper around exactly one inner context [0]..[4] alternative -- before surfacing
|
|
171
192
|
// the arm RAW, so a primitive / empty / mis-tagged node is rejected rather than
|
|
172
193
|
// reported to a verifier as a well-formed POP. The inner alternative's own value
|
|
173
194
|
// (incl. the encryptedKey [4] EnvelopedData) stays deferred to the verify layer.
|
|
174
195
|
// POPOPrivKey ::= CHOICE { thisMessage [0] BIT STRING (deprecated), subsequentMessage
|
|
175
196
|
// [1] SubsequentMessage, dhMAC [2] BIT STRING (deprecated), agreeMAC [3] PKMACValue,
|
|
176
|
-
// encryptedKey [4] EnvelopedData } (RFC 4211
|
|
197
|
+
// encryptedKey [4] EnvelopedData } (RFC 4211 sec. 4.2). The deprecated arms are still
|
|
177
198
|
// parsed. The inner VALUE stays deferred raw (the EnvelopedData / MAC decode is a
|
|
178
199
|
// verify-layer concern), but the alternative's shape is validated fail-closed.
|
|
179
200
|
var POPOPRIVKEY_METHODS = { 0: "thisMessage", 1: "subsequentMessage", 2: "dhMAC", 3: "agreeMAC", 4: "encryptedKey" };
|
|
180
201
|
function popoPrivKey(type) {
|
|
181
202
|
return schema.decode(function (n, ctx) {
|
|
182
203
|
if (!n.children || n.children.length !== 1) {
|
|
183
|
-
throw ctx.E("crmf/bad-popo", type + " [" + n.tagNumber + "] POPOPrivKey must be an EXPLICIT wrapper around one alternative (RFC 4211
|
|
204
|
+
throw ctx.E("crmf/bad-popo", type + " [" + n.tagNumber + "] POPOPrivKey must be an EXPLICIT wrapper around one alternative (RFC 4211 sec. 4)");
|
|
184
205
|
}
|
|
185
206
|
var inner = n.children[0];
|
|
186
207
|
if (inner.tagClass !== "context" || inner.tagNumber < 0 || inner.tagNumber > 4) {
|
|
187
|
-
throw ctx.E("crmf/bad-popo", type + " POPOPrivKey alternative must be a context [0]..[4] CHOICE element (RFC 4211
|
|
208
|
+
throw ctx.E("crmf/bad-popo", type + " POPOPrivKey alternative must be a context [0]..[4] CHOICE element (RFC 4211 sec. 4)");
|
|
188
209
|
}
|
|
189
|
-
// RFC 4211
|
|
210
|
+
// RFC 4211 sec. 4.2/sec. 4.3 -- the MAC alternatives dhMAC [2] / agreeMAC [3] are the
|
|
190
211
|
// key-agreement "fourth method"; key-encipherment POP uses only the
|
|
191
212
|
// thisMessage / subsequentMessage / encryptedKey methods, so a MAC arm under
|
|
192
213
|
// keyEncipherment is non-conforming.
|
|
193
214
|
if (type === "keyEncipherment" && (inner.tagNumber === 2 || inner.tagNumber === 3)) {
|
|
194
|
-
throw ctx.E("crmf/bad-popo", "keyEncipherment POP cannot use the MAC alternative " + POPOPRIVKEY_METHODS[inner.tagNumber] + " (RFC 4211
|
|
215
|
+
throw ctx.E("crmf/bad-popo", "keyEncipherment POP cannot use the MAC alternative " + POPOPRIVKEY_METHODS[inner.tagNumber] + " (RFC 4211 sec. 4.2)");
|
|
195
216
|
}
|
|
196
217
|
// FORM: thisMessage [0] / subsequentMessage [1] / dhMAC [2] are primitive (BIT
|
|
197
218
|
// STRING / INTEGER / BIT STRING); agreeMAC [3] PKMACValue and encryptedKey [4]
|
|
198
219
|
// EnvelopedData are constructed (SEQUENCE). A wrong-form node is malformed.
|
|
199
220
|
var mustBeConstructed = inner.tagNumber === 3 || inner.tagNumber === 4;
|
|
200
221
|
if (mustBeConstructed !== !!inner.children) {
|
|
201
|
-
throw ctx.E("crmf/bad-popo", type + " POPOPrivKey [" + inner.tagNumber + "] has the wrong primitive/constructed form (RFC 4211
|
|
222
|
+
throw ctx.E("crmf/bad-popo", type + " POPOPrivKey [" + inner.tagNumber + "] has the wrong primitive/constructed form (RFC 4211 sec. 4)");
|
|
202
223
|
}
|
|
203
224
|
// Each alternative's PAYLOAD is decoded fail-closed, not just its form:
|
|
204
225
|
// thisMessage [0] / dhMAC [2] are BIT STRINGs; subsequentMessage [1] an INTEGER
|
|
205
226
|
// in {0,1}; agreeMAC [3] a PKMACValue SEQUENCE; encryptedKey [4] an EnvelopedData.
|
|
206
227
|
if (inner.tagNumber === 0 || inner.tagNumber === 2) {
|
|
207
228
|
try { asn1.read.bitStringImplicit(inner, inner.tagNumber); }
|
|
208
|
-
catch (e) { throw ctx.E("crmf/bad-popo", POPOPRIVKEY_METHODS[inner.tagNumber] + " [" + inner.tagNumber + "] must be a BIT STRING (RFC 4211
|
|
229
|
+
catch (e) { throw ctx.E("crmf/bad-popo", POPOPRIVKEY_METHODS[inner.tagNumber] + " [" + inner.tagNumber + "] must be a BIT STRING (RFC 4211 sec. 4.2)", e); }
|
|
209
230
|
}
|
|
210
231
|
if (inner.tagNumber === 1) {
|
|
211
232
|
var v;
|
|
212
233
|
try { v = asn1.read.integerImplicit(inner, 1); }
|
|
213
234
|
catch (e) { throw ctx.E("crmf/bad-popo", "subsequentMessage must be an INTEGER", e); }
|
|
214
235
|
if (v !== 0n && v !== 1n) {
|
|
215
|
-
throw ctx.E("crmf/bad-popo", "SubsequentMessage must be encrCert(0) or challengeResp(1) (RFC 4211
|
|
236
|
+
throw ctx.E("crmf/bad-popo", "SubsequentMessage must be encrCert(0) or challengeResp(1) (RFC 4211 sec. 4.2)");
|
|
216
237
|
}
|
|
217
238
|
}
|
|
218
239
|
if (inner.tagNumber === 3) {
|
|
219
240
|
try { schema.embeddedDer(PKMAC_VALUE, asn1.sequenceTlv(inner), NS, { code: "crmf/bad-popo", what: "agreeMAC [3] PKMACValue" }); }
|
|
220
|
-
catch (e) { throw ctx.E("crmf/bad-popo", "agreeMAC [3] must be a PKMACValue SEQUENCE { algId, BIT STRING } (RFC 4211
|
|
241
|
+
catch (e) { throw ctx.E("crmf/bad-popo", "agreeMAC [3] must be a PKMACValue SEQUENCE { algId, BIT STRING } (RFC 4211 sec. 4.2)", e); }
|
|
221
242
|
}
|
|
222
|
-
// encryptedKey [4] EnvelopedData
|
|
243
|
+
// encryptedKey [4] EnvelopedData -- validate the CMS structure fail-closed by
|
|
223
244
|
// composing the shared EnvelopedData decoder (retag the IMPLICIT [4] to the
|
|
224
245
|
// universal SEQUENCE it decodes as). The EncKeyWithID identity check lives inside
|
|
225
246
|
// the ENCRYPTED content, so it stays a verify-layer concern.
|
|
226
247
|
if (inner.tagNumber === 4) {
|
|
227
248
|
var env;
|
|
228
249
|
try { env = cms.walkEnvelopedData(asn1.decode(asn1.sequenceTlv(inner))); }
|
|
229
|
-
catch (e) { throw ctx.E("crmf/bad-popo", "encryptedKey [4] must be a well-formed EnvelopedData (RFC 4211
|
|
230
|
-
// RFC 4211
|
|
250
|
+
catch (e) { throw ctx.E("crmf/bad-popo", "encryptedKey [4] must be a well-formed EnvelopedData (RFC 4211 sec. 4.2, RFC 5652 sec. 6.1)", e); }
|
|
251
|
+
// RFC 4211 sec. 4.2 -- the enveloped content type MUST be id-ct-encKeyWithID (the
|
|
231
252
|
// ContentType OID is in the clear even though the content itself is encrypted).
|
|
232
253
|
if (env.encryptedContentInfo.contentType !== oid.byName("encKeyWithID")) {
|
|
233
|
-
throw ctx.E("crmf/bad-popo", "encryptedKey [4] EnvelopedData content type MUST be id-ct-encKeyWithID (RFC 4211
|
|
254
|
+
throw ctx.E("crmf/bad-popo", "encryptedKey [4] EnvelopedData content type MUST be id-ct-encKeyWithID (RFC 4211 sec. 4.2)");
|
|
234
255
|
}
|
|
235
|
-
// The encrypted key material MUST be present
|
|
256
|
+
// The encrypted key material MUST be present -- CMS allows a detached
|
|
236
257
|
// EnvelopedData (encryptedContent OPTIONAL), but a POP with no key to verify
|
|
237
|
-
// or archive is meaningless.
|
|
238
|
-
|
|
239
|
-
|
|
258
|
+
// or archive is meaningless. A zero-length attached ciphertext is the same
|
|
259
|
+
// fault as a detached one (the rule the CMP encryptedKey arm also holds).
|
|
260
|
+
if (env.encryptedContentInfo.encryptedContent == null || env.encryptedContentInfo.encryptedContent.length === 0) {
|
|
261
|
+
throw ctx.E("crmf/bad-popo", "encryptedKey [4] EnvelopedData MUST carry a non-empty encrypted key in encryptedContent (RFC 4211 sec. 4.2)");
|
|
240
262
|
}
|
|
241
263
|
}
|
|
242
264
|
return { type: type, method: POPOPRIVKEY_METHODS[inner.tagNumber], bytes: n.bytes };
|
|
@@ -253,7 +275,7 @@ var PROOF_OF_POSSESSION = schema.choice([
|
|
|
253
275
|
{ when: { tagClass: "context", tagNumber: 3 }, schema: popoPrivKey("keyAgreement") },
|
|
254
276
|
], { code: "crmf/bad-popo" });
|
|
255
277
|
|
|
256
|
-
// ---- Controls / regInfo (
|
|
278
|
+
// ---- Controls / regInfo (sec. 5, sec. 6, sec. 7) ---------------------------------
|
|
257
279
|
// AttributeTypeAndValue ::= SEQUENCE { type OBJECT IDENTIFIER, value ANY DEFINED BY
|
|
258
280
|
// type }. The list is decoded; each value stays RAW (the per-OID value semantics
|
|
259
281
|
// are deferred). Shared shape for both controls and regInfo.
|
|
@@ -268,15 +290,15 @@ var CONTROL = schema.seq([
|
|
|
268
290
|
},
|
|
269
291
|
});
|
|
270
292
|
function mapControls(m) { return m.items.map(function (it) { return it.value.result; }); }
|
|
271
|
-
// Controls ::= SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue (
|
|
293
|
+
// Controls ::= SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue (sec. 5).
|
|
272
294
|
var CONTROLS = schema.seqOf(CONTROL, { assert: "sequence", min: 1, code: "crmf/bad-controls", what: "Controls", build: mapControls });
|
|
273
|
-
// regInfo ::= SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue (
|
|
295
|
+
// regInfo ::= SEQUENCE SIZE(1..MAX) OF AttributeTypeAndValue (sec. 7).
|
|
274
296
|
var REG_INFO = schema.seqOf(CONTROL, { assert: "sequence", min: 1, code: "crmf/bad-reg-info", what: "regInfo", build: mapControls });
|
|
275
297
|
|
|
276
|
-
// ---- CertTemplate (
|
|
298
|
+
// ---- CertTemplate (sec. 5) -- the IMPLICIT-TAGS core ----------------------
|
|
277
299
|
// CertTemplate ::= SEQUENCE { version [0], serialNumber [1], signingAlg [2],
|
|
278
300
|
// issuer [3] Name, validity [4] OptionalValidity, subject [5] Name, publicKey [6]
|
|
279
|
-
// SubjectPublicKeyInfo, issuerUID [7], subjectUID [8], extensions [9] }
|
|
301
|
+
// SubjectPublicKeyInfo, issuerUID [7], subjectUID [8], extensions [9] } -- every
|
|
280
302
|
// field IMPLICIT and OPTIONAL, so the whole body is one ascending [0..9] trailing
|
|
281
303
|
// run. issuer/subject Name and the [4] validity times are the CHOICE exceptions.
|
|
282
304
|
var CERT_TEMPLATE = schema.seq([
|
|
@@ -296,15 +318,20 @@ var CERT_TEMPLATE = schema.seq([
|
|
|
296
318
|
assert: "sequence", code: "crmf/bad-cert-template", what: "CertTemplate",
|
|
297
319
|
build: function (m, ctx) {
|
|
298
320
|
var f = m.fields;
|
|
299
|
-
// version MUST be 2 (v3) when supplied; SHOULD be omitted (RFC 4211
|
|
321
|
+
// version MUST be 2 (v3) when supplied; SHOULD be omitted (RFC 4211 sec. 5). This
|
|
300
322
|
// is a structural CertTemplate rule (it holds wherever the template appears);
|
|
301
323
|
// the CA-assigned-field omission rule is REQUEST-context-specific and lives in
|
|
302
324
|
// CERT_REQUEST.build, because the same CertTemplate structure identifies an
|
|
303
|
-
// existing certificate
|
|
304
|
-
// RevDetails (RFC 9810
|
|
325
|
+
// existing certificate -- serialNumber and issuer present -- inside a CMP
|
|
326
|
+
// RevDetails (RFC 9810 sec. 5.3.9). Every field is surfaced so both the request
|
|
305
327
|
// omission check and the revocation consumer read them off one result.
|
|
306
|
-
|
|
307
|
-
|
|
328
|
+
// The ONE profile override: RFC 9810 Appendix D.6 profiles the CMP
|
|
329
|
+
// cross-certification (ccr) template as "version v1 or v3", so the caller
|
|
330
|
+
// opts in to v1 (0) via ctx.allowV1Version -- the same base-section-vs-
|
|
331
|
+
// profile-appendix override mechanism as the signingAlg relaxation below.
|
|
332
|
+
if (f.version.present && f.version.value !== 2n &&
|
|
333
|
+
!(ctx.allowV1Version && f.version.value === 0n)) {
|
|
334
|
+
throw ctx.E("crmf/bad-version", "CertTemplate version MUST be 2 (v3) if supplied (RFC 4211 sec. 5)");
|
|
308
335
|
}
|
|
309
336
|
return {
|
|
310
337
|
version: f.version.present ? f.version.value : null,
|
|
@@ -321,10 +348,10 @@ var CERT_TEMPLATE = schema.seq([
|
|
|
321
348
|
},
|
|
322
349
|
});
|
|
323
350
|
|
|
324
|
-
// ---- CertRequest / CertReqMsg / CertReqMessages (
|
|
351
|
+
// ---- CertRequest / CertReqMsg / CertReqMessages (sec. 3, sec. 5) --------------
|
|
325
352
|
// CertRequest ::= SEQUENCE { certReqId INTEGER, certTemplate CertTemplate,
|
|
326
353
|
// controls Controls OPTIONAL }. certReqId is a SIGNED INTEGER with no value
|
|
327
|
-
// constraint
|
|
354
|
+
// constraint -- a negative value (the RFC 9483 -1 sentinel) is legal.
|
|
328
355
|
var CERT_REQUEST = schema.seq([
|
|
329
356
|
schema.field("certReqId", schema.integerLeaf()),
|
|
330
357
|
schema.field("certTemplate", CERT_TEMPLATE),
|
|
@@ -333,7 +360,7 @@ var CERT_REQUEST = schema.seq([
|
|
|
333
360
|
assert: "sequence", code: "crmf/bad-cert-request", what: "CertRequest",
|
|
334
361
|
build: function (m, ctx) {
|
|
335
362
|
var tpl = m.fields.certTemplate.value.result;
|
|
336
|
-
// RFC 4211
|
|
363
|
+
// RFC 4211 sec. 5 -- serialNumber, signingAlg, issuerUID, and subjectUID MUST be
|
|
337
364
|
// omitted from a REQUEST's CertTemplate: serialNumber and signingAlg are
|
|
338
365
|
// assigned by the CA, and the UID pair is deprecated. A requester must not
|
|
339
366
|
// dictate a CA-assigned value (a requester-chosen serialNumber is a real
|
|
@@ -350,7 +377,7 @@ var CERT_REQUEST = schema.seq([
|
|
|
350
377
|
: ["serialNumber", "signingAlg", "issuerUID", "subjectUID"];
|
|
351
378
|
for (var i = 0; i < caAssigned.length; i++) {
|
|
352
379
|
if (tpl[caAssigned[i]] !== null) {
|
|
353
|
-
throw ctx.E("crmf/bad-cert-template", "CertTemplate " + caAssigned[i] + " MUST be omitted
|
|
380
|
+
throw ctx.E("crmf/bad-cert-template", "CertTemplate " + caAssigned[i] + " MUST be omitted -- it is CA-assigned or deprecated (RFC 4211 sec. 5)");
|
|
354
381
|
}
|
|
355
382
|
}
|
|
356
383
|
return {
|
|
@@ -358,7 +385,7 @@ var CERT_REQUEST = schema.seq([
|
|
|
358
385
|
certReqIdHex: m.fields.certReqId.node.content.toString("hex"),
|
|
359
386
|
certTemplate: tpl,
|
|
360
387
|
controls: m.fields.controls.present ? m.fields.controls.value.result : null,
|
|
361
|
-
certReqBytes: m.node.bytes, // the exact CertRequest TLV the POP signature covers (
|
|
388
|
+
certReqBytes: m.node.bytes, // the exact CertRequest TLV the POP signature covers (sec. 4.1)
|
|
362
389
|
};
|
|
363
390
|
},
|
|
364
391
|
});
|
|
@@ -377,33 +404,33 @@ var CERT_REQ_MSG = schema.seq([
|
|
|
377
404
|
var certReq = m.fields.certReq.value.result;
|
|
378
405
|
var popo = m.fields.popo.present ? m.fields.popo.value : null;
|
|
379
406
|
// A POP whose signature/MAC covers the certReq requires the CertTemplate to
|
|
380
|
-
// contain both subject and publicKey
|
|
407
|
+
// contain both subject and publicKey -- otherwise there is no complete request
|
|
381
408
|
// to sign / MAC over, and a verifier would be bound to the wrong identity/key.
|
|
382
409
|
var complete = certReq.certTemplate.subject !== null && certReq.certTemplate.publicKey !== null;
|
|
383
|
-
// RFC 4211
|
|
410
|
+
// RFC 4211 sec. 4.1 -- for a signature POP, poposkInput's presence is fixed by the
|
|
384
411
|
// CertTemplate: it MUST be omitted when the template carries BOTH subject and
|
|
385
412
|
// publicKey (the signature is then over the DER of the CertRequest), and MUST be
|
|
386
413
|
// present otherwise.
|
|
387
414
|
if (popo && popo.type === "signature") {
|
|
388
415
|
if (complete && popo.poposkInput !== null) {
|
|
389
|
-
throw ctx.E("crmf/bad-popo", "poposkInput MUST be omitted when the CertTemplate contains both subject and publicKey (RFC 4211
|
|
416
|
+
throw ctx.E("crmf/bad-popo", "poposkInput MUST be omitted when the CertTemplate contains both subject and publicKey (RFC 4211 sec. 4.1)");
|
|
390
417
|
}
|
|
391
418
|
if (!complete && popo.poposkInput === null) {
|
|
392
|
-
throw ctx.E("crmf/bad-popo", "poposkInput MUST be present when the CertTemplate lacks subject or publicKey (RFC 4211
|
|
419
|
+
throw ctx.E("crmf/bad-popo", "poposkInput MUST be present when the CertTemplate lacks subject or publicKey (RFC 4211 sec. 4.1)");
|
|
393
420
|
}
|
|
394
|
-
// RFC 4211
|
|
421
|
+
// RFC 4211 sec. 4.1 -- the POPOSigningKeyInput publicKey MUST be exactly the template
|
|
395
422
|
// publicKey (both surfaced as canonical SubjectPublicKeyInfo DER), so a request
|
|
396
423
|
// cannot sign one key while asking for a certificate over a different key.
|
|
397
424
|
if (popo.poposkInput !== null && certReq.certTemplate.publicKey !== null &&
|
|
398
425
|
!popo.poposkInput.publicKey.equals(certReq.certTemplate.publicKey.bytes)) {
|
|
399
|
-
throw ctx.E("crmf/bad-popo", "POPOSigningKeyInput publicKey MUST equal the CertTemplate publicKey (RFC 4211
|
|
426
|
+
throw ctx.E("crmf/bad-popo", "POPOSigningKeyInput publicKey MUST equal the CertTemplate publicKey (RFC 4211 sec. 4.1)");
|
|
400
427
|
}
|
|
401
428
|
}
|
|
402
|
-
// RFC 4211
|
|
429
|
+
// RFC 4211 sec. 4.2/sec. 4.3 -- a MAC-based POPOPrivKey (dhMAC / agreeMAC) proves
|
|
403
430
|
// possession by MACing the certReq, which MUST include both subject and publicKey.
|
|
404
431
|
if (popo && (popo.type === "keyEncipherment" || popo.type === "keyAgreement") &&
|
|
405
432
|
(popo.method === "dhMAC" || popo.method === "agreeMAC") && !complete) {
|
|
406
|
-
throw ctx.E("crmf/bad-popo", "a MAC-based key-agreement/encipherment POP (dhMAC/agreeMAC) requires the CertTemplate to contain both subject and publicKey (RFC 4211
|
|
433
|
+
throw ctx.E("crmf/bad-popo", "a MAC-based key-agreement/encipherment POP (dhMAC/agreeMAC) requires the CertTemplate to contain both subject and publicKey (RFC 4211 sec. 4.2/sec. 4.3)");
|
|
407
434
|
}
|
|
408
435
|
return {
|
|
409
436
|
certReq: certReq,
|
|
@@ -413,8 +440,10 @@ var CERT_REQ_MSG = schema.seq([
|
|
|
413
440
|
},
|
|
414
441
|
});
|
|
415
442
|
|
|
416
|
-
// CertReqMessages ::= SEQUENCE SIZE(1..MAX) OF CertReqMsg (
|
|
417
|
-
// is malformed.
|
|
443
|
+
// CertReqMessages ::= SEQUENCE SIZE(1..MAX) OF CertReqMsg (sec. 3) -- an empty sequence
|
|
444
|
+
// is malformed. The top-level reject code predates the
|
|
445
|
+
// `<prefix>/not-a-<structure>` convention and stays frozen on the public
|
|
446
|
+
// error-code surface.
|
|
418
447
|
var CERT_REQ_MESSAGES = schema.seqOf(CERT_REQ_MSG, {
|
|
419
448
|
assert: "sequence", min: 1, code: "crmf/bad-cert-req-messages", what: "CertReqMessages",
|
|
420
449
|
build: function (m) { return { messages: m.items.map(function (it) { return it.value.result; }) }; },
|
|
@@ -434,17 +463,20 @@ var CERT_REQ_MESSAGES = schema.seqOf(CERT_REQ_MSG, {
|
|
|
434
463
|
* `{ messages: [ { certReq, popo, regInfo } ] }`. Each `certReq` is
|
|
435
464
|
* `{ certReqId, certReqIdHex, certTemplate, controls, certReqBytes }`, and
|
|
436
465
|
* `certTemplate` carries the requestable certificate fields (`version`, `issuer`,
|
|
437
|
-
* `validity`, `subject`, `publicKey`, `extensions`
|
|
438
|
-
* CA-assigned / deprecated fields RFC 4211
|
|
466
|
+
* `validity`, `subject`, `publicKey`, `extensions` -- each `null` when absent). The
|
|
467
|
+
* CA-assigned / deprecated fields RFC 4211 sec. 5 requires a request to omit
|
|
439
468
|
* (`serialNumber`, `signingAlg`, `issuerUID`, `subjectUID`) are rejected, not
|
|
440
469
|
* surfaced. `popo` is
|
|
441
470
|
* `null`, `{ type: "raVerified" }`, `{ type: "signature", poposkInput,
|
|
442
471
|
* algorithmIdentifier, signature }`, or `{ type: "keyEncipherment" |
|
|
443
|
-
* "keyAgreement", method, bytes }` (where `method` is the POPOPrivKey alternative
|
|
444
|
-
* `thisMessage` / `subsequentMessage` / `dhMAC` / `agreeMAC` / `encryptedKey`
|
|
445
|
-
* structurally validated, the `encryptedKey` EnvelopedData included). When present, `poposkInput` is
|
|
446
|
-
*
|
|
447
|
-
* re-tagged to the `SEQUENCE` DER the
|
|
472
|
+
* "keyAgreement", method, bytes }` (where `method` is the POPOPrivKey alternative --
|
|
473
|
+
* `thisMessage` / `subsequentMessage` / `dhMAC` / `agreeMAC` / `encryptedKey` -- each
|
|
474
|
+
* structurally validated, the `encryptedKey` EnvelopedData included). When present, `poposkInput` is
|
|
475
|
+
* `{ bytes, signedBytes, publicKey }` -- `bytes` is the raw wire `[0]` TLV,
|
|
476
|
+
* `signedBytes` is the `POPOSigningKeyInput` re-tagged to the `SEQUENCE` DER the
|
|
477
|
+
* signature actually covers, and `publicKey` is the canonical
|
|
478
|
+
* `SubjectPublicKeyInfo` DER the RFC 4211 sec. 4.1 template-match check compares
|
|
479
|
+
* against the `CertTemplate` publicKey (a POP verifier imports it directly).
|
|
448
480
|
* `certReqBytes` is the exact `CertRequest` byte range a proof-of-possession
|
|
449
481
|
* verifier hashes when `poposkInput` is absent.
|
|
450
482
|
*
|
|
@@ -469,7 +501,7 @@ var parse = pkix.makeParser({
|
|
|
469
501
|
* @related pki.schema.crmf.parse
|
|
470
502
|
*
|
|
471
503
|
* Extract the DER bytes from a PEM block (RFC 4211 registers no RFC 7468 label, so
|
|
472
|
-
* the first block is taken unless `label` is given
|
|
504
|
+
* the first block is taken unless `label` is given -- CRMF rides inside CMP / EST as
|
|
473
505
|
* DER in practice). Throws `PemError` on a missing envelope or a non-base64 body.
|
|
474
506
|
*
|
|
475
507
|
* @example
|
|
@@ -477,6 +509,23 @@ var parse = pkix.makeParser({
|
|
|
477
509
|
*/
|
|
478
510
|
function pemDecode(text, label) { return pkix.pemDecode(text, label || null, PemError); }
|
|
479
511
|
|
|
512
|
+
/**
|
|
513
|
+
* @primitive pki.schema.crmf.pemEncode
|
|
514
|
+
* @signature pki.schema.crmf.pemEncode(der, label) -> string
|
|
515
|
+
* @since 0.1.23
|
|
516
|
+
* @status experimental
|
|
517
|
+
* @spec RFC 7468, RFC 4211
|
|
518
|
+
* @related pki.schema.crmf.pemDecode
|
|
519
|
+
*
|
|
520
|
+
* Wrap DER bytes in a PEM envelope. RFC 4211 registers no RFC 7468 label, so
|
|
521
|
+
* `label` is REQUIRED -- the operator names the envelope explicitly (mirroring
|
|
522
|
+
* `pemDecode`, which accepts any label). Omitting it throws `pem/bad-label`.
|
|
523
|
+
*
|
|
524
|
+
* @example
|
|
525
|
+
* var pem = pki.schema.crmf.pemEncode(der, "CERT REQUEST MESSAGES");
|
|
526
|
+
*/
|
|
527
|
+
function pemEncode(der, label) { return pkix.pemEncode(der, label, PemError); }
|
|
528
|
+
|
|
480
529
|
// matches(root): a CertReqMessages is a universal SEQUENCE whose first CertReqMsg
|
|
481
530
|
// is a SEQUENCE whose first CertRequest is a SEQUENCE leading with a universal
|
|
482
531
|
// INTEGER (certReqId) then a universal SEQUENCE (certTemplate). This 3-level probe
|
|
@@ -495,15 +544,22 @@ function matches(root) {
|
|
|
495
544
|
schema.isUniversal(tpl, TAGS.SEQUENCE) && !!tpl.children;
|
|
496
545
|
}
|
|
497
546
|
|
|
498
|
-
// Validate a bare, already-decoded CertReqMessages / CertTemplate node
|
|
547
|
+
// Validate a bare, already-decoded CertReqMessages / CertTemplate node -- for a
|
|
499
548
|
// composer that carries these structures inside its own envelope (an RFC 9810
|
|
500
549
|
// CMP PKIBody's ir/cr/kur/krr/ccr arms; a RevDetails.certDetails). NS-bound to
|
|
501
550
|
// crmf so an interior failure throws CrmfError with a crmf/* code, never the
|
|
502
551
|
// composer's class wearing a foreign code. Same contract as the CMS walkers.
|
|
503
|
-
// opts
|
|
504
|
-
// the CMP cross-certification
|
|
552
|
+
// The opts carry the RFC 9810 Appendix D.6 ccr-profile overrides of RFC 4211 sec. 5,
|
|
553
|
+
// applied by the CMP composer to the cross-certification arm only:
|
|
554
|
+
// opts.allowSigningAlg relaxes the request-template signingAlg-omission rule and
|
|
555
|
+
// opts.allowV1Version accepts a CertTemplate version of v1 ("version v1 or v3").
|
|
505
556
|
function walkCertReqMessages(node, opts) {
|
|
506
|
-
var ctx =
|
|
557
|
+
var ctx = NS;
|
|
558
|
+
if (opts && (opts.allowSigningAlg || opts.allowV1Version)) {
|
|
559
|
+
ctx = Object.assign({}, NS);
|
|
560
|
+
if (opts.allowSigningAlg) ctx.allowSigningAlg = true;
|
|
561
|
+
if (opts.allowV1Version) ctx.allowV1Version = true;
|
|
562
|
+
}
|
|
507
563
|
return schema.walk(CERT_REQ_MESSAGES, node, ctx).result;
|
|
508
564
|
}
|
|
509
565
|
function walkCertTemplate(node) { return schema.walk(CERT_TEMPLATE, node, NS).result; }
|
|
@@ -511,11 +567,12 @@ function walkCertTemplate(node) { return schema.walk(CERT_TEMPLATE, node, NS).re
|
|
|
511
567
|
module.exports = {
|
|
512
568
|
parse: parse,
|
|
513
569
|
pemDecode: pemDecode,
|
|
570
|
+
pemEncode: pemEncode,
|
|
514
571
|
matches: matches,
|
|
515
572
|
walkCertReqMessages: walkCertReqMessages,
|
|
516
573
|
walkCertTemplate: walkCertTemplate,
|
|
517
574
|
// The top schema, exposed (not on the curated pki.schema.crmf surface) so a
|
|
518
575
|
// round-trip test can prove schema.encode(schema, v) -> decode -> parse recovers
|
|
519
|
-
// v
|
|
576
|
+
// v -- the invariant that structurally validates the IMPLICIT/EXPLICIT tag handling.
|
|
520
577
|
certReqMessagesSchema: CERT_REQ_MESSAGES,
|
|
521
578
|
};
|