source: frontend/node_modules/node-forge/lib/pkcs12.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

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