source: trip-planner-front/node_modules/node-forge/lib/pkcs12.js@ 59329aa

Last change on this file since 59329aa was 6a3a178, checked in by Ema <ema_spirova@…>, 3 years ago

initial commit

  • Property mode set to 100644
File size: 32.6 KB
Line 
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 */
96var forge = require('./forge');
97require('./asn1');
98require('./hmac');
99require('./oids');
100require('./pkcs7asn1');
101require('./pbe');
102require('./random');
103require('./rsa');
104require('./sha1');
105require('./util');
106require('./x509');
107
108// shortcut for asn.1 & PKI API
109var asn1 = forge.asn1;
110var pki = forge.pki;
111
112// shortcut for PKCS#12 API
113var p12 = module.exports = forge.pkcs12 = forge.pkcs12 || {};
114
115var 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
134var 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
198var 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
224var 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
244var 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 */
283function _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 */
316p12.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 */
495function _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 */
519function _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 */
577function _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 */
622function _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 */
738function _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 */
796p12.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 */
1074p12.generateKey = forge.pbe.generatePkcs12Key;
Note: See TracBrowser for help on using the repository browser.