@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-pkcs12.js
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
*
|
|
11
11
|
* @intro
|
|
12
12
|
* PKCS#12 (PFX) key-and-certificate store handling per RFC 7292. `parse`
|
|
13
|
-
* decodes a `.p12` / `.pfx` container
|
|
14
|
-
* format OpenSSL, Windows CAPI, macOS Keychain, and NSS emit
|
|
13
|
+
* decodes a `.p12` / `.pfx` container -- the personal-information-exchange
|
|
14
|
+
* format OpenSSL, Windows CAPI, macOS Keychain, and NSS emit -- into its
|
|
15
15
|
* bags: private keys (delegated to the PKCS#8 parser), shrouded keys
|
|
16
16
|
* (algorithm surfaced, ciphertext kept opaque), certificates, CRLs,
|
|
17
17
|
* secrets, and nested safe contents, each with its `friendlyName` /
|
|
@@ -26,14 +26,14 @@
|
|
|
26
26
|
* (`macedBytes`), public-key mode the SignedData with its signers, and a
|
|
27
27
|
* MAC-less store surfaces `integrityMode: "none"` for the caller's policy
|
|
28
28
|
* to judge. RFC 7292
|
|
29
|
-
*
|
|
30
|
-
* two BER shapes
|
|
29
|
+
* sec. 4.1 encodes content in BER, so parsing this one format accepts exactly
|
|
30
|
+
* two BER shapes -- indefinite lengths and constructed OCTET STRINGs --
|
|
31
31
|
* anywhere in the store; every other strictness verdict, and every other
|
|
32
32
|
* format, stays strict DER, fail-closed.
|
|
33
33
|
*
|
|
34
34
|
* @card
|
|
35
35
|
* Parse DER / BER / PEM RFC 7292 PKCS#12 (PFX) stores into key / cert /
|
|
36
|
-
* CRL / secret bags with their attributes
|
|
36
|
+
* CRL / secret bags with their attributes -- keys via the PKCS#8 parser,
|
|
37
37
|
* encrypted safes via CMS, MAC inputs surfaced raw for external
|
|
38
38
|
* verification, fail-closed.
|
|
39
39
|
*/
|
|
@@ -52,26 +52,30 @@ var PemError = frameworkError.PemError;
|
|
|
52
52
|
var NS = pkix.makeNS("pkcs12", Pkcs12Error, oid);
|
|
53
53
|
var TAGS = asn1.TAGS;
|
|
54
54
|
|
|
55
|
-
// Content types (RFC 5652
|
|
55
|
+
// Content types (RFC 5652 sec. 4 / RFC 7292 sec. 4.1).
|
|
56
56
|
var OID_DATA = oid.byName("data");
|
|
57
57
|
var OID_SIGNED_DATA = oid.byName("signedData");
|
|
58
58
|
var OID_ENVELOPED_DATA = oid.byName("envelopedData");
|
|
59
59
|
var OID_ENCRYPTED_DATA = oid.byName("encryptedData");
|
|
60
|
-
// Bag types (RFC 7292
|
|
60
|
+
// Bag types (RFC 7292 sec. 4.2).
|
|
61
61
|
var OID_KEY_BAG = oid.byName("keyBag");
|
|
62
62
|
var OID_SHROUDED_KEY_BAG = oid.byName("pkcs8ShroudedKeyBag");
|
|
63
63
|
var OID_CERT_BAG = oid.byName("certBag");
|
|
64
64
|
var OID_CRL_BAG = oid.byName("crlBag");
|
|
65
65
|
var OID_SECRET_BAG = oid.byName("secretBag");
|
|
66
66
|
var OID_SAFE_CONTENTS_BAG = oid.byName("safeContentsBag");
|
|
67
|
-
// CertBag / CRLBag discriminators (
|
|
67
|
+
// CertBag / CRLBag discriminators (sec. 4.2.3-sec. 4.2.4).
|
|
68
68
|
var OID_X509_CERTIFICATE = oid.byName("x509Certificate");
|
|
69
69
|
var OID_SDSI_CERTIFICATE = oid.byName("sdsiCertificate");
|
|
70
70
|
var OID_X509_CRL = oid.byName("x509CRL");
|
|
71
|
-
// Bag attributes (
|
|
71
|
+
// Bag attributes (sec. 4.2) + the RFC 9579 MacData arm.
|
|
72
72
|
var OID_FRIENDLY_NAME = oid.byName("friendlyName");
|
|
73
73
|
var OID_LOCAL_KEY_ID = oid.byName("localKeyId");
|
|
74
74
|
var OID_PBMAC1 = oid.byName("pbmac1");
|
|
75
|
+
// The PBKDF2-params prf DEFAULT is algid-hmacWithSHA1 -- hmacWithSHA1 with NULL
|
|
76
|
+
// parameters (RFC 8018 sec. 5.2) -- needed for the encoded-DEFAULT rejection below.
|
|
77
|
+
var OID_HMAC_SHA1 = oid.byName("hmacWithSHA1");
|
|
78
|
+
var DER_NULL = asn1.build.nullValue();
|
|
75
79
|
|
|
76
80
|
// DigestInfo ::= SEQUENCE { digestAlgorithm AlgorithmIdentifier, digest OCTET STRING }.
|
|
77
81
|
var DIGEST_INFO = schema.seq([
|
|
@@ -84,9 +88,9 @@ var DIGEST_INFO = schema.seq([
|
|
|
84
88
|
},
|
|
85
89
|
});
|
|
86
90
|
|
|
87
|
-
// PBKDF2-params (RFC 8018
|
|
91
|
+
// PBKDF2-params (RFC 8018 sec. 5.2), constrained to the RFC 9579 PBMAC1 profile:
|
|
88
92
|
// the salt MUST use the specified (OCTET STRING) choice and keyLength MUST be
|
|
89
|
-
// present (
|
|
93
|
+
// present (sec. 4.b / sec. 5 -- a MacData consumer cannot infer the MAC key size).
|
|
90
94
|
var PBKDF2_PARAMS = schema.seq([
|
|
91
95
|
schema.field("salt", schema.octetString()),
|
|
92
96
|
schema.field("iterationCount", schema.integerLeaf()),
|
|
@@ -96,9 +100,9 @@ var PBKDF2_PARAMS = schema.seq([
|
|
|
96
100
|
assert: "sequence", code: "pkcs12/bad-mac-data", what: "PBKDF2-params",
|
|
97
101
|
build: function (m, ctx) {
|
|
98
102
|
if (!m.fields.keyLength.present) {
|
|
99
|
-
throw ctx.E("pkcs12/bad-mac-data", "PBMAC1 PBKDF2-params must carry keyLength (RFC 9579
|
|
103
|
+
throw ctx.E("pkcs12/bad-mac-data", "PBMAC1 PBKDF2-params must carry keyLength (RFC 9579 sec. 5)");
|
|
100
104
|
}
|
|
101
|
-
// Both counters surface as exact JS numbers
|
|
105
|
+
// Both counters surface as exact JS numbers -- a value past the bound
|
|
102
106
|
// would round silently on conversion and hand a verifier wrong inputs.
|
|
103
107
|
var iterations = m.fields.iterationCount.value;
|
|
104
108
|
if (iterations < 1n || iterations > 2147483647n) {
|
|
@@ -109,18 +113,27 @@ var PBKDF2_PARAMS = schema.seq([
|
|
|
109
113
|
throw ctx.E("pkcs12/bad-mac-data", "PBKDF2 keyLength must be a positive integer within the key-length range");
|
|
110
114
|
}
|
|
111
115
|
var prf = m.fields.prf.present ? m.fields.prf.value.result : null;
|
|
116
|
+
// X.690 sec. 11.5 -- a DEFAULT-valued component must be omitted from a DER
|
|
117
|
+
// encoding, and the prf DEFAULT is algid-hmacWithSHA1: hmacWithSHA1 with
|
|
118
|
+
// NULL parameters (RFC 8018 sec. 5.2). An explicit prf byte-equal to that
|
|
119
|
+
// default is non-canonical and rejects -- the structured-value analogue of
|
|
120
|
+
// the MacData iterations rule below. hmacWithSHA1 with ABSENT parameters
|
|
121
|
+
// is a different value from the NULL-parameters default and decodes.
|
|
122
|
+
if (prf && prf.oid === OID_HMAC_SHA1 && prf.parameters !== null && prf.parameters.equals(DER_NULL)) {
|
|
123
|
+
throw ctx.E("pkcs12/bad-mac-data", "a PBKDF2 prf equal to its DEFAULT algid-hmacWithSHA1 must be omitted (X.690 sec. 11.5, RFC 8018 sec. 5.2)");
|
|
124
|
+
}
|
|
112
125
|
return {
|
|
113
126
|
salt: m.fields.salt.value,
|
|
114
127
|
iterationCount: Number(iterations),
|
|
115
128
|
keyLength: Number(keyLength),
|
|
116
|
-
prfOid: prf ? prf.oid :
|
|
129
|
+
prfOid: prf ? prf.oid : OID_HMAC_SHA1,
|
|
117
130
|
prfName: prf ? prf.name : "hmacWithSHA1",
|
|
118
131
|
};
|
|
119
132
|
},
|
|
120
133
|
});
|
|
121
134
|
|
|
122
135
|
// PBMAC1-params ::= SEQUENCE { keyDerivationFunc AlgorithmIdentifier{PBKDF2},
|
|
123
|
-
// messageAuthScheme AlgorithmIdentifier } (RFC 8018
|
|
136
|
+
// messageAuthScheme AlgorithmIdentifier } (RFC 8018 sec. A.5 / RFC 9579 sec. 4).
|
|
124
137
|
var PBMAC1_PARAMS = schema.seq([
|
|
125
138
|
schema.field("keyDerivationFunc", schema.seq([
|
|
126
139
|
schema.field("algorithm", schema.oidLeaf()),
|
|
@@ -132,7 +145,7 @@ var PBMAC1_PARAMS = schema.seq([
|
|
|
132
145
|
build: function (m, ctx) {
|
|
133
146
|
var kdf = m.fields.keyDerivationFunc.value;
|
|
134
147
|
if (kdf.fields.algorithm.value !== oid.byName("pbkdf2")) {
|
|
135
|
-
throw ctx.E("pkcs12/bad-mac-data", "PBMAC1 keyDerivationFunc must be PBKDF2 (RFC 9579
|
|
148
|
+
throw ctx.E("pkcs12/bad-mac-data", "PBMAC1 keyDerivationFunc must be PBKDF2 (RFC 9579 sec. 4)");
|
|
136
149
|
}
|
|
137
150
|
var scheme = m.fields.messageAuthScheme.value.result;
|
|
138
151
|
return {
|
|
@@ -152,23 +165,23 @@ var MAC_DATA = schema.seq([
|
|
|
152
165
|
], {
|
|
153
166
|
assert: "sequence", code: "pkcs12/bad-mac-data", what: "MacData",
|
|
154
167
|
build: function (m, ctx) {
|
|
155
|
-
// X.690
|
|
168
|
+
// X.690 sec. 11.5 -- a DEFAULT-valued component must be omitted from a DER
|
|
156
169
|
// encoding, so an explicitly-encoded iterations = 1 is non-canonical.
|
|
157
170
|
var it = m.fields.iterations;
|
|
158
171
|
var iterations = 1;
|
|
159
172
|
if (it.present) {
|
|
160
173
|
var v = it.value;
|
|
161
|
-
if (v === 1n) throw ctx.E("pkcs12/bad-mac-iterations", "iterations equal to its DEFAULT 1 must be omitted (X.690
|
|
174
|
+
if (v === 1n) throw ctx.E("pkcs12/bad-mac-iterations", "iterations equal to its DEFAULT 1 must be omitted (X.690 sec. 11.5)");
|
|
162
175
|
if (v < 1n || v > 2147483647n) throw ctx.E("pkcs12/bad-mac-iterations", "iterations must be a positive integer within the iteration-count range");
|
|
163
176
|
iterations = Number(v);
|
|
164
177
|
}
|
|
165
178
|
var di = m.fields.mac.value.result;
|
|
166
179
|
var pbmac1 = null;
|
|
167
180
|
if (di.digestAlgorithm.oid === OID_PBMAC1) {
|
|
168
|
-
// RFC 9579
|
|
181
|
+
// RFC 9579 sec. 4 -- the PBMAC1 algorithm identifier MUST carry PBMAC1-params
|
|
169
182
|
// (the KDF and MAC scheme a verifier needs live there, not in MacData).
|
|
170
183
|
if (di.digestAlgorithm.parameters === null) {
|
|
171
|
-
throw ctx.E("pkcs12/bad-mac-data", "a PBMAC1 MacData must carry PBMAC1-params (RFC 9579
|
|
184
|
+
throw ctx.E("pkcs12/bad-mac-data", "a PBMAC1 MacData must carry PBMAC1-params (RFC 9579 sec. 4)");
|
|
172
185
|
}
|
|
173
186
|
pbmac1 = schema.embeddedDer(PBMAC1_PARAMS, di.digestAlgorithm.parameters, ctx,
|
|
174
187
|
{ code: "pkcs12/bad-mac-data", what: "PBMAC1-params", ber: true }).result;
|
|
@@ -186,7 +199,7 @@ var MAC_DATA = schema.seq([
|
|
|
186
199
|
},
|
|
187
200
|
});
|
|
188
201
|
|
|
189
|
-
// CertBag ::= SEQUENCE { certId OID, certValue [0] EXPLICIT ANY } (
|
|
202
|
+
// CertBag ::= SEQUENCE { certId OID, certValue [0] EXPLICIT ANY } (sec. 4.2.3):
|
|
190
203
|
// x509Certificate wraps the DER certificate in an OCTET STRING; sdsiCertificate
|
|
191
204
|
// is an IA5String. Both arms of the closed set are accepted; the value is
|
|
192
205
|
// surfaced raw (byte-exact), never re-encoded or recursively parsed.
|
|
@@ -203,15 +216,15 @@ var CERT_BAG = schema.seq([
|
|
|
203
216
|
}
|
|
204
217
|
if (certId === OID_SDSI_CERTIFICATE) {
|
|
205
218
|
if (!(node.tagClass === "universal" && node.tagNumber === TAGS.IA5_STRING && node.content)) {
|
|
206
|
-
throw ctx.E("pkcs12/bad-bag-value", "an sdsiCertificate certValue must be an IA5String (RFC 7292
|
|
219
|
+
throw ctx.E("pkcs12/bad-bag-value", "an sdsiCertificate certValue must be an IA5String (RFC 7292 sec. 4.2.3)");
|
|
207
220
|
}
|
|
208
221
|
return { certType: "sdsiCertificate", certId: certId, certValue: node.content };
|
|
209
222
|
}
|
|
210
|
-
throw ctx.E("pkcs12/bad-cert-type", (ctx.oid.name(certId) || certId) + " is not a recognized CertBag certId (RFC 7292
|
|
223
|
+
throw ctx.E("pkcs12/bad-cert-type", (ctx.oid.name(certId) || certId) + " is not a recognized CertBag certId (RFC 7292 sec. 4.2.3)");
|
|
211
224
|
},
|
|
212
225
|
});
|
|
213
226
|
|
|
214
|
-
// CRLBag ::= SEQUENCE { crlId OID, crlValue [0] EXPLICIT ANY } (
|
|
227
|
+
// CRLBag ::= SEQUENCE { crlId OID, crlValue [0] EXPLICIT ANY } (sec. 4.2.4).
|
|
215
228
|
var CRL_BAG = schema.seq([
|
|
216
229
|
schema.field("crlId", schema.oidLeaf()),
|
|
217
230
|
schema.field("crlValue", schema.explicit(0, schema.any(), { code: "pkcs12/bad-bag-value", what: "crlValue" })),
|
|
@@ -220,15 +233,15 @@ var CRL_BAG = schema.seq([
|
|
|
220
233
|
build: function (m, ctx) {
|
|
221
234
|
var crlId = m.fields.crlId.value;
|
|
222
235
|
if (crlId !== OID_X509_CRL) {
|
|
223
|
-
throw ctx.E("pkcs12/bad-crl-type", (ctx.oid.name(crlId) || crlId) + " is not a recognized CRLBag crlId (RFC 7292
|
|
236
|
+
throw ctx.E("pkcs12/bad-crl-type", (ctx.oid.name(crlId) || crlId) + " is not a recognized CRLBag crlId (RFC 7292 sec. 4.2.4)");
|
|
224
237
|
}
|
|
225
238
|
return { crlType: "x509CRL", crlId: crlId, crlValue: _octetContent(m.fields.crlValue.value, ctx, "an x509CRL crlValue") };
|
|
226
239
|
},
|
|
227
240
|
});
|
|
228
241
|
|
|
229
242
|
// SecretBag ::= SEQUENCE { secretTypeId OID, secretValue [0] EXPLICIT ANY }
|
|
230
|
-
// (
|
|
231
|
-
// the value raw
|
|
243
|
+
// (sec. 4.2.5). SecretTypes is an open set, so the type is surfaced by OID with
|
|
244
|
+
// the value raw -- an unrecognized secret is representable, not a fault.
|
|
232
245
|
var SECRET_BAG = schema.seq([
|
|
233
246
|
schema.field("secretTypeId", schema.oidLeaf()),
|
|
234
247
|
schema.field("secretValue", schema.explicit(0, schema.any(), { code: "pkcs12/bad-bag-value", what: "secretValue" })),
|
|
@@ -241,7 +254,7 @@ var SECRET_BAG = schema.seq([
|
|
|
241
254
|
});
|
|
242
255
|
|
|
243
256
|
// SafeBag ::= SEQUENCE { bagId OID, bagValue [0] EXPLICIT ANY DEFINED BY bagId,
|
|
244
|
-
// bagAttributes SET OF PKCS12Attribute OPTIONAL } (
|
|
257
|
+
// bagAttributes SET OF PKCS12Attribute OPTIONAL } (sec. 4.2).
|
|
245
258
|
// The build surfaces the structural record (bagId + the inner value node +
|
|
246
259
|
// decoded attributes); the bagId dispatch happens in _buildBag, where the
|
|
247
260
|
// per-parse recursion state lives.
|
|
@@ -289,14 +302,14 @@ var SAFE_BAG = schema.seq([
|
|
|
289
302
|
},
|
|
290
303
|
});
|
|
291
304
|
|
|
292
|
-
// SafeContents ::= SEQUENCE OF SafeBag (
|
|
305
|
+
// SafeContents ::= SEQUENCE OF SafeBag (sec. 4.2). Strictly a SEQUENCE -- a SET OF
|
|
293
306
|
// here is a known producer divergence and rejects.
|
|
294
307
|
var SAFE_CONTENTS = schema.seqOf(SAFE_BAG, {
|
|
295
308
|
code: "pkcs12/bad-safe-contents", what: "SafeContents",
|
|
296
309
|
max: C.LIMITS.PKCS12_MAX_ELEMENTS, maxCode: "pkcs12/too-many-elements",
|
|
297
310
|
});
|
|
298
311
|
|
|
299
|
-
// AuthenticatedSafe ::= SEQUENCE OF ContentInfo (
|
|
312
|
+
// AuthenticatedSafe ::= SEQUENCE OF ContentInfo (sec. 4.1) -- each element parsed
|
|
300
313
|
// structurally here and dispatched by contentType in _dispatchSafes.
|
|
301
314
|
var AS_ELEMENT = schema.seq([
|
|
302
315
|
schema.field("contentType", schema.oidLeaf()),
|
|
@@ -312,7 +325,7 @@ var AUTHENTICATED_SAFE = schema.seqOf(AS_ELEMENT, {
|
|
|
312
325
|
max: C.LIMITS.PKCS12_MAX_ELEMENTS, maxCode: "pkcs12/too-many-elements",
|
|
313
326
|
});
|
|
314
327
|
|
|
315
|
-
// The authSafe ContentInfo (
|
|
328
|
+
// The authSafe ContentInfo (sec. 4): contentType is data (password integrity) or
|
|
316
329
|
// signedData (public-key integrity) and nothing else.
|
|
317
330
|
var AUTH_SAFE = schema.seq([
|
|
318
331
|
schema.field("contentType", schema.oidLeaf()),
|
|
@@ -325,7 +338,7 @@ var AUTH_SAFE = schema.seq([
|
|
|
325
338
|
});
|
|
326
339
|
|
|
327
340
|
// PFX ::= SEQUENCE { version INTEGER {v3(3)}, authSafe ContentInfo,
|
|
328
|
-
// macData MacData OPTIONAL } (
|
|
341
|
+
// macData MacData OPTIONAL } (sec. 4).
|
|
329
342
|
var PFX = schema.seq([
|
|
330
343
|
schema.field("version", pkix.versionReader(NS, { "3": 3 })),
|
|
331
344
|
schema.field("authSafe", AUTH_SAFE),
|
|
@@ -342,14 +355,14 @@ var PFX = schema.seq([
|
|
|
342
355
|
var state = { budget: { remaining: C.LIMITS.PKCS12_MAX_REDECODES }, bagDepth: 0 };
|
|
343
356
|
|
|
344
357
|
if (authSafe.contentType === OID_DATA) {
|
|
345
|
-
//
|
|
358
|
+
// sec. 5.1 step 5B -- the MAC covers the value octets of the id-data OCTET
|
|
346
359
|
// STRING (the AuthenticatedSafe encoding), excluding the TLV header.
|
|
347
360
|
// MacData itself is OPTIONAL in the PFX syntax: a store without it
|
|
348
361
|
// (OpenSSL `pkcs12 -export -nomac`) carries no integrity protection at
|
|
349
|
-
// all, which is a policy concern for the caller
|
|
362
|
+
// all, which is a policy concern for the caller -- surfaced as
|
|
350
363
|
// integrityMode "none", never rejected by a parser that surfaces
|
|
351
364
|
// integrity inputs rather than verifying them.
|
|
352
|
-
var macedBytes = _octetContent(authSafe.innerNode, ctx, "an id-data authSafe content");
|
|
365
|
+
var macedBytes = _octetContent(authSafe.innerNode, ctx, "an id-data authSafe content", "pkcs12/bad-authsafe");
|
|
353
366
|
var dispatched = _dispatchSafes(_walkAuthenticatedSafe(macedBytes, state, ctx), state, ctx);
|
|
354
367
|
return {
|
|
355
368
|
version: version, integrityMode: macPresent ? "password" : "none", mac: mac, macedBytes: macedBytes,
|
|
@@ -357,19 +370,19 @@ var PFX = schema.seq([
|
|
|
357
370
|
};
|
|
358
371
|
}
|
|
359
372
|
if (authSafe.contentType === OID_SIGNED_DATA) {
|
|
360
|
-
//
|
|
361
|
-
if (macPresent) throw ctx.E("pkcs12/bad-integrity-mode", "an id-signedData authSafe (public-key integrity) must omit macData (RFC 7292
|
|
373
|
+
// sec. 4 integrity coherence: public-key integrity has no MacData.
|
|
374
|
+
if (macPresent) throw ctx.E("pkcs12/bad-integrity-mode", "an id-signedData authSafe (public-key integrity) must omit macData (RFC 7292 sec. 4)");
|
|
362
375
|
var signed = cms.walkSignedData(authSafe.innerNode);
|
|
363
376
|
if (signed.encapContentInfo.eContentType !== OID_DATA) {
|
|
364
|
-
throw ctx.E("pkcs12/bad-authsafe", "a public-key-integrity authSafe must encapsulate id-data carrying the AuthenticatedSafe (RFC 7292
|
|
377
|
+
throw ctx.E("pkcs12/bad-authsafe", "a public-key-integrity authSafe must encapsulate id-data carrying the AuthenticatedSafe (RFC 7292 sec. 4.1)");
|
|
365
378
|
}
|
|
366
379
|
if (signed.encapContentInfo.eContent === null) {
|
|
367
|
-
throw ctx.E("pkcs12/bad-authsafe", "a public-key-integrity authSafe must carry attached eContent (RFC 7292
|
|
380
|
+
throw ctx.E("pkcs12/bad-authsafe", "a public-key-integrity authSafe must carry attached eContent (RFC 7292 sec. 4.1)");
|
|
368
381
|
}
|
|
369
|
-
// A signer-less SignedData carries no integrity at all
|
|
382
|
+
// A signer-less SignedData carries no integrity at all -- the one thing
|
|
370
383
|
// public-key-integrity mode exists to provide.
|
|
371
384
|
if (signed.signerInfos.length < 1) {
|
|
372
|
-
throw ctx.E("pkcs12/bad-authsafe", "a public-key-integrity authSafe must carry at least one SignerInfo (RFC 7292
|
|
385
|
+
throw ctx.E("pkcs12/bad-authsafe", "a public-key-integrity authSafe must carry at least one SignerInfo (RFC 7292 sec. 4)");
|
|
373
386
|
}
|
|
374
387
|
var dispatchedSigned = _dispatchSafes(_walkAuthenticatedSafe(signed.encapContentInfo.eContent, state, ctx), state, ctx);
|
|
375
388
|
return {
|
|
@@ -377,7 +390,7 @@ var PFX = schema.seq([
|
|
|
377
390
|
authSafeSigned: signed, safeBags: dispatchedSigned.safeBags, encryptedSafes: dispatchedSigned.encryptedSafes,
|
|
378
391
|
};
|
|
379
392
|
}
|
|
380
|
-
throw ctx.E("pkcs12/bad-authsafe-type", "authSafe contentType must be id-data or id-signedData (RFC 7292
|
|
393
|
+
throw ctx.E("pkcs12/bad-authsafe-type", "authSafe contentType must be id-data or id-signedData (RFC 7292 sec. 4), got " +
|
|
381
394
|
(ctx.oid.name(authSafe.contentType) || authSafe.contentType));
|
|
382
395
|
},
|
|
383
396
|
});
|
|
@@ -391,19 +404,19 @@ function _octetContent(node, ctx, what, code) {
|
|
|
391
404
|
return node.content;
|
|
392
405
|
}
|
|
393
406
|
|
|
394
|
-
// RFC 7292
|
|
407
|
+
// RFC 7292 sec. 4.1 -- a privacy-mode safe wraps a SafeContents, so the encrypted
|
|
395
408
|
// content's declared type must be id-data whichever CMS privacy structure
|
|
396
409
|
// carries it, and the ciphertext must be attached: CMS permits a detached
|
|
397
|
-
// EncryptedContentInfo, but here the ciphertext IS the safe's contents
|
|
410
|
+
// EncryptedContentInfo, but here the ciphertext IS the safe's contents -- a
|
|
398
411
|
// safe without it holds nothing a passphrase could ever recover.
|
|
399
412
|
function _privacySafe(content, ctx) {
|
|
400
413
|
if (content.encryptedContentInfo.contentType !== OID_DATA) {
|
|
401
414
|
throw ctx.E("pkcs12/bad-safe-contentinfo-type",
|
|
402
|
-
"an encrypted safe must declare id-data (SafeContents) as its encrypted content type (RFC 7292
|
|
415
|
+
"an encrypted safe must declare id-data (SafeContents) as its encrypted content type (RFC 7292 sec. 4.1)");
|
|
403
416
|
}
|
|
404
|
-
if (content.encryptedContentInfo.encryptedContent === null) {
|
|
417
|
+
if (content.encryptedContentInfo.encryptedContent === null || content.encryptedContentInfo.encryptedContent.length === 0) {
|
|
405
418
|
throw ctx.E("pkcs12/bad-safe-contentinfo",
|
|
406
|
-
"an encrypted safe must carry attached ciphertext
|
|
419
|
+
"an encrypted safe must carry non-empty attached ciphertext -- its encryptedContent is the SafeContents (RFC 7292 sec. 4.1)");
|
|
407
420
|
}
|
|
408
421
|
return content;
|
|
409
422
|
}
|
|
@@ -413,7 +426,7 @@ function _privacySafe(content, ctx) {
|
|
|
413
426
|
function _bmpValue(bytes, ctx) {
|
|
414
427
|
var node = asn1.decode(bytes, { ber: true });
|
|
415
428
|
if (!(node.tagClass === "universal" && node.tagNumber === TAGS.BMP_STRING)) {
|
|
416
|
-
throw ctx.E("pkcs12/bad-friendly-name", "a friendlyName value must be a BMPString (RFC 7292
|
|
429
|
+
throw ctx.E("pkcs12/bad-friendly-name", "a friendlyName value must be a BMPString (RFC 7292 sec. 4.2)");
|
|
417
430
|
}
|
|
418
431
|
return asn1.read.string(node);
|
|
419
432
|
}
|
|
@@ -426,17 +439,17 @@ function _walkAuthenticatedSafe(bytes, state, ctx) {
|
|
|
426
439
|
});
|
|
427
440
|
}
|
|
428
441
|
|
|
429
|
-
// Dispatch each AuthenticatedSafe element by contentType (
|
|
442
|
+
// Dispatch each AuthenticatedSafe element by contentType (sec. 4.1): id-data
|
|
430
443
|
// carries plaintext SafeContents (re-decoded here); the two privacy modes are
|
|
431
444
|
// full CMS structures validated by the CMS module on the already-decoded node
|
|
432
|
-
//
|
|
445
|
+
// -- ciphertext stays opaque inside the CMS surface.
|
|
433
446
|
function _dispatchSafes(asMatch, state, ctx) {
|
|
434
447
|
var safeBags = [];
|
|
435
448
|
var encryptedSafes = [];
|
|
436
449
|
for (var i = 0; i < asMatch.items.length; i++) {
|
|
437
450
|
var el = asMatch.items[i].value.result;
|
|
438
451
|
if (el.contentType === OID_DATA) {
|
|
439
|
-
var contents = schema.embeddedDer(SAFE_CONTENTS, _octetContent(el.innerNode, ctx, "an id-data safe content"), ctx, {
|
|
452
|
+
var contents = schema.embeddedDer(SAFE_CONTENTS, _octetContent(el.innerNode, ctx, "an id-data safe content", "pkcs12/bad-safe-contentinfo"), ctx, {
|
|
440
453
|
code: "pkcs12/bad-der", what: "a SafeContents", ber: true,
|
|
441
454
|
budget: state.budget, budgetCode: "pkcs12/too-deep",
|
|
442
455
|
});
|
|
@@ -448,14 +461,14 @@ function _dispatchSafes(asMatch, state, ctx) {
|
|
|
448
461
|
} else if (el.contentType === OID_ENVELOPED_DATA) {
|
|
449
462
|
encryptedSafes.push({ type: "envelopedData", content: _privacySafe(cms.walkEnvelopedData(el.innerNode), ctx) });
|
|
450
463
|
} else {
|
|
451
|
-
throw ctx.E("pkcs12/bad-safe-contentinfo-type", "an AuthenticatedSafe element must be id-data, id-encryptedData, or id-envelopedData (RFC 7292
|
|
464
|
+
throw ctx.E("pkcs12/bad-safe-contentinfo-type", "an AuthenticatedSafe element must be id-data, id-encryptedData, or id-envelopedData (RFC 7292 sec. 4.1), got " +
|
|
452
465
|
(ctx.oid.name(el.contentType) || el.contentType));
|
|
453
466
|
}
|
|
454
467
|
}
|
|
455
468
|
return { safeBags: safeBags, encryptedSafes: encryptedSafes };
|
|
456
469
|
}
|
|
457
470
|
|
|
458
|
-
// bagId dispatch (
|
|
471
|
+
// bagId dispatch (sec. 4.2, Appendix D). The bag-type set is closed: an
|
|
459
472
|
// unrecognized bagId rejects. Key material delegates to the exported PKCS#8
|
|
460
473
|
// parsers; cert / CRL / secret values are surfaced raw.
|
|
461
474
|
function _buildBag(rec, state, ctx) {
|
|
@@ -463,7 +476,7 @@ function _buildBag(rec, state, ctx) {
|
|
|
463
476
|
type: null, bagId: rec.bagId,
|
|
464
477
|
friendlyName: rec.friendlyName, localKeyId: rec.localKeyId, attributes: rec.attributes,
|
|
465
478
|
};
|
|
466
|
-
// Key bags are walked from the DECODED node
|
|
479
|
+
// Key bags are walked from the DECODED node -- their wire bytes may carry
|
|
467
480
|
// BER shapes the strict pkcs8 parse entry refuses, and the node already
|
|
468
481
|
// exists (no re-decode).
|
|
469
482
|
if (rec.bagId === OID_KEY_BAG) {
|
|
@@ -495,7 +508,7 @@ function _buildBag(rec, state, ctx) {
|
|
|
495
508
|
return bag;
|
|
496
509
|
}
|
|
497
510
|
if (rec.bagId === OID_SAFE_CONTENTS_BAG) {
|
|
498
|
-
//
|
|
511
|
+
// sec. 4.2.6 recursion -- bounded independently of the codec depth cap so a
|
|
499
512
|
// chain across re-decode boundaries cannot restart it.
|
|
500
513
|
if (state.bagDepth + 1 > C.LIMITS.PKCS12_MAX_BAG_DEPTH) {
|
|
501
514
|
throw ctx.E("pkcs12/too-deep", "safeContentsBag nesting exceeds the depth cap " + C.LIMITS.PKCS12_MAX_BAG_DEPTH);
|
|
@@ -509,7 +522,7 @@ function _buildBag(rec, state, ctx) {
|
|
|
509
522
|
}
|
|
510
523
|
return bag;
|
|
511
524
|
}
|
|
512
|
-
throw ctx.E("pkcs12/bad-bag-type", (ctx.oid.name(rec.bagId) || rec.bagId) + " is not a recognized SafeBag bagId (RFC 7292
|
|
525
|
+
throw ctx.E("pkcs12/bad-bag-type", (ctx.oid.name(rec.bagId) || rec.bagId) + " is not a recognized SafeBag bagId (RFC 7292 sec. 4.2)");
|
|
513
526
|
}
|
|
514
527
|
|
|
515
528
|
/**
|
|
@@ -524,29 +537,32 @@ function _buildBag(rec, state, ctx) {
|
|
|
524
537
|
* Parse a DER / BER `Buffer` or a PEM string into a structured PFX:
|
|
525
538
|
* `{ version, integrityMode, mac, macedBytes, authSafeSigned, safeBags,
|
|
526
539
|
* encryptedSafes }`. `integrityMode` is `"password"` (id-data authSafe with
|
|
527
|
-
* MacData
|
|
540
|
+
* MacData -- `mac` carries `{ kind, hashOid, hashName, hashParameters,
|
|
528
541
|
* pbmac1, macValue, macSalt, iterations }`, where `kind` distinguishes the
|
|
529
542
|
* RFC 9579 PBMAC1 arm; for PBMAC1 the required parameters are validated and
|
|
530
543
|
* decoded onto `pbmac1` (`{ kdf: { salt, iterationCount, keyLength, prfOid,
|
|
531
544
|
* prfName }, schemeOid, schemeName }`) with `hashParameters` keeping the raw
|
|
532
545
|
* bytes, and `macedBytes` is the exact byte range the HMAC covers),
|
|
533
|
-
* `"none"` (id-data authSafe without MacData
|
|
546
|
+
* `"none"` (id-data authSafe without MacData -- the shape
|
|
534
547
|
* `openssl pkcs12 -export -nomac` emits; `mac` is `null` and the store
|
|
535
548
|
* carries no integrity protection, a policy decision left to the caller),
|
|
536
|
-
* or `"public-key"` (id-signedData authSafe
|
|
549
|
+
* or `"public-key"` (id-signedData authSafe -- the CMS SignedData surfaced
|
|
537
550
|
* on `authSafeSigned`, signature not verified here). Each of `safeBags` is
|
|
538
551
|
* `{ type, bagId, friendlyName, localKeyId, attributes }` plus its arm:
|
|
539
552
|
* a `keyBag` carries `key` (the PKCS#8 parse), a `pkcs8ShroudedKeyBag`
|
|
540
553
|
* carries `encrypted` (algorithm surfaced, ciphertext opaque), `certBag` /
|
|
541
554
|
* `crlBag` / `secretBag` carry their values raw and byte-exact, and a
|
|
542
555
|
* `safeContentsBag` carries `nested` bags. Encrypted / enveloped safes are
|
|
543
|
-
* surfaced structurally on `encryptedSafes` via the CMS module
|
|
556
|
+
* surfaced structurally on `encryptedSafes` via the CMS module -- recipient
|
|
544
557
|
* infos and algorithms decoded, ciphertext never parsed. MAC verification
|
|
545
558
|
* and bag decryption are passphrase operations for an external layer; the
|
|
546
559
|
* inputs are surfaced exactly.
|
|
547
560
|
*
|
|
548
561
|
* Throws `Pkcs12Error` when the bytes are not a well-formed PFX, and
|
|
549
|
-
* `Asn1Error` when the underlying encoding is malformed.
|
|
562
|
+
* `Asn1Error` when the underlying encoding is malformed. Faults inside a
|
|
563
|
+
* delegated CMS structure (a signedData authSafe, an encrypted / enveloped
|
|
564
|
+
* safe) surface as `CmsError` (`cms/*`) and inside a key bag as `Pkcs8Error`
|
|
565
|
+
* (`pkcs8/*`) -- all `PkiError` subclasses.
|
|
550
566
|
*
|
|
551
567
|
* @example
|
|
552
568
|
* var store = pki.schema.pkcs12.parse(der);
|
|
@@ -566,7 +582,7 @@ var parse = pkix.makeParser({
|
|
|
566
582
|
* @related pki.schema.pkcs12.parse
|
|
567
583
|
*
|
|
568
584
|
* Extract the DER bytes from a PEM block (default label `PKCS12`). A
|
|
569
|
-
* `.p12` / `.pfx` file is almost always binary
|
|
585
|
+
* `.p12` / `.pfx` file is almost always binary -- the PEM path is a
|
|
570
586
|
* convenience for stores that transit text channels.
|
|
571
587
|
*
|
|
572
588
|
* @example
|
|
@@ -590,8 +606,8 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || "PKCS12",
|
|
|
590
606
|
function pemEncode(der, label) { return pkix.pemEncode(der, label || "PKCS12", PemError); }
|
|
591
607
|
|
|
592
608
|
// A PFX root leads with a universal INTEGER (version), colliding with the
|
|
593
|
-
// PKCS#8 detector on the first child
|
|
594
|
-
// (a ContentInfo: SEQUENCE of exactly 2, OID first, [0] constructed second
|
|
609
|
+
// PKCS#8 detector on the first child -- so the discriminators are children[1]
|
|
610
|
+
// (a ContentInfo: SEQUENCE of exactly 2, OID first, [0] constructed second --
|
|
595
611
|
// a shape a PrivateKeyInfo's AlgorithmIdentifier never presents) and
|
|
596
612
|
// children[2] (a SEQUENCE MacData or absent, never PKCS#8's OCTET STRING).
|
|
597
613
|
function matches(root) {
|
package/lib/schema-pkcs8.js
CHANGED
|
@@ -9,25 +9,25 @@
|
|
|
9
9
|
* @slug pkcs8
|
|
10
10
|
*
|
|
11
11
|
* @intro
|
|
12
|
-
* PKCS#8 private-key handling per RFC 5208
|
|
12
|
+
* PKCS#8 private-key handling per RFC 5208 sec. 5 (PrivateKeyInfo) and RFC 5958 sec. 2
|
|
13
13
|
* (OneAsymmetricKey). `parse` turns a DER or PEM (`PRIVATE KEY`) key into a
|
|
14
14
|
* structured object: version, the private-key algorithm identifier, the raw
|
|
15
|
-
* private-key bytes, the optional attributes, and
|
|
15
|
+
* private-key bytes, the optional attributes, and -- for a v2 OneAsymmetricKey --
|
|
16
16
|
* the optional public key. It composes the same schema engine and shared PKIX
|
|
17
17
|
* sub-schemas (AlgorithmIdentifier, Attribute) the other parsers use.
|
|
18
18
|
*
|
|
19
19
|
* A PKCS#8 key is a container, not a signed structure: it has no signature, no
|
|
20
20
|
* distinguished name, and no to-be-signed region. The private-key OCTET STRING
|
|
21
|
-
* content is kept raw
|
|
21
|
+
* content is kept raw -- the algorithm-specific inner key (an RSAPrivateKey, an
|
|
22
22
|
* ECPrivateKey, a CurvePrivateKey) is decoded by the caller using the surfaced
|
|
23
23
|
* algorithm OID, so an unknown or future key type never fails the parse. An
|
|
24
|
-
* `ENCRYPTED PRIVATE KEY` (EncryptedPrivateKeyInfo, RFC 5958
|
|
24
|
+
* `ENCRYPTED PRIVATE KEY` (EncryptedPrivateKeyInfo, RFC 5958 sec. 3) is recognized
|
|
25
25
|
* and surfaced with its encryption algorithm and raw ciphertext; decrypting it
|
|
26
26
|
* needs a passphrase and is out of scope for structural parsing.
|
|
27
27
|
*
|
|
28
28
|
* @card
|
|
29
29
|
* Parse DER / PEM PKCS#8 private keys (RFC 5208 / 5958) into structured,
|
|
30
|
-
* validated fields
|
|
30
|
+
* validated fields -- algorithm, raw key bytes, attributes, optional public key,
|
|
31
31
|
* fail-closed. Encrypted keys are recognized, not decrypted.
|
|
32
32
|
*/
|
|
33
33
|
|
|
@@ -46,7 +46,7 @@ var NS = pkix.makeNS("pkcs8", Pkcs8Error, oid);
|
|
|
46
46
|
var ALGORITHM_IDENTIFIER = pkix.algorithmIdentifier(NS);
|
|
47
47
|
var ATTRIBUTE = pkix.attribute(NS);
|
|
48
48
|
|
|
49
|
-
// version ::= INTEGER { v1(0), v2(1) }
|
|
49
|
+
// version ::= INTEGER { v1(0), v2(1) } -- 0 and 1 are both LEGAL (the divergence
|
|
50
50
|
// from every other reader; cert/CRL reject 0, CSR accepts only 0). Surface as the
|
|
51
51
|
// RFC's vN number.
|
|
52
52
|
var PKCS8_VERSION = pkix.versionReader(NS, { "0": 1, "1": 2 });
|
|
@@ -69,7 +69,7 @@ var PRIVATE_KEY_INFO = schema.seq([
|
|
|
69
69
|
build: function (m) {
|
|
70
70
|
var version = m.fields.version.value; // 1 (v1) | 2 (v2)
|
|
71
71
|
var hasPublicKey = m.fields.publicKey.present;
|
|
72
|
-
// RFC 5958
|
|
72
|
+
// RFC 5958 sec. 2 -- publicKey present <=> version is v2. Enforce both directions.
|
|
73
73
|
if (hasPublicKey && version !== 2) throw NS.E("pkcs8/bad-version", "a [1] publicKey is permitted only in a v2 OneAsymmetricKey");
|
|
74
74
|
if (!hasPublicKey && version === 2) throw NS.E("pkcs8/bad-version", "a v2 OneAsymmetricKey must carry a [1] publicKey");
|
|
75
75
|
return {
|
|
@@ -83,7 +83,7 @@ var PRIVATE_KEY_INFO = schema.seq([
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
// EncryptedPrivateKeyInfo ::= SEQUENCE { encryptionAlgorithm AlgorithmIdentifier,
|
|
86
|
-
// encryptedData OCTET STRING } (RFC 5958
|
|
86
|
+
// encryptedData OCTET STRING } (RFC 5958 sec. 3). Recognized and surfaced; the raw
|
|
87
87
|
// ciphertext is kept for a later decryption layer (PBES2/PBKDF2 + a passphrase).
|
|
88
88
|
var ENCRYPTED_PRIVATE_KEY_INFO = schema.seq([
|
|
89
89
|
schema.field("encryptionAlgorithm", ALGORITHM_IDENTIFIER),
|
|
@@ -172,7 +172,7 @@ function pemDecode(text, label) { return pkix.pemDecode(text, label || "PRIVATE
|
|
|
172
172
|
function pemEncode(der, label) { return pkix.pemEncode(der, label || "PRIVATE KEY", PemError); }
|
|
173
173
|
|
|
174
174
|
// A PKCS#8 PrivateKeyInfo root is a SEQUENCE whose first child is a universal
|
|
175
|
-
// INTEGER (version) and third child a universal OCTET STRING (privateKey)
|
|
175
|
+
// INTEGER (version) and third child a universal OCTET STRING (privateKey) -- a
|
|
176
176
|
// shape the signed-envelope trio (whose children[0] is a SEQUENCE and children[2]
|
|
177
177
|
// a BIT STRING) can never present, so the detectors are mutually exclusive
|
|
178
178
|
// regardless of registry order. Trailing context fields are [0]/[1] in ascending
|
|
@@ -193,16 +193,16 @@ function matches(root) {
|
|
|
193
193
|
}
|
|
194
194
|
|
|
195
195
|
// NOTE: there is intentionally no `matchesEncrypted` detector. An
|
|
196
|
-
// EncryptedPrivateKeyInfo is a SEQUENCE { SEQUENCE, OCTET STRING }
|
|
196
|
+
// EncryptedPrivateKeyInfo is a SEQUENCE { SEQUENCE, OCTET STRING } -- a shape it
|
|
197
197
|
// shares with a PKCS#1 DigestInfo and other AlgorithmIdentifier-plus-octets
|
|
198
|
-
// structures
|
|
198
|
+
// structures -- so it cannot be classified from structure alone. The orchestrator
|
|
199
199
|
// does not auto-route it; an operator who knows a key is encrypted (e.g. from an
|
|
200
200
|
// 'ENCRYPTED PRIVATE KEY' PEM label) calls parseEncrypted directly. A structural
|
|
201
201
|
// auto-route can return once a validated encryption-algorithm discriminator (the
|
|
202
202
|
// PBES layer) exists.
|
|
203
203
|
|
|
204
204
|
// Validate a bare, already-decoded PrivateKeyInfo / EncryptedPrivateKeyInfo
|
|
205
|
-
// node
|
|
205
|
+
// node -- for a composer that holds a decoded node and must not re-parse its
|
|
206
206
|
// bytes (an RFC 7292 PFX key bag, whose wire encoding may be BER that the
|
|
207
207
|
// strict `parse` entry would refuse). Same contract as the CMS walkers: the
|
|
208
208
|
// node is the bare structure, typed pkcs8/* on rejection.
|