@blamejs/pki 0.1.17 → 0.1.18
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 +23 -1
- package/README.md +8 -7
- package/lib/asn1-der.js +109 -22
- package/lib/constants.js +16 -0
- package/lib/framework-error.js +7 -0
- package/lib/oid.js +33 -2
- package/lib/path-validate.js +10 -2
- package/lib/schema-all.js +26 -7
- package/lib/schema-cms.js +10 -0
- package/lib/schema-crmf.js +1 -1
- package/lib/schema-engine.js +69 -5
- package/lib/schema-ocsp.js +2 -4
- package/lib/schema-pkcs12.js +615 -0
- package/lib/schema-pkcs8.js +10 -0
- package/lib/schema-pkix.js +13 -3
- package/lib/schema-tsp.js +1 -1
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
|
@@ -0,0 +1,615 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// Copyright (c) blamejs contributors
|
|
3
|
+
"use strict";
|
|
4
|
+
/**
|
|
5
|
+
* @module pki.schema.pkcs12
|
|
6
|
+
* @nav Schema
|
|
7
|
+
* @title PKCS#12
|
|
8
|
+
* @order 190
|
|
9
|
+
* @slug pkcs12
|
|
10
|
+
*
|
|
11
|
+
* @intro
|
|
12
|
+
* PKCS#12 (PFX) key-and-certificate store handling per RFC 7292. `parse`
|
|
13
|
+
* decodes a `.p12` / `.pfx` container — the personal-information-exchange
|
|
14
|
+
* format OpenSSL, Windows CAPI, macOS Keychain, and NSS emit — into its
|
|
15
|
+
* bags: private keys (delegated to the PKCS#8 parser), shrouded keys
|
|
16
|
+
* (algorithm surfaced, ciphertext kept opaque), certificates, CRLs,
|
|
17
|
+
* secrets, and nested safe contents, each with its `friendlyName` /
|
|
18
|
+
* `localKeyId` attributes decoded.
|
|
19
|
+
*
|
|
20
|
+
* A PFX is a nested container: the authenticated safe is a CMS ContentInfo
|
|
21
|
+
* whose OCTET STRING content re-decodes to a list of ContentInfos, each
|
|
22
|
+
* carrying plaintext safe contents or a CMS EncryptedData / EnvelopedData
|
|
23
|
+
* privacy wrapper (surfaced structurally via the CMS module; ciphertext is
|
|
24
|
+
* never parsed). Integrity is surfaced, not verified: password mode yields
|
|
25
|
+
* the MAC parameters plus the exact byte range the HMAC covers
|
|
26
|
+
* (`macedBytes`), public-key mode the SignedData with its signers, and a
|
|
27
|
+
* MAC-less store surfaces `integrityMode: "none"` for the caller's policy
|
|
28
|
+
* to judge. RFC 7292
|
|
29
|
+
* §4.1 encodes content in BER, so parsing this one format accepts exactly
|
|
30
|
+
* two BER shapes — indefinite lengths and constructed OCTET STRINGs —
|
|
31
|
+
* anywhere in the store; every other strictness verdict, and every other
|
|
32
|
+
* format, stays strict DER, fail-closed.
|
|
33
|
+
*
|
|
34
|
+
* @card
|
|
35
|
+
* Parse DER / BER / PEM RFC 7292 PKCS#12 (PFX) stores into key / cert /
|
|
36
|
+
* CRL / secret bags with their attributes — keys via the PKCS#8 parser,
|
|
37
|
+
* encrypted safes via CMS, MAC inputs surfaced raw for external
|
|
38
|
+
* verification, fail-closed.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
var C = require("./constants.js");
|
|
42
|
+
var asn1 = require("./asn1-der.js");
|
|
43
|
+
var oid = require("./oid.js");
|
|
44
|
+
var schema = require("./schema-engine.js");
|
|
45
|
+
var pkix = require("./schema-pkix.js");
|
|
46
|
+
var cms = require("./schema-cms.js");
|
|
47
|
+
var pkcs8 = require("./schema-pkcs8.js");
|
|
48
|
+
var frameworkError = require("./framework-error.js");
|
|
49
|
+
|
|
50
|
+
var Pkcs12Error = frameworkError.Pkcs12Error;
|
|
51
|
+
var PemError = frameworkError.PemError;
|
|
52
|
+
var NS = pkix.makeNS("pkcs12", Pkcs12Error, oid);
|
|
53
|
+
var TAGS = asn1.TAGS;
|
|
54
|
+
|
|
55
|
+
// Content types (RFC 5652 §4 / RFC 7292 §4.1).
|
|
56
|
+
var OID_DATA = oid.byName("data");
|
|
57
|
+
var OID_SIGNED_DATA = oid.byName("signedData");
|
|
58
|
+
var OID_ENVELOPED_DATA = oid.byName("envelopedData");
|
|
59
|
+
var OID_ENCRYPTED_DATA = oid.byName("encryptedData");
|
|
60
|
+
// Bag types (RFC 7292 §4.2).
|
|
61
|
+
var OID_KEY_BAG = oid.byName("keyBag");
|
|
62
|
+
var OID_SHROUDED_KEY_BAG = oid.byName("pkcs8ShroudedKeyBag");
|
|
63
|
+
var OID_CERT_BAG = oid.byName("certBag");
|
|
64
|
+
var OID_CRL_BAG = oid.byName("crlBag");
|
|
65
|
+
var OID_SECRET_BAG = oid.byName("secretBag");
|
|
66
|
+
var OID_SAFE_CONTENTS_BAG = oid.byName("safeContentsBag");
|
|
67
|
+
// CertBag / CRLBag discriminators (§4.2.3-§4.2.4).
|
|
68
|
+
var OID_X509_CERTIFICATE = oid.byName("x509Certificate");
|
|
69
|
+
var OID_SDSI_CERTIFICATE = oid.byName("sdsiCertificate");
|
|
70
|
+
var OID_X509_CRL = oid.byName("x509CRL");
|
|
71
|
+
// Bag attributes (§4.2) + the RFC 9579 MacData arm.
|
|
72
|
+
var OID_FRIENDLY_NAME = oid.byName("friendlyName");
|
|
73
|
+
var OID_LOCAL_KEY_ID = oid.byName("localKeyId");
|
|
74
|
+
var OID_PBMAC1 = oid.byName("pbmac1");
|
|
75
|
+
|
|
76
|
+
// DigestInfo ::= SEQUENCE { digestAlgorithm AlgorithmIdentifier, digest OCTET STRING }.
|
|
77
|
+
var DIGEST_INFO = schema.seq([
|
|
78
|
+
schema.field("digestAlgorithm", pkix.algorithmIdentifier(NS)),
|
|
79
|
+
schema.field("digest", schema.octetString()),
|
|
80
|
+
], {
|
|
81
|
+
assert: "sequence", arity: { exact: 2 }, code: "pkcs12/bad-mac-data", what: "DigestInfo",
|
|
82
|
+
build: function (m) {
|
|
83
|
+
return { digestAlgorithm: m.fields.digestAlgorithm.value.result, digest: m.fields.digest.value };
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
// PBKDF2-params (RFC 8018 §5.2), constrained to the RFC 9579 PBMAC1 profile:
|
|
88
|
+
// the salt MUST use the specified (OCTET STRING) choice and keyLength MUST be
|
|
89
|
+
// present (§4.b / §5 — a MacData consumer cannot infer the MAC key size).
|
|
90
|
+
var PBKDF2_PARAMS = schema.seq([
|
|
91
|
+
schema.field("salt", schema.octetString()),
|
|
92
|
+
schema.field("iterationCount", schema.integerLeaf()),
|
|
93
|
+
schema.optional("keyLength", schema.integerLeaf(), { whenUniversal: [TAGS.INTEGER] }),
|
|
94
|
+
schema.optional("prf", pkix.algorithmIdentifier(NS), { whenUniversal: [TAGS.SEQUENCE] }),
|
|
95
|
+
], {
|
|
96
|
+
assert: "sequence", code: "pkcs12/bad-mac-data", what: "PBKDF2-params",
|
|
97
|
+
build: function (m, ctx) {
|
|
98
|
+
if (!m.fields.keyLength.present) {
|
|
99
|
+
throw ctx.E("pkcs12/bad-mac-data", "PBMAC1 PBKDF2-params must carry keyLength (RFC 9579 §5)");
|
|
100
|
+
}
|
|
101
|
+
// Both counters surface as exact JS numbers — a value past the bound
|
|
102
|
+
// would round silently on conversion and hand a verifier wrong inputs.
|
|
103
|
+
var iterations = m.fields.iterationCount.value;
|
|
104
|
+
if (iterations < 1n || iterations > 2147483647n) {
|
|
105
|
+
throw ctx.E("pkcs12/bad-mac-data", "PBKDF2 iterationCount must be a positive integer within the iteration-count range");
|
|
106
|
+
}
|
|
107
|
+
var keyLength = m.fields.keyLength.value;
|
|
108
|
+
if (keyLength < 1n || keyLength > 2147483647n) {
|
|
109
|
+
throw ctx.E("pkcs12/bad-mac-data", "PBKDF2 keyLength must be a positive integer within the key-length range");
|
|
110
|
+
}
|
|
111
|
+
var prf = m.fields.prf.present ? m.fields.prf.value.result : null;
|
|
112
|
+
return {
|
|
113
|
+
salt: m.fields.salt.value,
|
|
114
|
+
iterationCount: Number(iterations),
|
|
115
|
+
keyLength: Number(keyLength),
|
|
116
|
+
prfOid: prf ? prf.oid : oid.byName("hmacWithSHA1"),
|
|
117
|
+
prfName: prf ? prf.name : "hmacWithSHA1",
|
|
118
|
+
};
|
|
119
|
+
},
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
// PBMAC1-params ::= SEQUENCE { keyDerivationFunc AlgorithmIdentifier{PBKDF2},
|
|
123
|
+
// messageAuthScheme AlgorithmIdentifier } (RFC 8018 §A.5 / RFC 9579 §4).
|
|
124
|
+
var PBMAC1_PARAMS = schema.seq([
|
|
125
|
+
schema.field("keyDerivationFunc", schema.seq([
|
|
126
|
+
schema.field("algorithm", schema.oidLeaf()),
|
|
127
|
+
schema.field("parameters", PBKDF2_PARAMS),
|
|
128
|
+
], { assert: "sequence", arity: { exact: 2 }, code: "pkcs12/bad-mac-data", what: "PBMAC1 keyDerivationFunc" })),
|
|
129
|
+
schema.field("messageAuthScheme", pkix.algorithmIdentifier(NS)),
|
|
130
|
+
], {
|
|
131
|
+
assert: "sequence", arity: { exact: 2 }, code: "pkcs12/bad-mac-data", what: "PBMAC1-params",
|
|
132
|
+
build: function (m, ctx) {
|
|
133
|
+
var kdf = m.fields.keyDerivationFunc.value;
|
|
134
|
+
if (kdf.fields.algorithm.value !== oid.byName("pbkdf2")) {
|
|
135
|
+
throw ctx.E("pkcs12/bad-mac-data", "PBMAC1 keyDerivationFunc must be PBKDF2 (RFC 9579 §4)");
|
|
136
|
+
}
|
|
137
|
+
var scheme = m.fields.messageAuthScheme.value.result;
|
|
138
|
+
return {
|
|
139
|
+
kdf: kdf.fields.parameters.value.result,
|
|
140
|
+
schemeOid: scheme.oid,
|
|
141
|
+
schemeName: scheme.name,
|
|
142
|
+
};
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// MacData ::= SEQUENCE { mac DigestInfo, macSalt OCTET STRING,
|
|
147
|
+
// iterations INTEGER DEFAULT 1 }.
|
|
148
|
+
var MAC_DATA = schema.seq([
|
|
149
|
+
schema.field("mac", DIGEST_INFO),
|
|
150
|
+
schema.field("macSalt", schema.octetString()),
|
|
151
|
+
schema.optional("iterations", schema.integerLeaf(), { whenUniversal: [TAGS.INTEGER], default: 1 }),
|
|
152
|
+
], {
|
|
153
|
+
assert: "sequence", code: "pkcs12/bad-mac-data", what: "MacData",
|
|
154
|
+
build: function (m, ctx) {
|
|
155
|
+
// X.690 §11.5 — a DEFAULT-valued component must be omitted from a DER
|
|
156
|
+
// encoding, so an explicitly-encoded iterations = 1 is non-canonical.
|
|
157
|
+
var it = m.fields.iterations;
|
|
158
|
+
var iterations = 1;
|
|
159
|
+
if (it.present) {
|
|
160
|
+
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 §11.5)");
|
|
162
|
+
if (v < 1n || v > 2147483647n) throw ctx.E("pkcs12/bad-mac-iterations", "iterations must be a positive integer within the iteration-count range");
|
|
163
|
+
iterations = Number(v);
|
|
164
|
+
}
|
|
165
|
+
var di = m.fields.mac.value.result;
|
|
166
|
+
var pbmac1 = null;
|
|
167
|
+
if (di.digestAlgorithm.oid === OID_PBMAC1) {
|
|
168
|
+
// RFC 9579 §4 — the PBMAC1 algorithm identifier MUST carry PBMAC1-params
|
|
169
|
+
// (the KDF and MAC scheme a verifier needs live there, not in MacData).
|
|
170
|
+
if (di.digestAlgorithm.parameters === null) {
|
|
171
|
+
throw ctx.E("pkcs12/bad-mac-data", "a PBMAC1 MacData must carry PBMAC1-params (RFC 9579 §4)");
|
|
172
|
+
}
|
|
173
|
+
pbmac1 = schema.embeddedDer(PBMAC1_PARAMS, di.digestAlgorithm.parameters, ctx,
|
|
174
|
+
{ code: "pkcs12/bad-mac-data", what: "PBMAC1-params", ber: true }).result;
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
kind: pbmac1 ? "pbmac1" : "hmac",
|
|
178
|
+
hashOid: di.digestAlgorithm.oid,
|
|
179
|
+
hashName: di.digestAlgorithm.name,
|
|
180
|
+
hashParameters: di.digestAlgorithm.parameters,
|
|
181
|
+
pbmac1: pbmac1,
|
|
182
|
+
macValue: di.digest,
|
|
183
|
+
macSalt: m.fields.macSalt.value,
|
|
184
|
+
iterations: iterations,
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// CertBag ::= SEQUENCE { certId OID, certValue [0] EXPLICIT ANY } (§4.2.3):
|
|
190
|
+
// x509Certificate wraps the DER certificate in an OCTET STRING; sdsiCertificate
|
|
191
|
+
// is an IA5String. Both arms of the closed set are accepted; the value is
|
|
192
|
+
// surfaced raw (byte-exact), never re-encoded or recursively parsed.
|
|
193
|
+
var CERT_BAG = schema.seq([
|
|
194
|
+
schema.field("certId", schema.oidLeaf()),
|
|
195
|
+
schema.field("certValue", schema.explicit(0, schema.any(), { code: "pkcs12/bad-bag-value", what: "certValue" })),
|
|
196
|
+
], {
|
|
197
|
+
assert: "sequence", arity: { exact: 2 }, code: "pkcs12/bad-bag-value", what: "CertBag",
|
|
198
|
+
build: function (m, ctx) {
|
|
199
|
+
var certId = m.fields.certId.value;
|
|
200
|
+
var node = m.fields.certValue.value;
|
|
201
|
+
if (certId === OID_X509_CERTIFICATE) {
|
|
202
|
+
return { certType: "x509Certificate", certId: certId, certValue: _octetContent(node, ctx, "an x509Certificate certValue") };
|
|
203
|
+
}
|
|
204
|
+
if (certId === OID_SDSI_CERTIFICATE) {
|
|
205
|
+
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 §4.2.3)");
|
|
207
|
+
}
|
|
208
|
+
return { certType: "sdsiCertificate", certId: certId, certValue: node.content };
|
|
209
|
+
}
|
|
210
|
+
throw ctx.E("pkcs12/bad-cert-type", (ctx.oid.name(certId) || certId) + " is not a recognized CertBag certId (RFC 7292 §4.2.3)");
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
// CRLBag ::= SEQUENCE { crlId OID, crlValue [0] EXPLICIT ANY } (§4.2.4).
|
|
215
|
+
var CRL_BAG = schema.seq([
|
|
216
|
+
schema.field("crlId", schema.oidLeaf()),
|
|
217
|
+
schema.field("crlValue", schema.explicit(0, schema.any(), { code: "pkcs12/bad-bag-value", what: "crlValue" })),
|
|
218
|
+
], {
|
|
219
|
+
assert: "sequence", arity: { exact: 2 }, code: "pkcs12/bad-bag-value", what: "CRLBag",
|
|
220
|
+
build: function (m, ctx) {
|
|
221
|
+
var crlId = m.fields.crlId.value;
|
|
222
|
+
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 §4.2.4)");
|
|
224
|
+
}
|
|
225
|
+
return { crlType: "x509CRL", crlId: crlId, crlValue: _octetContent(m.fields.crlValue.value, ctx, "an x509CRL crlValue") };
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
// SecretBag ::= SEQUENCE { secretTypeId OID, secretValue [0] EXPLICIT ANY }
|
|
230
|
+
// (§4.2.5). SecretTypes is an open set, so the type is surfaced by OID with
|
|
231
|
+
// the value raw — an unrecognized secret is representable, not a fault.
|
|
232
|
+
var SECRET_BAG = schema.seq([
|
|
233
|
+
schema.field("secretTypeId", schema.oidLeaf()),
|
|
234
|
+
schema.field("secretValue", schema.explicit(0, schema.any(), { code: "pkcs12/bad-bag-value", what: "secretValue" })),
|
|
235
|
+
], {
|
|
236
|
+
assert: "sequence", arity: { exact: 2 }, code: "pkcs12/bad-bag-value", what: "SecretBag",
|
|
237
|
+
build: function (m, ctx) {
|
|
238
|
+
var t = m.fields.secretTypeId.value;
|
|
239
|
+
return { secretTypeId: t, secretTypeName: ctx.oid.name(t) || null, secretValue: m.fields.secretValue.value.bytes };
|
|
240
|
+
},
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
// SafeBag ::= SEQUENCE { bagId OID, bagValue [0] EXPLICIT ANY DEFINED BY bagId,
|
|
244
|
+
// bagAttributes SET OF PKCS12Attribute OPTIONAL } (§4.2).
|
|
245
|
+
// The build surfaces the structural record (bagId + the inner value node +
|
|
246
|
+
// decoded attributes); the bagId dispatch happens in _buildBag, where the
|
|
247
|
+
// per-parse recursion state lives.
|
|
248
|
+
var SAFE_BAG = schema.seq([
|
|
249
|
+
schema.field("bagId", schema.oidLeaf()),
|
|
250
|
+
schema.field("bagValue", schema.explicit(0, schema.any(), { code: "pkcs12/bad-bag-value", what: "bagValue" })),
|
|
251
|
+
schema.optional("bagAttributes", schema.setOf(pkix.attribute(NS), {
|
|
252
|
+
code: "pkcs12/bad-attributes", what: "bagAttributes",
|
|
253
|
+
max: C.LIMITS.PKCS12_MAX_ELEMENTS, maxCode: "pkcs12/too-many-elements",
|
|
254
|
+
}), { whenUniversal: [TAGS.SET] }),
|
|
255
|
+
], {
|
|
256
|
+
assert: "sequence", code: "pkcs12/bad-safe-contents", what: "SafeBag",
|
|
257
|
+
build: function (m, ctx) {
|
|
258
|
+
var attributes = [];
|
|
259
|
+
var friendlyName = null;
|
|
260
|
+
var localKeyId = null;
|
|
261
|
+
var seen = {};
|
|
262
|
+
if (m.fields.bagAttributes.present) {
|
|
263
|
+
var items = m.fields.bagAttributes.value.items;
|
|
264
|
+
for (var i = 0; i < items.length; i++) {
|
|
265
|
+
var a = items[i].value.result;
|
|
266
|
+
attributes.push(a);
|
|
267
|
+
// friendlyName / localKeyId are SINGLE VALUE TRUE (PKCS#9 / RFC 2985):
|
|
268
|
+
// exactly one value, at most one instance of each attribute.
|
|
269
|
+
if (a.type === OID_FRIENDLY_NAME) {
|
|
270
|
+
if (seen[a.type] || a.values.length !== 1) throw ctx.E("pkcs12/bad-friendly-name", "friendlyName carries exactly one BMPString value (PKCS#9 SINGLE VALUE)");
|
|
271
|
+
friendlyName = _bmpValue(a.values[0], ctx);
|
|
272
|
+
}
|
|
273
|
+
if (a.type === OID_LOCAL_KEY_ID) {
|
|
274
|
+
if (seen[a.type] || a.values.length !== 1) throw ctx.E("pkcs12/bad-local-key-id", "localKeyId carries exactly one OCTET STRING value (PKCS#9 SINGLE VALUE)");
|
|
275
|
+
// The value bytes are a raw wire slice out of a store whose content
|
|
276
|
+
// is normatively BER, so the re-decode follows the same BER rules.
|
|
277
|
+
localKeyId = _octetContent(asn1.decode(a.values[0], { ber: true }), ctx, "a localKeyId value", "pkcs12/bad-local-key-id");
|
|
278
|
+
}
|
|
279
|
+
seen[a.type] = true;
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return {
|
|
283
|
+
bagId: m.fields.bagId.value,
|
|
284
|
+
valueNode: m.fields.bagValue.value,
|
|
285
|
+
attributes: attributes,
|
|
286
|
+
friendlyName: friendlyName,
|
|
287
|
+
localKeyId: localKeyId,
|
|
288
|
+
};
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// SafeContents ::= SEQUENCE OF SafeBag (§4.2). Strictly a SEQUENCE — a SET OF
|
|
293
|
+
// here is a known producer divergence and rejects.
|
|
294
|
+
var SAFE_CONTENTS = schema.seqOf(SAFE_BAG, {
|
|
295
|
+
code: "pkcs12/bad-safe-contents", what: "SafeContents",
|
|
296
|
+
max: C.LIMITS.PKCS12_MAX_ELEMENTS, maxCode: "pkcs12/too-many-elements",
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
// AuthenticatedSafe ::= SEQUENCE OF ContentInfo (§4.1) — each element parsed
|
|
300
|
+
// structurally here and dispatched by contentType in _dispatchSafes.
|
|
301
|
+
var AS_ELEMENT = schema.seq([
|
|
302
|
+
schema.field("contentType", schema.oidLeaf()),
|
|
303
|
+
schema.field("content", schema.explicit(0, schema.any(), { code: "pkcs12/bad-safe-contentinfo", what: "safe ContentInfo content" })),
|
|
304
|
+
], {
|
|
305
|
+
assert: "sequence", arity: { exact: 2 }, code: "pkcs12/bad-safe-contentinfo", what: "safe ContentInfo",
|
|
306
|
+
build: function (m) {
|
|
307
|
+
return { contentType: m.fields.contentType.value, innerNode: m.fields.content.value };
|
|
308
|
+
},
|
|
309
|
+
});
|
|
310
|
+
var AUTHENTICATED_SAFE = schema.seqOf(AS_ELEMENT, {
|
|
311
|
+
code: "pkcs12/bad-authenticated-safe", what: "AuthenticatedSafe",
|
|
312
|
+
max: C.LIMITS.PKCS12_MAX_ELEMENTS, maxCode: "pkcs12/too-many-elements",
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// The authSafe ContentInfo (§4): contentType is data (password integrity) or
|
|
316
|
+
// signedData (public-key integrity) and nothing else.
|
|
317
|
+
var AUTH_SAFE = schema.seq([
|
|
318
|
+
schema.field("contentType", schema.oidLeaf()),
|
|
319
|
+
schema.field("content", schema.explicit(0, schema.any(), { code: "pkcs12/bad-authsafe", what: "authSafe content" })),
|
|
320
|
+
], {
|
|
321
|
+
assert: "sequence", arity: { exact: 2 }, code: "pkcs12/bad-authsafe", what: "authSafe ContentInfo",
|
|
322
|
+
build: function (m) {
|
|
323
|
+
return { contentType: m.fields.contentType.value, innerNode: m.fields.content.value };
|
|
324
|
+
},
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
// PFX ::= SEQUENCE { version INTEGER {v3(3)}, authSafe ContentInfo,
|
|
328
|
+
// macData MacData OPTIONAL } (§4).
|
|
329
|
+
var PFX = schema.seq([
|
|
330
|
+
schema.field("version", pkix.versionReader(NS, { "3": 3 })),
|
|
331
|
+
schema.field("authSafe", AUTH_SAFE),
|
|
332
|
+
schema.optional("macData", MAC_DATA, { whenUniversal: [TAGS.SEQUENCE] }),
|
|
333
|
+
], {
|
|
334
|
+
assert: "sequence", code: "pkcs12/not-a-pfx", what: "PFX",
|
|
335
|
+
build: function (m, ctx) {
|
|
336
|
+
var version = m.fields.version.value;
|
|
337
|
+
var authSafe = m.fields.authSafe.value.result;
|
|
338
|
+
var macPresent = m.fields.macData.present;
|
|
339
|
+
var mac = macPresent ? m.fields.macData.value.result : null;
|
|
340
|
+
// Every re-decode below shares one budget, so nesting chained across
|
|
341
|
+
// OCTET-STRING (and delegation) boundaries cannot restart the caps.
|
|
342
|
+
var state = { budget: { remaining: C.LIMITS.PKCS12_MAX_REDECODES }, bagDepth: 0 };
|
|
343
|
+
|
|
344
|
+
if (authSafe.contentType === OID_DATA) {
|
|
345
|
+
// §5.1 step 5B — the MAC covers the value octets of the id-data OCTET
|
|
346
|
+
// STRING (the AuthenticatedSafe encoding), excluding the TLV header.
|
|
347
|
+
// MacData itself is OPTIONAL in the PFX syntax: a store without it
|
|
348
|
+
// (OpenSSL `pkcs12 -export -nomac`) carries no integrity protection at
|
|
349
|
+
// all, which is a policy concern for the caller — surfaced as
|
|
350
|
+
// integrityMode "none", never rejected by a parser that surfaces
|
|
351
|
+
// integrity inputs rather than verifying them.
|
|
352
|
+
var macedBytes = _octetContent(authSafe.innerNode, ctx, "an id-data authSafe content");
|
|
353
|
+
var dispatched = _dispatchSafes(_walkAuthenticatedSafe(macedBytes, state, ctx), state, ctx);
|
|
354
|
+
return {
|
|
355
|
+
version: version, integrityMode: macPresent ? "password" : "none", mac: mac, macedBytes: macedBytes,
|
|
356
|
+
authSafeSigned: null, safeBags: dispatched.safeBags, encryptedSafes: dispatched.encryptedSafes,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
if (authSafe.contentType === OID_SIGNED_DATA) {
|
|
360
|
+
// §4 integrity coherence: public-key integrity has no MacData.
|
|
361
|
+
if (macPresent) throw ctx.E("pkcs12/bad-integrity-mode", "an id-signedData authSafe (public-key integrity) must omit macData (RFC 7292 §4)");
|
|
362
|
+
var signed = cms.walkSignedData(authSafe.innerNode);
|
|
363
|
+
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 §4.1)");
|
|
365
|
+
}
|
|
366
|
+
if (signed.encapContentInfo.eContent === null) {
|
|
367
|
+
throw ctx.E("pkcs12/bad-authsafe", "a public-key-integrity authSafe must carry attached eContent (RFC 7292 §4.1)");
|
|
368
|
+
}
|
|
369
|
+
// A signer-less SignedData carries no integrity at all — the one thing
|
|
370
|
+
// public-key-integrity mode exists to provide.
|
|
371
|
+
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 §4)");
|
|
373
|
+
}
|
|
374
|
+
var dispatchedSigned = _dispatchSafes(_walkAuthenticatedSafe(signed.encapContentInfo.eContent, state, ctx), state, ctx);
|
|
375
|
+
return {
|
|
376
|
+
version: version, integrityMode: "public-key", mac: null, macedBytes: null,
|
|
377
|
+
authSafeSigned: signed, safeBags: dispatchedSigned.safeBags, encryptedSafes: dispatchedSigned.encryptedSafes,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
throw ctx.E("pkcs12/bad-authsafe-type", "authSafe contentType must be id-data or id-signedData (RFC 7292 §4), got " +
|
|
381
|
+
(ctx.oid.name(authSafe.contentType) || authSafe.contentType));
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
// The value octets of a universal OCTET STRING node (a BER constructed string
|
|
386
|
+
// arrives already reassembled to primitive content by the codec's ber mode).
|
|
387
|
+
function _octetContent(node, ctx, what, code) {
|
|
388
|
+
if (!(node && node.tagClass === "universal" && node.tagNumber === TAGS.OCTET_STRING && node.content)) {
|
|
389
|
+
throw ctx.E(code || "pkcs12/bad-bag-value", what + " must be an OCTET STRING");
|
|
390
|
+
}
|
|
391
|
+
return node.content;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// RFC 7292 §4.1 — a privacy-mode safe wraps a SafeContents, so the encrypted
|
|
395
|
+
// content's declared type must be id-data whichever CMS privacy structure
|
|
396
|
+
// carries it, and the ciphertext must be attached: CMS permits a detached
|
|
397
|
+
// EncryptedContentInfo, but here the ciphertext IS the safe's contents — a
|
|
398
|
+
// safe without it holds nothing a passphrase could ever recover.
|
|
399
|
+
function _privacySafe(content, ctx) {
|
|
400
|
+
if (content.encryptedContentInfo.contentType !== OID_DATA) {
|
|
401
|
+
throw ctx.E("pkcs12/bad-safe-contentinfo-type",
|
|
402
|
+
"an encrypted safe must declare id-data (SafeContents) as its encrypted content type (RFC 7292 §4.1)");
|
|
403
|
+
}
|
|
404
|
+
if (content.encryptedContentInfo.encryptedContent === null) {
|
|
405
|
+
throw ctx.E("pkcs12/bad-safe-contentinfo",
|
|
406
|
+
"an encrypted safe must carry attached ciphertext — its encryptedContent is the SafeContents (RFC 7292 §4.1)");
|
|
407
|
+
}
|
|
408
|
+
return content;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
// A PKCS#9 SINGLE-VALUE BMPString attribute value (friendlyName), decoded
|
|
412
|
+
// UTF-16BE by the codec's string reader (even length + surrogate rules there).
|
|
413
|
+
function _bmpValue(bytes, ctx) {
|
|
414
|
+
var node = asn1.decode(bytes, { ber: true });
|
|
415
|
+
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 §4.2)");
|
|
417
|
+
}
|
|
418
|
+
return asn1.read.string(node);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// Re-decode the AuthenticatedSafe blob (BER content region, budgeted).
|
|
422
|
+
function _walkAuthenticatedSafe(bytes, state, ctx) {
|
|
423
|
+
return schema.embeddedDer(AUTHENTICATED_SAFE, bytes, ctx, {
|
|
424
|
+
code: "pkcs12/bad-der", what: "the AuthenticatedSafe", ber: true,
|
|
425
|
+
budget: state.budget, budgetCode: "pkcs12/too-deep",
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Dispatch each AuthenticatedSafe element by contentType (§4.1): id-data
|
|
430
|
+
// carries plaintext SafeContents (re-decoded here); the two privacy modes are
|
|
431
|
+
// full CMS structures validated by the CMS module on the already-decoded node
|
|
432
|
+
// — ciphertext stays opaque inside the CMS surface.
|
|
433
|
+
function _dispatchSafes(asMatch, state, ctx) {
|
|
434
|
+
var safeBags = [];
|
|
435
|
+
var encryptedSafes = [];
|
|
436
|
+
for (var i = 0; i < asMatch.items.length; i++) {
|
|
437
|
+
var el = asMatch.items[i].value.result;
|
|
438
|
+
if (el.contentType === OID_DATA) {
|
|
439
|
+
var contents = schema.embeddedDer(SAFE_CONTENTS, _octetContent(el.innerNode, ctx, "an id-data safe content"), ctx, {
|
|
440
|
+
code: "pkcs12/bad-der", what: "a SafeContents", ber: true,
|
|
441
|
+
budget: state.budget, budgetCode: "pkcs12/too-deep",
|
|
442
|
+
});
|
|
443
|
+
for (var j = 0; j < contents.items.length; j++) {
|
|
444
|
+
safeBags.push(_buildBag(contents.items[j].value.result, state, ctx));
|
|
445
|
+
}
|
|
446
|
+
} else if (el.contentType === OID_ENCRYPTED_DATA) {
|
|
447
|
+
encryptedSafes.push({ type: "encryptedData", content: _privacySafe(cms.walkEncryptedData(el.innerNode), ctx) });
|
|
448
|
+
} else if (el.contentType === OID_ENVELOPED_DATA) {
|
|
449
|
+
encryptedSafes.push({ type: "envelopedData", content: _privacySafe(cms.walkEnvelopedData(el.innerNode), ctx) });
|
|
450
|
+
} else {
|
|
451
|
+
throw ctx.E("pkcs12/bad-safe-contentinfo-type", "an AuthenticatedSafe element must be id-data, id-encryptedData, or id-envelopedData (RFC 7292 §4.1), got " +
|
|
452
|
+
(ctx.oid.name(el.contentType) || el.contentType));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return { safeBags: safeBags, encryptedSafes: encryptedSafes };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// bagId dispatch (§4.2, Appendix D). The bag-type set is closed: an
|
|
459
|
+
// unrecognized bagId rejects. Key material delegates to the exported PKCS#8
|
|
460
|
+
// parsers; cert / CRL / secret values are surfaced raw.
|
|
461
|
+
function _buildBag(rec, state, ctx) {
|
|
462
|
+
var bag = {
|
|
463
|
+
type: null, bagId: rec.bagId,
|
|
464
|
+
friendlyName: rec.friendlyName, localKeyId: rec.localKeyId, attributes: rec.attributes,
|
|
465
|
+
};
|
|
466
|
+
// Key bags are walked from the DECODED node — their wire bytes may carry
|
|
467
|
+
// BER shapes the strict pkcs8 parse entry refuses, and the node already
|
|
468
|
+
// exists (no re-decode).
|
|
469
|
+
if (rec.bagId === OID_KEY_BAG) {
|
|
470
|
+
bag.type = "keyBag";
|
|
471
|
+
bag.key = pkcs8.walkPrivateKeyInfo(rec.valueNode);
|
|
472
|
+
return bag;
|
|
473
|
+
}
|
|
474
|
+
if (rec.bagId === OID_SHROUDED_KEY_BAG) {
|
|
475
|
+
bag.type = "pkcs8ShroudedKeyBag";
|
|
476
|
+
bag.encrypted = pkcs8.walkEncryptedPrivateKeyInfo(rec.valueNode);
|
|
477
|
+
return bag;
|
|
478
|
+
}
|
|
479
|
+
if (rec.bagId === OID_CERT_BAG) {
|
|
480
|
+
bag.type = "certBag";
|
|
481
|
+
var cert = schema.walk(CERT_BAG, rec.valueNode, ctx).result;
|
|
482
|
+
bag.certType = cert.certType; bag.certId = cert.certId; bag.certValue = cert.certValue;
|
|
483
|
+
return bag;
|
|
484
|
+
}
|
|
485
|
+
if (rec.bagId === OID_CRL_BAG) {
|
|
486
|
+
bag.type = "crlBag";
|
|
487
|
+
var crl = schema.walk(CRL_BAG, rec.valueNode, ctx).result;
|
|
488
|
+
bag.crlType = crl.crlType; bag.crlId = crl.crlId; bag.crlValue = crl.crlValue;
|
|
489
|
+
return bag;
|
|
490
|
+
}
|
|
491
|
+
if (rec.bagId === OID_SECRET_BAG) {
|
|
492
|
+
bag.type = "secretBag";
|
|
493
|
+
var secret = schema.walk(SECRET_BAG, rec.valueNode, ctx).result;
|
|
494
|
+
bag.secretTypeId = secret.secretTypeId; bag.secretTypeName = secret.secretTypeName; bag.secretValue = secret.secretValue;
|
|
495
|
+
return bag;
|
|
496
|
+
}
|
|
497
|
+
if (rec.bagId === OID_SAFE_CONTENTS_BAG) {
|
|
498
|
+
// §4.2.6 recursion — bounded independently of the codec depth cap so a
|
|
499
|
+
// chain across re-decode boundaries cannot restart it.
|
|
500
|
+
if (state.bagDepth + 1 > C.LIMITS.PKCS12_MAX_BAG_DEPTH) {
|
|
501
|
+
throw ctx.E("pkcs12/too-deep", "safeContentsBag nesting exceeds the depth cap " + C.LIMITS.PKCS12_MAX_BAG_DEPTH);
|
|
502
|
+
}
|
|
503
|
+
var nestedState = { budget: state.budget, bagDepth: state.bagDepth + 1 };
|
|
504
|
+
bag.type = "safeContentsBag";
|
|
505
|
+
bag.nested = [];
|
|
506
|
+
var nestedMatch = schema.walk(SAFE_CONTENTS, rec.valueNode, ctx);
|
|
507
|
+
for (var i = 0; i < nestedMatch.items.length; i++) {
|
|
508
|
+
bag.nested.push(_buildBag(nestedMatch.items[i].value.result, nestedState, ctx));
|
|
509
|
+
}
|
|
510
|
+
return bag;
|
|
511
|
+
}
|
|
512
|
+
throw ctx.E("pkcs12/bad-bag-type", (ctx.oid.name(rec.bagId) || rec.bagId) + " is not a recognized SafeBag bagId (RFC 7292 §4.2)");
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* @primitive pki.schema.pkcs12.parse
|
|
517
|
+
* @signature pki.schema.pkcs12.parse(input) -> pfx
|
|
518
|
+
* @since 0.1.18
|
|
519
|
+
* @status experimental
|
|
520
|
+
* @spec RFC 7292, RFC 9579
|
|
521
|
+
* @defends ASN.1-parser-DoS (CWE-400)
|
|
522
|
+
* @related pki.schema.parse, pki.schema.pkcs8.parse, pki.schema.cms.parse
|
|
523
|
+
*
|
|
524
|
+
* Parse a DER / BER `Buffer` or a PEM string into a structured PFX:
|
|
525
|
+
* `{ version, integrityMode, mac, macedBytes, authSafeSigned, safeBags,
|
|
526
|
+
* encryptedSafes }`. `integrityMode` is `"password"` (id-data authSafe with
|
|
527
|
+
* MacData — `mac` carries `{ kind, hashOid, hashName, hashParameters,
|
|
528
|
+
* pbmac1, macValue, macSalt, iterations }`, where `kind` distinguishes the
|
|
529
|
+
* RFC 9579 PBMAC1 arm; for PBMAC1 the required parameters are validated and
|
|
530
|
+
* decoded onto `pbmac1` (`{ kdf: { salt, iterationCount, keyLength, prfOid,
|
|
531
|
+
* prfName }, schemeOid, schemeName }`) with `hashParameters` keeping the raw
|
|
532
|
+
* bytes, and `macedBytes` is the exact byte range the HMAC covers),
|
|
533
|
+
* `"none"` (id-data authSafe without MacData — the shape
|
|
534
|
+
* `openssl pkcs12 -export -nomac` emits; `mac` is `null` and the store
|
|
535
|
+
* carries no integrity protection, a policy decision left to the caller),
|
|
536
|
+
* or `"public-key"` (id-signedData authSafe — the CMS SignedData surfaced
|
|
537
|
+
* on `authSafeSigned`, signature not verified here). Each of `safeBags` is
|
|
538
|
+
* `{ type, bagId, friendlyName, localKeyId, attributes }` plus its arm:
|
|
539
|
+
* a `keyBag` carries `key` (the PKCS#8 parse), a `pkcs8ShroudedKeyBag`
|
|
540
|
+
* carries `encrypted` (algorithm surfaced, ciphertext opaque), `certBag` /
|
|
541
|
+
* `crlBag` / `secretBag` carry their values raw and byte-exact, and a
|
|
542
|
+
* `safeContentsBag` carries `nested` bags. Encrypted / enveloped safes are
|
|
543
|
+
* surfaced structurally on `encryptedSafes` via the CMS module — recipient
|
|
544
|
+
* infos and algorithms decoded, ciphertext never parsed. MAC verification
|
|
545
|
+
* and bag decryption are passphrase operations for an external layer; the
|
|
546
|
+
* inputs are surfaced exactly.
|
|
547
|
+
*
|
|
548
|
+
* Throws `Pkcs12Error` when the bytes are not a well-formed PFX, and
|
|
549
|
+
* `Asn1Error` when the underlying encoding is malformed.
|
|
550
|
+
*
|
|
551
|
+
* @example
|
|
552
|
+
* var store = pki.schema.pkcs12.parse(der);
|
|
553
|
+
* store.safeBags.map(function (b) { return b.type; });
|
|
554
|
+
*/
|
|
555
|
+
var parse = pkix.makeParser({
|
|
556
|
+
pemLabel: "PKCS12", PemError: PemError, ErrorClass: Pkcs12Error,
|
|
557
|
+
prefix: "pkcs12", what: "PFX", topSchema: PFX, ns: NS, ber: true,
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* @primitive pki.schema.pkcs12.pemDecode
|
|
562
|
+
* @signature pki.schema.pkcs12.pemDecode(text, label?) -> Buffer
|
|
563
|
+
* @since 0.1.18
|
|
564
|
+
* @status experimental
|
|
565
|
+
* @spec RFC 7468, RFC 7292
|
|
566
|
+
* @related pki.schema.pkcs12.parse
|
|
567
|
+
*
|
|
568
|
+
* Extract the DER bytes from a PEM block (default label `PKCS12`). A
|
|
569
|
+
* `.p12` / `.pfx` file is almost always binary — the PEM path is a
|
|
570
|
+
* convenience for stores that transit text channels.
|
|
571
|
+
*
|
|
572
|
+
* @example
|
|
573
|
+
* var der = pki.schema.pkcs12.pemDecode(pemText);
|
|
574
|
+
*/
|
|
575
|
+
function pemDecode(text, label) { return pkix.pemDecode(text, label || "PKCS12", PemError); }
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* @primitive pki.schema.pkcs12.pemEncode
|
|
579
|
+
* @signature pki.schema.pkcs12.pemEncode(der, label?) -> string
|
|
580
|
+
* @since 0.1.18
|
|
581
|
+
* @status experimental
|
|
582
|
+
* @spec RFC 7468
|
|
583
|
+
* @related pki.schema.pkcs12.pemDecode
|
|
584
|
+
*
|
|
585
|
+
* Wrap DER bytes in a PEM envelope (default label `PKCS12`).
|
|
586
|
+
*
|
|
587
|
+
* @example
|
|
588
|
+
* var pem = pki.schema.pkcs12.pemEncode(der);
|
|
589
|
+
*/
|
|
590
|
+
function pemEncode(der, label) { return pkix.pemEncode(der, label || "PKCS12", PemError); }
|
|
591
|
+
|
|
592
|
+
// A PFX root leads with a universal INTEGER (version), colliding with the
|
|
593
|
+
// PKCS#8 detector on the first child — so the discriminators are children[1]
|
|
594
|
+
// (a ContentInfo: SEQUENCE of exactly 2, OID first, [0] constructed second —
|
|
595
|
+
// a shape a PrivateKeyInfo's AlgorithmIdentifier never presents) and
|
|
596
|
+
// children[2] (a SEQUENCE MacData or absent, never PKCS#8's OCTET STRING).
|
|
597
|
+
function matches(root) {
|
|
598
|
+
if (!root || root.tagClass !== "universal" || root.tagNumber !== TAGS.SEQUENCE) return false;
|
|
599
|
+
var k = root.children;
|
|
600
|
+
if (!k || k.length < 2 || k.length > 3) return false;
|
|
601
|
+
if (!(k[0].tagClass === "universal" && k[0].tagNumber === TAGS.INTEGER)) return false;
|
|
602
|
+
var ci = k[1];
|
|
603
|
+
if (!ci.children || ci.tagClass !== "universal" || ci.tagNumber !== TAGS.SEQUENCE || ci.children.length !== 2) return false;
|
|
604
|
+
if (!(ci.children[0].tagClass === "universal" && ci.children[0].tagNumber === TAGS.OBJECT_IDENTIFIER)) return false;
|
|
605
|
+
if (!(ci.children[1].tagClass === "context" && ci.children[1].tagNumber === 0 && ci.children[1].children)) return false;
|
|
606
|
+
if (k.length === 3 && !(k[2].tagClass === "universal" && k[2].tagNumber === TAGS.SEQUENCE && k[2].children)) return false;
|
|
607
|
+
return true;
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
module.exports = {
|
|
611
|
+
parse: parse,
|
|
612
|
+
pemDecode: pemDecode,
|
|
613
|
+
pemEncode: pemEncode,
|
|
614
|
+
matches: matches,
|
|
615
|
+
};
|
package/lib/schema-pkcs8.js
CHANGED
|
@@ -202,10 +202,20 @@ function matches(root) {
|
|
|
202
202
|
// auto-route can return once a validated encryption-algorithm discriminator (the
|
|
203
203
|
// PBES layer) exists.
|
|
204
204
|
|
|
205
|
+
// Validate a bare, already-decoded PrivateKeyInfo / EncryptedPrivateKeyInfo
|
|
206
|
+
// node — for a composer that holds a decoded node and must not re-parse its
|
|
207
|
+
// bytes (an RFC 7292 PFX key bag, whose wire encoding may be BER that the
|
|
208
|
+
// strict `parse` entry would refuse). Same contract as the CMS walkers: the
|
|
209
|
+
// node is the bare structure, typed pkcs8/* on rejection.
|
|
210
|
+
function walkPrivateKeyInfo(node) { return schema.walk(PRIVATE_KEY_INFO, node, NS).result; }
|
|
211
|
+
function walkEncryptedPrivateKeyInfo(node) { return schema.walk(ENCRYPTED_PRIVATE_KEY_INFO, node, NS).result; }
|
|
212
|
+
|
|
205
213
|
module.exports = {
|
|
206
214
|
parse: parse,
|
|
207
215
|
parseEncrypted: parseEncrypted,
|
|
208
216
|
pemDecode: pemDecode,
|
|
209
217
|
pemEncode: pemEncode,
|
|
210
218
|
matches: matches,
|
|
219
|
+
walkPrivateKeyInfo: walkPrivateKeyInfo,
|
|
220
|
+
walkEncryptedPrivateKeyInfo: walkEncryptedPrivateKeyInfo,
|
|
211
221
|
};
|
package/lib/schema-pkix.js
CHANGED
|
@@ -77,8 +77,17 @@ function _isPemArmor(buf) {
|
|
|
77
77
|
|
|
78
78
|
// Decode the DER root, wrapping a codec fault in the caller's <prefix>/bad-der.
|
|
79
79
|
function decodeRoot(der, opts) {
|
|
80
|
-
|
|
81
|
-
|
|
80
|
+
// A format whose wire encoding is normatively BER (RFC 7292 PKCS#12) opts
|
|
81
|
+
// in to the codec's ber mode for the WHOLE decode. BER-vs-DER divergence is
|
|
82
|
+
// not always a decode-time throw — a definite-length constructed IMPLICIT
|
|
83
|
+
// OCTET STRING decodes strictly and only diverges at the typed reader — so
|
|
84
|
+
// a strict-first-retry-on-throw boundary would miss legal BER shapes. A
|
|
85
|
+
// strict-DER input decodes identically under the ber mode, every other
|
|
86
|
+
// strictness verdict included.
|
|
87
|
+
try { return asn1.decode(der, opts.ber ? { ber: true } : undefined); }
|
|
88
|
+
catch (e) {
|
|
89
|
+
throw new opts.ErrorClass(opts.prefix + "/bad-der", (opts.what || "input") + " DER did not decode: " + ((e && e.message) || String(e)), e);
|
|
90
|
+
}
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
// The shared parse entry: coerce -> decode -> walk the top schema. A format's
|
|
@@ -692,7 +701,8 @@ function spki(ns, opts) {
|
|
|
692
701
|
function attribute(ns) {
|
|
693
702
|
return schema.seq([
|
|
694
703
|
schema.field("type", schema.oidLeaf()),
|
|
695
|
-
schema.field("values", schema.setOf(schema.any(), { assert: "set", min: 1,
|
|
704
|
+
schema.field("values", schema.setOf(schema.any(), { assert: "set", min: 1, max: constants.LIMITS.ATTRIBUTE_MAX_VALUES,
|
|
705
|
+
code: ns.prefix + "/bad-attribute-values", what: "attribute values" })),
|
|
696
706
|
], {
|
|
697
707
|
assert: "sequence", arity: { exact: 2 }, code: ns.prefix + "/bad-attribute", what: "Attribute",
|
|
698
708
|
build: function (m, ctx) {
|