corrade-nucleus-nucleons – Blame information for rev 22
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
22 | office | 1 | /** |
2 | * Javascript implementation of PKCS#12. |
||
3 | * |
||
4 | * @author Dave Longley |
||
5 | * @author Stefan Siegl <stesie@brokenpipe.de> |
||
6 | * |
||
7 | * Copyright (c) 2010-2014 Digital Bazaar, Inc. |
||
8 | * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de> |
||
9 | * |
||
10 | * The ASN.1 representation of PKCS#12 is as follows |
||
11 | * (see ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-12/pkcs-12-tc1.pdf for details) |
||
12 | * |
||
13 | * PFX ::= SEQUENCE { |
||
14 | * version INTEGER {v3(3)}(v3,...), |
||
15 | * authSafe ContentInfo, |
||
16 | * macData MacData OPTIONAL |
||
17 | * } |
||
18 | * |
||
19 | * MacData ::= SEQUENCE { |
||
20 | * mac DigestInfo, |
||
21 | * macSalt OCTET STRING, |
||
22 | * iterations INTEGER DEFAULT 1 |
||
23 | * } |
||
24 | * Note: The iterations default is for historical reasons and its use is |
||
25 | * deprecated. A higher value, like 1024, is recommended. |
||
26 | * |
||
27 | * DigestInfo is defined in PKCS#7 as follows: |
||
28 | * |
||
29 | * DigestInfo ::= SEQUENCE { |
||
30 | * digestAlgorithm DigestAlgorithmIdentifier, |
||
31 | * digest Digest |
||
32 | * } |
||
33 | * |
||
34 | * DigestAlgorithmIdentifier ::= AlgorithmIdentifier |
||
35 | * |
||
36 | * The AlgorithmIdentifier contains an Object Identifier (OID) and parameters |
||
37 | * for the algorithm, if any. In the case of SHA1 there is none. |
||
38 | * |
||
39 | * AlgorithmIdentifer ::= SEQUENCE { |
||
40 | * algorithm OBJECT IDENTIFIER, |
||
41 | * parameters ANY DEFINED BY algorithm OPTIONAL |
||
42 | * } |
||
43 | * |
||
44 | * Digest ::= OCTET STRING |
||
45 | * |
||
46 | * |
||
47 | * ContentInfo ::= SEQUENCE { |
||
48 | * contentType ContentType, |
||
49 | * content [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL |
||
50 | * } |
||
51 | * |
||
52 | * ContentType ::= OBJECT IDENTIFIER |
||
53 | * |
||
54 | * AuthenticatedSafe ::= SEQUENCE OF ContentInfo |
||
55 | * -- Data if unencrypted |
||
56 | * -- EncryptedData if password-encrypted |
||
57 | * -- EnvelopedData if public key-encrypted |
||
58 | * |
||
59 | * |
||
60 | * SafeContents ::= SEQUENCE OF SafeBag |
||
61 | * |
||
62 | * SafeBag ::= SEQUENCE { |
||
63 | * bagId BAG-TYPE.&id ({PKCS12BagSet}) |
||
64 | * bagValue [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}), |
||
65 | * bagAttributes SET OF PKCS12Attribute OPTIONAL |
||
66 | * } |
||
67 | * |
||
68 | * PKCS12Attribute ::= SEQUENCE { |
||
69 | * attrId ATTRIBUTE.&id ({PKCS12AttrSet}), |
||
70 | * attrValues SET OF ATTRIBUTE.&Type ({PKCS12AttrSet}{@attrId}) |
||
71 | * } -- This type is compatible with the X.500 type ’Attribute’ |
||
72 | * |
||
73 | * PKCS12AttrSet ATTRIBUTE ::= { |
||
74 | * friendlyName | -- from PKCS #9 |
||
75 | * localKeyId, -- from PKCS #9 |
||
76 | * ... -- Other attributes are allowed |
||
77 | * } |
||
78 | * |
||
79 | * CertBag ::= SEQUENCE { |
||
80 | * certId BAG-TYPE.&id ({CertTypes}), |
||
81 | * certValue [0] EXPLICIT BAG-TYPE.&Type ({CertTypes}{@certId}) |
||
82 | * } |
||
83 | * |
||
84 | * x509Certificate BAG-TYPE ::= {OCTET STRING IDENTIFIED BY {certTypes 1}} |
||
85 | * -- DER-encoded X.509 certificate stored in OCTET STRING |
||
86 | * |
||
87 | * sdsiCertificate BAG-TYPE ::= {IA5String IDENTIFIED BY {certTypes 2}} |
||
88 | * -- Base64-encoded SDSI certificate stored in IA5String |
||
89 | * |
||
90 | * CertTypes BAG-TYPE ::= { |
||
91 | * x509Certificate | |
||
92 | * sdsiCertificate, |
||
93 | * ... -- For future extensions |
||
94 | * } |
||
95 | */ |
||
96 | var forge = require('./forge'); |
||
97 | require('./asn1'); |
||
98 | require('./hmac'); |
||
99 | require('./oids'); |
||
100 | require('./pkcs7asn1'); |
||
101 | require('./pbe'); |
||
102 | require('./random'); |
||
103 | require('./rsa'); |
||
104 | require('./sha1'); |
||
105 | require('./util'); |
||
106 | require('./x509'); |
||
107 | |||
108 | // shortcut for asn.1 & PKI API |
||
109 | var asn1 = forge.asn1; |
||
110 | var pki = forge.pki; |
||
111 | |||
112 | // shortcut for PKCS#12 API |
||
113 | var p12 = module.exports = forge.pkcs12 = forge.pkcs12 || {}; |
||
114 | |||
115 | var contentInfoValidator = { |
||
116 | name: 'ContentInfo', |
||
117 | tagClass: asn1.Class.UNIVERSAL, |
||
118 | type: asn1.Type.SEQUENCE, // a ContentInfo |
||
119 | constructed: true, |
||
120 | value: [{ |
||
121 | name: 'ContentInfo.contentType', |
||
122 | tagClass: asn1.Class.UNIVERSAL, |
||
123 | type: asn1.Type.OID, |
||
124 | constructed: false, |
||
125 | capture: 'contentType' |
||
126 | }, { |
||
127 | name: 'ContentInfo.content', |
||
128 | tagClass: asn1.Class.CONTEXT_SPECIFIC, |
||
129 | constructed: true, |
||
130 | captureAsn1: 'content' |
||
131 | }] |
||
132 | }; |
||
133 | |||
134 | var pfxValidator = { |
||
135 | name: 'PFX', |
||
136 | tagClass: asn1.Class.UNIVERSAL, |
||
137 | type: asn1.Type.SEQUENCE, |
||
138 | constructed: true, |
||
139 | value: [{ |
||
140 | name: 'PFX.version', |
||
141 | tagClass: asn1.Class.UNIVERSAL, |
||
142 | type: asn1.Type.INTEGER, |
||
143 | constructed: false, |
||
144 | capture: 'version' |
||
145 | }, |
||
146 | contentInfoValidator, { |
||
147 | name: 'PFX.macData', |
||
148 | tagClass: asn1.Class.UNIVERSAL, |
||
149 | type: asn1.Type.SEQUENCE, |
||
150 | constructed: true, |
||
151 | optional: true, |
||
152 | captureAsn1: 'mac', |
||
153 | value: [{ |
||
154 | name: 'PFX.macData.mac', |
||
155 | tagClass: asn1.Class.UNIVERSAL, |
||
156 | type: asn1.Type.SEQUENCE, // DigestInfo |
||
157 | constructed: true, |
||
158 | value: [{ |
||
159 | name: 'PFX.macData.mac.digestAlgorithm', |
||
160 | tagClass: asn1.Class.UNIVERSAL, |
||
161 | type: asn1.Type.SEQUENCE, // DigestAlgorithmIdentifier |
||
162 | constructed: true, |
||
163 | value: [{ |
||
164 | name: 'PFX.macData.mac.digestAlgorithm.algorithm', |
||
165 | tagClass: asn1.Class.UNIVERSAL, |
||
166 | type: asn1.Type.OID, |
||
167 | constructed: false, |
||
168 | capture: 'macAlgorithm' |
||
169 | }, { |
||
170 | name: 'PFX.macData.mac.digestAlgorithm.parameters', |
||
171 | tagClass: asn1.Class.UNIVERSAL, |
||
172 | captureAsn1: 'macAlgorithmParameters' |
||
173 | }] |
||
174 | }, { |
||
175 | name: 'PFX.macData.mac.digest', |
||
176 | tagClass: asn1.Class.UNIVERSAL, |
||
177 | type: asn1.Type.OCTETSTRING, |
||
178 | constructed: false, |
||
179 | capture: 'macDigest' |
||
180 | }] |
||
181 | }, { |
||
182 | name: 'PFX.macData.macSalt', |
||
183 | tagClass: asn1.Class.UNIVERSAL, |
||
184 | type: asn1.Type.OCTETSTRING, |
||
185 | constructed: false, |
||
186 | capture: 'macSalt' |
||
187 | }, { |
||
188 | name: 'PFX.macData.iterations', |
||
189 | tagClass: asn1.Class.UNIVERSAL, |
||
190 | type: asn1.Type.INTEGER, |
||
191 | constructed: false, |
||
192 | optional: true, |
||
193 | capture: 'macIterations' |
||
194 | }] |
||
195 | }] |
||
196 | }; |
||
197 | |||
198 | var safeBagValidator = { |
||
199 | name: 'SafeBag', |
||
200 | tagClass: asn1.Class.UNIVERSAL, |
||
201 | type: asn1.Type.SEQUENCE, |
||
202 | constructed: true, |
||
203 | value: [{ |
||
204 | name: 'SafeBag.bagId', |
||
205 | tagClass: asn1.Class.UNIVERSAL, |
||
206 | type: asn1.Type.OID, |
||
207 | constructed: false, |
||
208 | capture: 'bagId' |
||
209 | }, { |
||
210 | name: 'SafeBag.bagValue', |
||
211 | tagClass: asn1.Class.CONTEXT_SPECIFIC, |
||
212 | constructed: true, |
||
213 | captureAsn1: 'bagValue' |
||
214 | }, { |
||
215 | name: 'SafeBag.bagAttributes', |
||
216 | tagClass: asn1.Class.UNIVERSAL, |
||
217 | type: asn1.Type.SET, |
||
218 | constructed: true, |
||
219 | optional: true, |
||
220 | capture: 'bagAttributes' |
||
221 | }] |
||
222 | }; |
||
223 | |||
224 | var attributeValidator = { |
||
225 | name: 'Attribute', |
||
226 | tagClass: asn1.Class.UNIVERSAL, |
||
227 | type: asn1.Type.SEQUENCE, |
||
228 | constructed: true, |
||
229 | value: [{ |
||
230 | name: 'Attribute.attrId', |
||
231 | tagClass: asn1.Class.UNIVERSAL, |
||
232 | type: asn1.Type.OID, |
||
233 | constructed: false, |
||
234 | capture: 'oid' |
||
235 | }, { |
||
236 | name: 'Attribute.attrValues', |
||
237 | tagClass: asn1.Class.UNIVERSAL, |
||
238 | type: asn1.Type.SET, |
||
239 | constructed: true, |
||
240 | capture: 'values' |
||
241 | }] |
||
242 | }; |
||
243 | |||
244 | var certBagValidator = { |
||
245 | name: 'CertBag', |
||
246 | tagClass: asn1.Class.UNIVERSAL, |
||
247 | type: asn1.Type.SEQUENCE, |
||
248 | constructed: true, |
||
249 | value: [{ |
||
250 | name: 'CertBag.certId', |
||
251 | tagClass: asn1.Class.UNIVERSAL, |
||
252 | type: asn1.Type.OID, |
||
253 | constructed: false, |
||
254 | capture: 'certId' |
||
255 | }, { |
||
256 | name: 'CertBag.certValue', |
||
257 | tagClass: asn1.Class.CONTEXT_SPECIFIC, |
||
258 | constructed: true, |
||
259 | /* So far we only support X.509 certificates (which are wrapped in |
||
260 | an OCTET STRING, hence hard code that here). */ |
||
261 | value: [{ |
||
262 | name: 'CertBag.certValue[0]', |
||
263 | tagClass: asn1.Class.UNIVERSAL, |
||
264 | type: asn1.Class.OCTETSTRING, |
||
265 | constructed: false, |
||
266 | capture: 'cert' |
||
267 | }] |
||
268 | }] |
||
269 | }; |
||
270 | |||
271 | /** |
||
272 | * Search SafeContents structure for bags with matching attributes. |
||
273 | * |
||
274 | * The search can optionally be narrowed by a certain bag type. |
||
275 | * |
||
276 | * @param safeContents the SafeContents structure to search in. |
||
277 | * @param attrName the name of the attribute to compare against. |
||
278 | * @param attrValue the attribute value to search for. |
||
279 | * @param [bagType] bag type to narrow search by. |
||
280 | * |
||
281 | * @return an array of matching bags. |
||
282 | */ |
||
283 | function _getBagsByAttribute(safeContents, attrName, attrValue, bagType) { |
||
284 | var result = []; |
||
285 | |||
286 | for(var i = 0; i < safeContents.length; i++) { |
||
287 | for(var j = 0; j < safeContents[i].safeBags.length; j++) { |
||
288 | var bag = safeContents[i].safeBags[j]; |
||
289 | if(bagType !== undefined && bag.type !== bagType) { |
||
290 | continue; |
||
291 | } |
||
292 | // only filter by bag type, no attribute specified |
||
293 | if(attrName === null) { |
||
294 | result.push(bag); |
||
295 | continue; |
||
296 | } |
||
297 | if(bag.attributes[attrName] !== undefined && |
||
298 | bag.attributes[attrName].indexOf(attrValue) >= 0) { |
||
299 | result.push(bag); |
||
300 | } |
||
301 | } |
||
302 | } |
||
303 | |||
304 | return result; |
||
305 | } |
||
306 | |||
307 | /** |
||
308 | * Converts a PKCS#12 PFX in ASN.1 notation into a PFX object. |
||
309 | * |
||
310 | * @param obj The PKCS#12 PFX in ASN.1 notation. |
||
311 | * @param strict true to use strict DER decoding, false not to (default: true). |
||
312 | * @param {String} password Password to decrypt with (optional). |
||
313 | * |
||
314 | * @return PKCS#12 PFX object. |
||
315 | */ |
||
316 | p12.pkcs12FromAsn1 = function(obj, strict, password) { |
||
317 | // handle args |
||
318 | if(typeof strict === 'string') { |
||
319 | password = strict; |
||
320 | strict = true; |
||
321 | } else if(strict === undefined) { |
||
322 | strict = true; |
||
323 | } |
||
324 | |||
325 | // validate PFX and capture data |
||
326 | var capture = {}; |
||
327 | var errors = []; |
||
328 | if(!asn1.validate(obj, pfxValidator, capture, errors)) { |
||
329 | var error = new Error('Cannot read PKCS#12 PFX. ' + |
||
330 | 'ASN.1 object is not an PKCS#12 PFX.'); |
||
331 | error.errors = error; |
||
332 | throw error; |
||
333 | } |
||
334 | |||
335 | var pfx = { |
||
336 | version: capture.version.charCodeAt(0), |
||
337 | safeContents: [], |
||
338 | |||
339 | /** |
||
340 | * Gets bags with matching attributes. |
||
341 | * |
||
342 | * @param filter the attributes to filter by: |
||
343 | * [localKeyId] the localKeyId to search for. |
||
344 | * [localKeyIdHex] the localKeyId in hex to search for. |
||
345 | * [friendlyName] the friendly name to search for. |
||
346 | * [bagType] bag type to narrow each attribute search by. |
||
347 | * |
||
348 | * @return a map of attribute type to an array of matching bags or, if no |
||
349 | * attribute was given but a bag type, the map key will be the |
||
350 | * bag type. |
||
351 | */ |
||
352 | getBags: function(filter) { |
||
353 | var rval = {}; |
||
354 | |||
355 | var localKeyId; |
||
356 | if('localKeyId' in filter) { |
||
357 | localKeyId = filter.localKeyId; |
||
358 | } else if('localKeyIdHex' in filter) { |
||
359 | localKeyId = forge.util.hexToBytes(filter.localKeyIdHex); |
||
360 | } |
||
361 | |||
362 | // filter on bagType only |
||
363 | if(localKeyId === undefined && !('friendlyName' in filter) && |
||
364 | 'bagType' in filter) { |
||
365 | rval[filter.bagType] = _getBagsByAttribute( |
||
366 | pfx.safeContents, null, null, filter.bagType); |
||
367 | } |
||
368 | |||
369 | if(localKeyId !== undefined) { |
||
370 | rval.localKeyId = _getBagsByAttribute( |
||
371 | pfx.safeContents, 'localKeyId', |
||
372 | localKeyId, filter.bagType); |
||
373 | } |
||
374 | if('friendlyName' in filter) { |
||
375 | rval.friendlyName = _getBagsByAttribute( |
||
376 | pfx.safeContents, 'friendlyName', |
||
377 | filter.friendlyName, filter.bagType); |
||
378 | } |
||
379 | |||
380 | return rval; |
||
381 | }, |
||
382 | |||
383 | /** |
||
384 | * DEPRECATED: use getBags() instead. |
||
385 | * |
||
386 | * Get bags with matching friendlyName attribute. |
||
387 | * |
||
388 | * @param friendlyName the friendly name to search for. |
||
389 | * @param [bagType] bag type to narrow search by. |
||
390 | * |
||
391 | * @return an array of bags with matching friendlyName attribute. |
||
392 | */ |
||
393 | getBagsByFriendlyName: function(friendlyName, bagType) { |
||
394 | return _getBagsByAttribute( |
||
395 | pfx.safeContents, 'friendlyName', friendlyName, bagType); |
||
396 | }, |
||
397 | |||
398 | /** |
||
399 | * DEPRECATED: use getBags() instead. |
||
400 | * |
||
401 | * Get bags with matching localKeyId attribute. |
||
402 | * |
||
403 | * @param localKeyId the localKeyId to search for. |
||
404 | * @param [bagType] bag type to narrow search by. |
||
405 | * |
||
406 | * @return an array of bags with matching localKeyId attribute. |
||
407 | */ |
||
408 | getBagsByLocalKeyId: function(localKeyId, bagType) { |
||
409 | return _getBagsByAttribute( |
||
410 | pfx.safeContents, 'localKeyId', localKeyId, bagType); |
||
411 | } |
||
412 | }; |
||
413 | |||
414 | if(capture.version.charCodeAt(0) !== 3) { |
||
415 | var error = new Error('PKCS#12 PFX of version other than 3 not supported.'); |
||
416 | error.version = capture.version.charCodeAt(0); |
||
417 | throw error; |
||
418 | } |
||
419 | |||
420 | if(asn1.derToOid(capture.contentType) !== pki.oids.data) { |
||
421 | var error = new Error('Only PKCS#12 PFX in password integrity mode supported.'); |
||
422 | error.oid = asn1.derToOid(capture.contentType); |
||
423 | throw error; |
||
424 | } |
||
425 | |||
426 | var data = capture.content.value[0]; |
||
427 | if(data.tagClass !== asn1.Class.UNIVERSAL || |
||
428 | data.type !== asn1.Type.OCTETSTRING) { |
||
429 | throw new Error('PKCS#12 authSafe content data is not an OCTET STRING.'); |
||
430 | } |
||
431 | data = _decodePkcs7Data(data); |
||
432 | |||
433 | // check for MAC |
||
434 | if(capture.mac) { |
||
435 | var md = null; |
||
436 | var macKeyBytes = 0; |
||
437 | var macAlgorithm = asn1.derToOid(capture.macAlgorithm); |
||
438 | switch(macAlgorithm) { |
||
439 | case pki.oids.sha1: |
||
440 | md = forge.md.sha1.create(); |
||
441 | macKeyBytes = 20; |
||
442 | break; |
||
443 | case pki.oids.sha256: |
||
444 | md = forge.md.sha256.create(); |
||
445 | macKeyBytes = 32; |
||
446 | break; |
||
447 | case pki.oids.sha384: |
||
448 | md = forge.md.sha384.create(); |
||
449 | macKeyBytes = 48; |
||
450 | break; |
||
451 | case pki.oids.sha512: |
||
452 | md = forge.md.sha512.create(); |
||
453 | macKeyBytes = 64; |
||
454 | break; |
||
455 | case pki.oids.md5: |
||
456 | md = forge.md.md5.create(); |
||
457 | macKeyBytes = 16; |
||
458 | break; |
||
459 | } |
||
460 | if(md === null) { |
||
461 | throw new Error('PKCS#12 uses unsupported MAC algorithm: ' + macAlgorithm); |
||
462 | } |
||
463 | |||
464 | // verify MAC (iterations default to 1) |
||
465 | var macSalt = new forge.util.ByteBuffer(capture.macSalt); |
||
466 | var macIterations = (('macIterations' in capture) ? |
||
467 | parseInt(forge.util.bytesToHex(capture.macIterations), 16) : 1); |
||
468 | var macKey = p12.generateKey( |
||
469 | password, macSalt, 3, macIterations, macKeyBytes, md); |
||
470 | var mac = forge.hmac.create(); |
||
471 | mac.start(md, macKey); |
||
472 | mac.update(data.value); |
||
473 | var macValue = mac.getMac(); |
||
474 | if(macValue.getBytes() !== capture.macDigest) { |
||
475 | throw new Error('PKCS#12 MAC could not be verified. Invalid password?'); |
||
476 | } |
||
477 | } |
||
478 | |||
479 | _decodeAuthenticatedSafe(pfx, data.value, strict, password); |
||
480 | return pfx; |
||
481 | }; |
||
482 | |||
483 | /** |
||
484 | * Decodes PKCS#7 Data. PKCS#7 (RFC 2315) defines "Data" as an OCTET STRING, |
||
485 | * but it is sometimes an OCTET STRING that is composed/constructed of chunks, |
||
486 | * each its own OCTET STRING. This is BER-encoding vs. DER-encoding. This |
||
487 | * function transforms this corner-case into the usual simple, |
||
488 | * non-composed/constructed OCTET STRING. |
||
489 | * |
||
490 | * This function may be moved to ASN.1 at some point to better deal with |
||
491 | * more BER-encoding issues, should they arise. |
||
492 | * |
||
493 | * @param data the ASN.1 Data object to transform. |
||
494 | */ |
||
495 | function _decodePkcs7Data(data) { |
||
496 | // handle special case of "chunked" data content: an octet string composed |
||
497 | // of other octet strings |
||
498 | if(data.composed || data.constructed) { |
||
499 | var value = forge.util.createBuffer(); |
||
500 | for(var i = 0; i < data.value.length; ++i) { |
||
501 | value.putBytes(data.value[i].value); |
||
502 | } |
||
503 | data.composed = data.constructed = false; |
||
504 | data.value = value.getBytes(); |
||
505 | } |
||
506 | return data; |
||
507 | } |
||
508 | |||
509 | /** |
||
510 | * Decode PKCS#12 AuthenticatedSafe (BER encoded) into PFX object. |
||
511 | * |
||
512 | * The AuthenticatedSafe is a BER-encoded SEQUENCE OF ContentInfo. |
||
513 | * |
||
514 | * @param pfx The PKCS#12 PFX object to fill. |
||
515 | * @param {String} authSafe BER-encoded AuthenticatedSafe. |
||
516 | * @param strict true to use strict DER decoding, false not to. |
||
517 | * @param {String} password Password to decrypt with (optional). |
||
518 | */ |
||
519 | function _decodeAuthenticatedSafe(pfx, authSafe, strict, password) { |
||
520 | authSafe = asn1.fromDer(authSafe, strict); /* actually it's BER encoded */ |
||
521 | |||
522 | if(authSafe.tagClass !== asn1.Class.UNIVERSAL || |
||
523 | authSafe.type !== asn1.Type.SEQUENCE || |
||
524 | authSafe.constructed !== true) { |
||
525 | throw new Error('PKCS#12 AuthenticatedSafe expected to be a ' + |
||
526 | 'SEQUENCE OF ContentInfo'); |
||
527 | } |
||
528 | |||
529 | for(var i = 0; i < authSafe.value.length; i++) { |
||
530 | var contentInfo = authSafe.value[i]; |
||
531 | |||
532 | // validate contentInfo and capture data |
||
533 | var capture = {}; |
||
534 | var errors = []; |
||
535 | if(!asn1.validate(contentInfo, contentInfoValidator, capture, errors)) { |
||
536 | var error = new Error('Cannot read ContentInfo.'); |
||
537 | error.errors = errors; |
||
538 | throw error; |
||
539 | } |
||
540 | |||
541 | var obj = { |
||
542 | encrypted: false |
||
543 | }; |
||
544 | var safeContents = null; |
||
545 | var data = capture.content.value[0]; |
||
546 | switch(asn1.derToOid(capture.contentType)) { |
||
547 | case pki.oids.data: |
||
548 | if(data.tagClass !== asn1.Class.UNIVERSAL || |
||
549 | data.type !== asn1.Type.OCTETSTRING) { |
||
550 | throw new Error('PKCS#12 SafeContents Data is not an OCTET STRING.'); |
||
551 | } |
||
552 | safeContents = _decodePkcs7Data(data).value; |
||
553 | break; |
||
554 | case pki.oids.encryptedData: |
||
555 | safeContents = _decryptSafeContents(data, password); |
||
556 | obj.encrypted = true; |
||
557 | break; |
||
558 | default: |
||
559 | var error = new Error('Unsupported PKCS#12 contentType.'); |
||
560 | error.contentType = asn1.derToOid(capture.contentType); |
||
561 | throw error; |
||
562 | } |
||
563 | |||
564 | obj.safeBags = _decodeSafeContents(safeContents, strict, password); |
||
565 | pfx.safeContents.push(obj); |
||
566 | } |
||
567 | } |
||
568 | |||
569 | /** |
||
570 | * Decrypt PKCS#7 EncryptedData structure. |
||
571 | * |
||
572 | * @param data ASN.1 encoded EncryptedContentInfo object. |
||
573 | * @param password The user-provided password. |
||
574 | * |
||
575 | * @return The decrypted SafeContents (ASN.1 object). |
||
576 | */ |
||
577 | function _decryptSafeContents(data, password) { |
||
578 | var capture = {}; |
||
579 | var errors = []; |
||
580 | if(!asn1.validate( |
||
581 | data, forge.pkcs7.asn1.encryptedDataValidator, capture, errors)) { |
||
582 | var error = new Error('Cannot read EncryptedContentInfo.'); |
||
583 | error.errors = errors; |
||
584 | throw error; |
||
585 | } |
||
586 | |||
587 | var oid = asn1.derToOid(capture.contentType); |
||
588 | if(oid !== pki.oids.data) { |
||
589 | var error = new Error( |
||
590 | 'PKCS#12 EncryptedContentInfo ContentType is not Data.'); |
||
591 | error.oid = oid; |
||
592 | throw error; |
||
593 | } |
||
594 | |||
595 | // get cipher |
||
596 | oid = asn1.derToOid(capture.encAlgorithm); |
||
597 | var cipher = pki.pbe.getCipher(oid, capture.encParameter, password); |
||
598 | |||
599 | // get encrypted data |
||
600 | var encryptedContentAsn1 = _decodePkcs7Data(capture.encryptedContentAsn1); |
||
601 | var encrypted = forge.util.createBuffer(encryptedContentAsn1.value); |
||
602 | |||
603 | cipher.update(encrypted); |
||
604 | if(!cipher.finish()) { |
||
605 | throw new Error('Failed to decrypt PKCS#12 SafeContents.'); |
||
606 | } |
||
607 | |||
608 | return cipher.output.getBytes(); |
||
609 | } |
||
610 | |||
611 | /** |
||
612 | * Decode PKCS#12 SafeContents (BER-encoded) into array of Bag objects. |
||
613 | * |
||
614 | * The safeContents is a BER-encoded SEQUENCE OF SafeBag. |
||
615 | * |
||
616 | * @param {String} safeContents BER-encoded safeContents. |
||
617 | * @param strict true to use strict DER decoding, false not to. |
||
618 | * @param {String} password Password to decrypt with (optional). |
||
619 | * |
||
620 | * @return {Array} Array of Bag objects. |
||
621 | */ |
||
622 | function _decodeSafeContents(safeContents, strict, password) { |
||
623 | // if strict and no safe contents, return empty safes |
||
624 | if(!strict && safeContents.length === 0) { |
||
625 | return []; |
||
626 | } |
||
627 | |||
628 | // actually it's BER-encoded |
||
629 | safeContents = asn1.fromDer(safeContents, strict); |
||
630 | |||
631 | if(safeContents.tagClass !== asn1.Class.UNIVERSAL || |
||
632 | safeContents.type !== asn1.Type.SEQUENCE || |
||
633 | safeContents.constructed !== true) { |
||
634 | throw new Error( |
||
635 | 'PKCS#12 SafeContents expected to be a SEQUENCE OF SafeBag.'); |
||
636 | } |
||
637 | |||
638 | var res = []; |
||
639 | for(var i = 0; i < safeContents.value.length; i++) { |
||
640 | var safeBag = safeContents.value[i]; |
||
641 | |||
642 | // validate SafeBag and capture data |
||
643 | var capture = {}; |
||
644 | var errors = []; |
||
645 | if(!asn1.validate(safeBag, safeBagValidator, capture, errors)) { |
||
646 | var error = new Error('Cannot read SafeBag.'); |
||
647 | error.errors = errors; |
||
648 | throw error; |
||
649 | } |
||
650 | |||
651 | /* Create bag object and push to result array. */ |
||
652 | var bag = { |
||
653 | type: asn1.derToOid(capture.bagId), |
||
654 | attributes: _decodeBagAttributes(capture.bagAttributes) |
||
655 | }; |
||
656 | res.push(bag); |
||
657 | |||
658 | var validator, decoder; |
||
659 | var bagAsn1 = capture.bagValue.value[0]; |
||
660 | switch(bag.type) { |
||
661 | case pki.oids.pkcs8ShroudedKeyBag: |
||
662 | /* bagAsn1 has a EncryptedPrivateKeyInfo, which we need to decrypt. |
||
663 | Afterwards we can handle it like a keyBag, |
||
664 | which is a PrivateKeyInfo. */ |
||
665 | bagAsn1 = pki.decryptPrivateKeyInfo(bagAsn1, password); |
||
666 | if(bagAsn1 === null) { |
||
667 | throw new Error( |
||
668 | 'Unable to decrypt PKCS#8 ShroudedKeyBag, wrong password?'); |
||
669 | } |
||
670 | |||
671 | /* fall through */ |
||
672 | case pki.oids.keyBag: |
||
673 | /* A PKCS#12 keyBag is a simple PrivateKeyInfo as understood by our |
||
674 | PKI module, hence we don't have to do validation/capturing here, |
||
675 | just pass what we already got. */ |
||
676 | try { |
||
677 | bag.key = pki.privateKeyFromAsn1(bagAsn1); |
||
678 | } catch(e) { |
||
679 | // ignore unknown key type, pass asn1 value |
||
680 | bag.key = null; |
||
681 | bag.asn1 = bagAsn1; |
||
682 | } |
||
683 | continue; /* Nothing more to do. */ |
||
684 | |||
685 | case pki.oids.certBag: |
||
686 | /* A PKCS#12 certBag can wrap both X.509 and sdsi certificates. |
||
687 | Therefore put the SafeBag content through another validator to |
||
688 | capture the fields. Afterwards check & store the results. */ |
||
689 | validator = certBagValidator; |
||
690 | decoder = function() { |
||
691 | if(asn1.derToOid(capture.certId) !== pki.oids.x509Certificate) { |
||
692 | var error = new Error( |
||
693 | 'Unsupported certificate type, only X.509 supported.'); |
||
694 | error.oid = asn1.derToOid(capture.certId); |
||
695 | throw error; |
||
696 | } |
||
697 | |||
698 | // true=produce cert hash |
||
699 | var certAsn1 = asn1.fromDer(capture.cert, strict); |
||
700 | try { |
||
701 | bag.cert = pki.certificateFromAsn1(certAsn1, true); |
||
702 | } catch(e) { |
||
703 | // ignore unknown cert type, pass asn1 value |
||
704 | bag.cert = null; |
||
705 | bag.asn1 = certAsn1; |
||
706 | } |
||
707 | }; |
||
708 | break; |
||
709 | |||
710 | default: |
||
711 | var error = new Error('Unsupported PKCS#12 SafeBag type.'); |
||
712 | error.oid = bag.type; |
||
713 | throw error; |
||
714 | } |
||
715 | |||
716 | /* Validate SafeBag value (i.e. CertBag, etc.) and capture data if needed. */ |
||
717 | if(validator !== undefined && |
||
718 | !asn1.validate(bagAsn1, validator, capture, errors)) { |
||
719 | var error = new Error('Cannot read PKCS#12 ' + validator.name); |
||
720 | error.errors = errors; |
||
721 | throw error; |
||
722 | } |
||
723 | |||
724 | /* Call decoder function from above to store the results. */ |
||
725 | decoder(); |
||
726 | } |
||
727 | |||
728 | return res; |
||
729 | } |
||
730 | |||
731 | /** |
||
732 | * Decode PKCS#12 SET OF PKCS12Attribute into JavaScript object. |
||
733 | * |
||
734 | * @param attributes SET OF PKCS12Attribute (ASN.1 object). |
||
735 | * |
||
736 | * @return the decoded attributes. |
||
737 | */ |
||
738 | function _decodeBagAttributes(attributes) { |
||
739 | var decodedAttrs = {}; |
||
740 | |||
741 | if(attributes !== undefined) { |
||
742 | for(var i = 0; i < attributes.length; ++i) { |
||
743 | var capture = {}; |
||
744 | var errors = []; |
||
745 | if(!asn1.validate(attributes[i], attributeValidator, capture, errors)) { |
||
746 | var error = new Error('Cannot read PKCS#12 BagAttribute.'); |
||
747 | error.errors = errors; |
||
748 | throw error; |
||
749 | } |
||
750 | |||
751 | var oid = asn1.derToOid(capture.oid); |
||
752 | if(pki.oids[oid] === undefined) { |
||
753 | // unsupported attribute type, ignore. |
||
754 | continue; |
||
755 | } |
||
756 | |||
757 | decodedAttrs[pki.oids[oid]] = []; |
||
758 | for(var j = 0; j < capture.values.length; ++j) { |
||
759 | decodedAttrs[pki.oids[oid]].push(capture.values[j].value); |
||
760 | } |
||
761 | } |
||
762 | } |
||
763 | |||
764 | return decodedAttrs; |
||
765 | } |
||
766 | |||
767 | /** |
||
768 | * Wraps a private key and certificate in a PKCS#12 PFX wrapper. If a |
||
769 | * password is provided then the private key will be encrypted. |
||
770 | * |
||
771 | * An entire certificate chain may also be included. To do this, pass |
||
772 | * an array for the "cert" parameter where the first certificate is |
||
773 | * the one that is paired with the private key and each subsequent one |
||
774 | * verifies the previous one. The certificates may be in PEM format or |
||
775 | * have been already parsed by Forge. |
||
776 | * |
||
777 | * @todo implement password-based-encryption for the whole package |
||
778 | * |
||
779 | * @param key the private key. |
||
780 | * @param cert the certificate (may be an array of certificates in order |
||
781 | * to specify a certificate chain). |
||
782 | * @param password the password to use, null for none. |
||
783 | * @param options: |
||
784 | * algorithm the encryption algorithm to use |
||
785 | * ('aes128', 'aes192', 'aes256', '3des'), defaults to 'aes128'. |
||
786 | * count the iteration count to use. |
||
787 | * saltSize the salt size to use. |
||
788 | * useMac true to include a MAC, false not to, defaults to true. |
||
789 | * localKeyId the local key ID to use, in hex. |
||
790 | * friendlyName the friendly name to use. |
||
791 | * generateLocalKeyId true to generate a random local key ID, |
||
792 | * false not to, defaults to true. |
||
793 | * |
||
794 | * @return the PKCS#12 PFX ASN.1 object. |
||
795 | */ |
||
796 | p12.toPkcs12Asn1 = function(key, cert, password, options) { |
||
797 | // set default options |
||
798 | options = options || {}; |
||
799 | options.saltSize = options.saltSize || 8; |
||
800 | options.count = options.count || 2048; |
||
801 | options.algorithm = options.algorithm || options.encAlgorithm || 'aes128'; |
||
802 | if(!('useMac' in options)) { |
||
803 | options.useMac = true; |
||
804 | } |
||
805 | if(!('localKeyId' in options)) { |
||
806 | options.localKeyId = null; |
||
807 | } |
||
808 | if(!('generateLocalKeyId' in options)) { |
||
809 | options.generateLocalKeyId = true; |
||
810 | } |
||
811 | |||
812 | var localKeyId = options.localKeyId; |
||
813 | var bagAttrs; |
||
814 | if(localKeyId !== null) { |
||
815 | localKeyId = forge.util.hexToBytes(localKeyId); |
||
816 | } else if(options.generateLocalKeyId) { |
||
817 | // use SHA-1 of paired cert, if available |
||
818 | if(cert) { |
||
819 | var pairedCert = forge.util.isArray(cert) ? cert[0] : cert; |
||
820 | if(typeof pairedCert === 'string') { |
||
821 | pairedCert = pki.certificateFromPem(pairedCert); |
||
822 | } |
||
823 | var sha1 = forge.md.sha1.create(); |
||
824 | sha1.update(asn1.toDer(pki.certificateToAsn1(pairedCert)).getBytes()); |
||
825 | localKeyId = sha1.digest().getBytes(); |
||
826 | } else { |
||
827 | // FIXME: consider using SHA-1 of public key (which can be generated |
||
828 | // from private key components), see: cert.generateSubjectKeyIdentifier |
||
829 | // generate random bytes |
||
830 | localKeyId = forge.random.getBytes(20); |
||
831 | } |
||
832 | } |
||
833 | |||
834 | var attrs = []; |
||
835 | if(localKeyId !== null) { |
||
836 | attrs.push( |
||
837 | // localKeyID |
||
838 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ |
||
839 | // attrId |
||
840 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, |
||
841 | asn1.oidToDer(pki.oids.localKeyId).getBytes()), |
||
842 | // attrValues |
||
843 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [ |
||
844 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, |
||
845 | localKeyId) |
||
846 | ]) |
||
847 | ])); |
||
848 | } |
||
849 | if('friendlyName' in options) { |
||
850 | attrs.push( |
||
851 | // friendlyName |
||
852 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ |
||
853 | // attrId |
||
854 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, |
||
855 | asn1.oidToDer(pki.oids.friendlyName).getBytes()), |
||
856 | // attrValues |
||
857 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [ |
||
858 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BMPSTRING, false, |
||
859 | options.friendlyName) |
||
860 | ]) |
||
861 | ])); |
||
862 | } |
||
863 | |||
864 | if(attrs.length > 0) { |
||
865 | bagAttrs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, attrs); |
||
866 | } |
||
867 | |||
868 | // collect contents for AuthenticatedSafe |
||
869 | var contents = []; |
||
870 | |||
871 | // create safe bag(s) for certificate chain |
||
872 | var chain = []; |
||
873 | if(cert !== null) { |
||
874 | if(forge.util.isArray(cert)) { |
||
875 | chain = cert; |
||
876 | } else { |
||
877 | chain = [cert]; |
||
878 | } |
||
879 | } |
||
880 | |||
881 | var certSafeBags = []; |
||
882 | for(var i = 0; i < chain.length; ++i) { |
||
883 | // convert cert from PEM as necessary |
||
884 | cert = chain[i]; |
||
885 | if(typeof cert === 'string') { |
||
886 | cert = pki.certificateFromPem(cert); |
||
887 | } |
||
888 | |||
889 | // SafeBag |
||
890 | var certBagAttrs = (i === 0) ? bagAttrs : undefined; |
||
891 | var certAsn1 = pki.certificateToAsn1(cert); |
||
892 | var certSafeBag = |
||
893 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ |
||
894 | // bagId |
||
895 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, |
||
896 | asn1.oidToDer(pki.oids.certBag).getBytes()), |
||
897 | // bagValue |
||
898 | asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ |
||
899 | // CertBag |
||
900 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ |
||
901 | // certId |
||
902 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, |
||
903 | asn1.oidToDer(pki.oids.x509Certificate).getBytes()), |
||
904 | // certValue (x509Certificate) |
||
905 | asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ |
||
906 | asn1.create( |
||
907 | asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, |
||
908 | asn1.toDer(certAsn1).getBytes()) |
||
909 | ])])]), |
||
910 | // bagAttributes (OPTIONAL) |
||
911 | certBagAttrs |
||
912 | ]); |
||
913 | certSafeBags.push(certSafeBag); |
||
914 | } |
||
915 | |||
916 | if(certSafeBags.length > 0) { |
||
917 | // SafeContents |
||
918 | var certSafeContents = asn1.create( |
||
919 | asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, certSafeBags); |
||
920 | |||
921 | // ContentInfo |
||
922 | var certCI = |
||
923 | // PKCS#7 ContentInfo |
||
924 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ |
||
925 | // contentType |
||
926 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, |
||
927 | // OID for the content type is 'data' |
||
928 | asn1.oidToDer(pki.oids.data).getBytes()), |
||
929 | // content |
||
930 | asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ |
||
931 | asn1.create( |
||
932 | asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, |
||
933 | asn1.toDer(certSafeContents).getBytes()) |
||
934 | ]) |
||
935 | ]); |
||
936 | contents.push(certCI); |
||
937 | } |
||
938 | |||
939 | // create safe contents for private key |
||
940 | var keyBag = null; |
||
941 | if(key !== null) { |
||
942 | // SafeBag |
||
943 | var pkAsn1 = pki.wrapRsaPrivateKey(pki.privateKeyToAsn1(key)); |
||
944 | if(password === null) { |
||
945 | // no encryption |
||
946 | keyBag = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ |
||
947 | // bagId |
||
948 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, |
||
949 | asn1.oidToDer(pki.oids.keyBag).getBytes()), |
||
950 | // bagValue |
||
951 | asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ |
||
952 | // PrivateKeyInfo |
||
953 | pkAsn1 |
||
954 | ]), |
||
955 | // bagAttributes (OPTIONAL) |
||
956 | bagAttrs |
||
957 | ]); |
||
958 | } else { |
||
959 | // encrypted PrivateKeyInfo |
||
960 | keyBag = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ |
||
961 | // bagId |
||
962 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, |
||
963 | asn1.oidToDer(pki.oids.pkcs8ShroudedKeyBag).getBytes()), |
||
964 | // bagValue |
||
965 | asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ |
||
966 | // EncryptedPrivateKeyInfo |
||
967 | pki.encryptPrivateKeyInfo(pkAsn1, password, options) |
||
968 | ]), |
||
969 | // bagAttributes (OPTIONAL) |
||
970 | bagAttrs |
||
971 | ]); |
||
972 | } |
||
973 | |||
974 | // SafeContents |
||
975 | var keySafeContents = |
||
976 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [keyBag]); |
||
977 | |||
978 | // ContentInfo |
||
979 | var keyCI = |
||
980 | // PKCS#7 ContentInfo |
||
981 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ |
||
982 | // contentType |
||
983 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, |
||
984 | // OID for the content type is 'data' |
||
985 | asn1.oidToDer(pki.oids.data).getBytes()), |
||
986 | // content |
||
987 | asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ |
||
988 | asn1.create( |
||
989 | asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, |
||
990 | asn1.toDer(keySafeContents).getBytes()) |
||
991 | ]) |
||
992 | ]); |
||
993 | contents.push(keyCI); |
||
994 | } |
||
995 | |||
996 | // create AuthenticatedSafe by stringing together the contents |
||
997 | var safe = asn1.create( |
||
998 | asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, contents); |
||
999 | |||
1000 | var macData; |
||
1001 | if(options.useMac) { |
||
1002 | // MacData |
||
1003 | var sha1 = forge.md.sha1.create(); |
||
1004 | var macSalt = new forge.util.ByteBuffer( |
||
1005 | forge.random.getBytes(options.saltSize)); |
||
1006 | var count = options.count; |
||
1007 | // 160-bit key |
||
1008 | var key = p12.generateKey(password, macSalt, 3, count, 20); |
||
1009 | var mac = forge.hmac.create(); |
||
1010 | mac.start(sha1, key); |
||
1011 | mac.update(asn1.toDer(safe).getBytes()); |
||
1012 | var macValue = mac.getMac(); |
||
1013 | macData = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ |
||
1014 | // mac DigestInfo |
||
1015 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ |
||
1016 | // digestAlgorithm |
||
1017 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ |
||
1018 | // algorithm = SHA-1 |
||
1019 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, |
||
1020 | asn1.oidToDer(pki.oids.sha1).getBytes()), |
||
1021 | // parameters = Null |
||
1022 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '') |
||
1023 | ]), |
||
1024 | // digest |
||
1025 | asn1.create( |
||
1026 | asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, |
||
1027 | false, macValue.getBytes()) |
||
1028 | ]), |
||
1029 | // macSalt OCTET STRING |
||
1030 | asn1.create( |
||
1031 | asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, macSalt.getBytes()), |
||
1032 | // iterations INTEGER (XXX: Only support count < 65536) |
||
1033 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, |
||
1034 | asn1.integerToDer(count).getBytes() |
||
1035 | ) |
||
1036 | ]); |
||
1037 | } |
||
1038 | |||
1039 | // PFX |
||
1040 | return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ |
||
1041 | // version (3) |
||
1042 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false, |
||
1043 | asn1.integerToDer(3).getBytes()), |
||
1044 | // PKCS#7 ContentInfo |
||
1045 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [ |
||
1046 | // contentType |
||
1047 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false, |
||
1048 | // OID for the content type is 'data' |
||
1049 | asn1.oidToDer(pki.oids.data).getBytes()), |
||
1050 | // content |
||
1051 | asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [ |
||
1052 | asn1.create( |
||
1053 | asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, |
||
1054 | asn1.toDer(safe).getBytes()) |
||
1055 | ]) |
||
1056 | ]), |
||
1057 | macData |
||
1058 | ]); |
||
1059 | }; |
||
1060 | |||
1061 | /** |
||
1062 | * Derives a PKCS#12 key. |
||
1063 | * |
||
1064 | * @param password the password to derive the key material from, null or |
||
1065 | * undefined for none. |
||
1066 | * @param salt the salt, as a ByteBuffer, to use. |
||
1067 | * @param id the PKCS#12 ID byte (1 = key material, 2 = IV, 3 = MAC). |
||
1068 | * @param iter the iteration count. |
||
1069 | * @param n the number of bytes to derive from the password. |
||
1070 | * @param md the message digest to use, defaults to SHA-1. |
||
1071 | * |
||
1072 | * @return a ByteBuffer with the bytes derived from the password. |
||
1073 | */ |
||
1074 | p12.generateKey = forge.pbe.generatePkcs12Key; |