You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							1075 lines
						
					
					
						
							33 KiB
						
					
					
				
			
		
		
	
	
							1075 lines
						
					
					
						
							33 KiB
						
					
					
				| /**
 | |
|  * Javascript implementation of PKCS#12.
 | |
|  *
 | |
|  * @author Dave Longley
 | |
|  * @author Stefan Siegl <stesie@brokenpipe.de>
 | |
|  *
 | |
|  * Copyright (c) 2010-2014 Digital Bazaar, Inc.
 | |
|  * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
 | |
|  *
 | |
|  * The ASN.1 representation of PKCS#12 is as follows
 | |
|  * (see ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-12/pkcs-12-tc1.pdf for details)
 | |
|  *
 | |
|  * PFX ::= SEQUENCE {
 | |
|  *   version  INTEGER {v3(3)}(v3,...),
 | |
|  *   authSafe ContentInfo,
 | |
|  *   macData  MacData OPTIONAL
 | |
|  * }
 | |
|  *
 | |
|  * MacData ::= SEQUENCE {
 | |
|  *   mac DigestInfo,
 | |
|  *   macSalt OCTET STRING,
 | |
|  *   iterations INTEGER DEFAULT 1
 | |
|  * }
 | |
|  * Note: The iterations default is for historical reasons and its use is
 | |
|  * deprecated. A higher value, like 1024, is recommended.
 | |
|  *
 | |
|  * DigestInfo is defined in PKCS#7 as follows:
 | |
|  *
 | |
|  * DigestInfo ::= SEQUENCE {
 | |
|  *   digestAlgorithm DigestAlgorithmIdentifier,
 | |
|  *   digest Digest
 | |
|  * }
 | |
|  *
 | |
|  * DigestAlgorithmIdentifier ::= AlgorithmIdentifier
 | |
|  *
 | |
|  * The AlgorithmIdentifier contains an Object Identifier (OID) and parameters
 | |
|  * for the algorithm, if any. In the case of SHA1 there is none.
 | |
|  *
 | |
|  * AlgorithmIdentifer ::= SEQUENCE {
 | |
|  *    algorithm OBJECT IDENTIFIER,
 | |
|  *    parameters ANY DEFINED BY algorithm OPTIONAL
 | |
|  * }
 | |
|  *
 | |
|  * Digest ::= OCTET STRING
 | |
|  *
 | |
|  *
 | |
|  * ContentInfo ::= SEQUENCE {
 | |
|  *   contentType ContentType,
 | |
|  *   content     [0] EXPLICIT ANY DEFINED BY contentType OPTIONAL
 | |
|  * }
 | |
|  *
 | |
|  * ContentType ::= OBJECT IDENTIFIER
 | |
|  *
 | |
|  * AuthenticatedSafe ::= SEQUENCE OF ContentInfo
 | |
|  * -- Data if unencrypted
 | |
|  * -- EncryptedData if password-encrypted
 | |
|  * -- EnvelopedData if public key-encrypted
 | |
|  *
 | |
|  *
 | |
|  * SafeContents ::= SEQUENCE OF SafeBag
 | |
|  *
 | |
|  * SafeBag ::= SEQUENCE {
 | |
|  *   bagId     BAG-TYPE.&id ({PKCS12BagSet})
 | |
|  *   bagValue  [0] EXPLICIT BAG-TYPE.&Type({PKCS12BagSet}{@bagId}),
 | |
|  *   bagAttributes SET OF PKCS12Attribute OPTIONAL
 | |
|  * }
 | |
|  *
 | |
|  * PKCS12Attribute ::= SEQUENCE {
 | |
|  *   attrId ATTRIBUTE.&id ({PKCS12AttrSet}),
 | |
|  *   attrValues SET OF ATTRIBUTE.&Type ({PKCS12AttrSet}{@attrId})
 | |
|  * } -- This type is compatible with the X.500 type 'Attribute'
 | |
|  *
 | |
|  * PKCS12AttrSet ATTRIBUTE ::= {
 | |
|  *   friendlyName | -- from PKCS #9
 | |
|  *   localKeyId, -- from PKCS #9
 | |
|  *   ... -- Other attributes are allowed
 | |
|  * }
 | |
|  *
 | |
|  * CertBag ::= SEQUENCE {
 | |
|  *   certId    BAG-TYPE.&id   ({CertTypes}),
 | |
|  *   certValue [0] EXPLICIT BAG-TYPE.&Type ({CertTypes}{@certId})
 | |
|  * }
 | |
|  *
 | |
|  * x509Certificate BAG-TYPE ::= {OCTET STRING IDENTIFIED BY {certTypes 1}}
 | |
|  *   -- DER-encoded X.509 certificate stored in OCTET STRING
 | |
|  *
 | |
|  * sdsiCertificate BAG-TYPE ::= {IA5String IDENTIFIED BY {certTypes 2}}
 | |
|  * -- Base64-encoded SDSI certificate stored in IA5String
 | |
|  *
 | |
|  * CertTypes BAG-TYPE ::= {
 | |
|  *   x509Certificate |
 | |
|  *   sdsiCertificate,
 | |
|  *   ... -- For future extensions
 | |
|  * }
 | |
|  */
 | |
| var forge = require('./forge');
 | |
| require('./asn1');
 | |
| require('./hmac');
 | |
| require('./oids');
 | |
| require('./pkcs7asn1');
 | |
| require('./pbe');
 | |
| require('./random');
 | |
| require('./rsa');
 | |
| require('./sha1');
 | |
| require('./util');
 | |
| require('./x509');
 | |
| 
 | |
| // shortcut for asn.1 & PKI API
 | |
| var asn1 = forge.asn1;
 | |
| var pki = forge.pki;
 | |
| 
 | |
| // shortcut for PKCS#12 API
 | |
| var p12 = module.exports = forge.pkcs12 = forge.pkcs12 || {};
 | |
| 
 | |
| var contentInfoValidator = {
 | |
|   name: 'ContentInfo',
 | |
|   tagClass: asn1.Class.UNIVERSAL,
 | |
|   type: asn1.Type.SEQUENCE,  // a ContentInfo
 | |
|   constructed: true,
 | |
|   value: [{
 | |
|     name: 'ContentInfo.contentType',
 | |
|     tagClass: asn1.Class.UNIVERSAL,
 | |
|     type: asn1.Type.OID,
 | |
|     constructed: false,
 | |
|     capture: 'contentType'
 | |
|   }, {
 | |
|     name: 'ContentInfo.content',
 | |
|     tagClass: asn1.Class.CONTEXT_SPECIFIC,
 | |
|     constructed: true,
 | |
|     captureAsn1: 'content'
 | |
|   }]
 | |
| };
 | |
| 
 | |
| var pfxValidator = {
 | |
|   name: 'PFX',
 | |
|   tagClass: asn1.Class.UNIVERSAL,
 | |
|   type: asn1.Type.SEQUENCE,
 | |
|   constructed: true,
 | |
|   value: [{
 | |
|     name: 'PFX.version',
 | |
|     tagClass: asn1.Class.UNIVERSAL,
 | |
|     type: asn1.Type.INTEGER,
 | |
|     constructed: false,
 | |
|     capture: 'version'
 | |
|   },
 | |
|   contentInfoValidator, {
 | |
|     name: 'PFX.macData',
 | |
|     tagClass: asn1.Class.UNIVERSAL,
 | |
|     type: asn1.Type.SEQUENCE,
 | |
|     constructed: true,
 | |
|     optional: true,
 | |
|     captureAsn1: 'mac',
 | |
|     value: [{
 | |
|       name: 'PFX.macData.mac',
 | |
|       tagClass: asn1.Class.UNIVERSAL,
 | |
|       type: asn1.Type.SEQUENCE,  // DigestInfo
 | |
|       constructed: true,
 | |
|       value: [{
 | |
|         name: 'PFX.macData.mac.digestAlgorithm',
 | |
|         tagClass: asn1.Class.UNIVERSAL,
 | |
|         type: asn1.Type.SEQUENCE,  // DigestAlgorithmIdentifier
 | |
|         constructed: true,
 | |
|         value: [{
 | |
|           name: 'PFX.macData.mac.digestAlgorithm.algorithm',
 | |
|           tagClass: asn1.Class.UNIVERSAL,
 | |
|           type: asn1.Type.OID,
 | |
|           constructed: false,
 | |
|           capture: 'macAlgorithm'
 | |
|         }, {
 | |
|           name: 'PFX.macData.mac.digestAlgorithm.parameters',
 | |
|           tagClass: asn1.Class.UNIVERSAL,
 | |
|           captureAsn1: 'macAlgorithmParameters'
 | |
|         }]
 | |
|       }, {
 | |
|         name: 'PFX.macData.mac.digest',
 | |
|         tagClass: asn1.Class.UNIVERSAL,
 | |
|         type: asn1.Type.OCTETSTRING,
 | |
|         constructed: false,
 | |
|         capture: 'macDigest'
 | |
|       }]
 | |
|     }, {
 | |
|       name: 'PFX.macData.macSalt',
 | |
|       tagClass: asn1.Class.UNIVERSAL,
 | |
|       type: asn1.Type.OCTETSTRING,
 | |
|       constructed: false,
 | |
|       capture: 'macSalt'
 | |
|     }, {
 | |
|       name: 'PFX.macData.iterations',
 | |
|       tagClass: asn1.Class.UNIVERSAL,
 | |
|       type: asn1.Type.INTEGER,
 | |
|       constructed: false,
 | |
|       optional: true,
 | |
|       capture: 'macIterations'
 | |
|     }]
 | |
|   }]
 | |
| };
 | |
| 
 | |
| var safeBagValidator = {
 | |
|   name: 'SafeBag',
 | |
|   tagClass: asn1.Class.UNIVERSAL,
 | |
|   type: asn1.Type.SEQUENCE,
 | |
|   constructed: true,
 | |
|   value: [{
 | |
|     name: 'SafeBag.bagId',
 | |
|     tagClass: asn1.Class.UNIVERSAL,
 | |
|     type: asn1.Type.OID,
 | |
|     constructed: false,
 | |
|     capture: 'bagId'
 | |
|   }, {
 | |
|     name: 'SafeBag.bagValue',
 | |
|     tagClass: asn1.Class.CONTEXT_SPECIFIC,
 | |
|     constructed: true,
 | |
|     captureAsn1: 'bagValue'
 | |
|   }, {
 | |
|     name: 'SafeBag.bagAttributes',
 | |
|     tagClass: asn1.Class.UNIVERSAL,
 | |
|     type: asn1.Type.SET,
 | |
|     constructed: true,
 | |
|     optional: true,
 | |
|     capture: 'bagAttributes'
 | |
|   }]
 | |
| };
 | |
| 
 | |
| var attributeValidator = {
 | |
|   name: 'Attribute',
 | |
|   tagClass: asn1.Class.UNIVERSAL,
 | |
|   type: asn1.Type.SEQUENCE,
 | |
|   constructed: true,
 | |
|   value: [{
 | |
|     name: 'Attribute.attrId',
 | |
|     tagClass: asn1.Class.UNIVERSAL,
 | |
|     type: asn1.Type.OID,
 | |
|     constructed: false,
 | |
|     capture: 'oid'
 | |
|   }, {
 | |
|     name: 'Attribute.attrValues',
 | |
|     tagClass: asn1.Class.UNIVERSAL,
 | |
|     type: asn1.Type.SET,
 | |
|     constructed: true,
 | |
|     capture: 'values'
 | |
|   }]
 | |
| };
 | |
| 
 | |
| var certBagValidator = {
 | |
|   name: 'CertBag',
 | |
|   tagClass: asn1.Class.UNIVERSAL,
 | |
|   type: asn1.Type.SEQUENCE,
 | |
|   constructed: true,
 | |
|   value: [{
 | |
|     name: 'CertBag.certId',
 | |
|     tagClass: asn1.Class.UNIVERSAL,
 | |
|     type: asn1.Type.OID,
 | |
|     constructed: false,
 | |
|     capture: 'certId'
 | |
|   }, {
 | |
|     name: 'CertBag.certValue',
 | |
|     tagClass: asn1.Class.CONTEXT_SPECIFIC,
 | |
|     constructed: true,
 | |
|     /* So far we only support X.509 certificates (which are wrapped in
 | |
|        an OCTET STRING, hence hard code that here). */
 | |
|     value: [{
 | |
|       name: 'CertBag.certValue[0]',
 | |
|       tagClass: asn1.Class.UNIVERSAL,
 | |
|       type: asn1.Class.OCTETSTRING,
 | |
|       constructed: false,
 | |
|       capture: 'cert'
 | |
|     }]
 | |
|   }]
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Search SafeContents structure for bags with matching attributes.
 | |
|  *
 | |
|  * The search can optionally be narrowed by a certain bag type.
 | |
|  *
 | |
|  * @param safeContents the SafeContents structure to search in.
 | |
|  * @param attrName the name of the attribute to compare against.
 | |
|  * @param attrValue the attribute value to search for.
 | |
|  * @param [bagType] bag type to narrow search by.
 | |
|  *
 | |
|  * @return an array of matching bags.
 | |
|  */
 | |
| function _getBagsByAttribute(safeContents, attrName, attrValue, bagType) {
 | |
|   var result = [];
 | |
| 
 | |
|   for(var i = 0; i < safeContents.length; i++) {
 | |
|     for(var j = 0; j < safeContents[i].safeBags.length; j++) {
 | |
|       var bag = safeContents[i].safeBags[j];
 | |
|       if(bagType !== undefined && bag.type !== bagType) {
 | |
|         continue;
 | |
|       }
 | |
|       // only filter by bag type, no attribute specified
 | |
|       if(attrName === null) {
 | |
|         result.push(bag);
 | |
|         continue;
 | |
|       }
 | |
|       if(bag.attributes[attrName] !== undefined &&
 | |
|         bag.attributes[attrName].indexOf(attrValue) >= 0) {
 | |
|         result.push(bag);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return result;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Converts a PKCS#12 PFX in ASN.1 notation into a PFX object.
 | |
|  *
 | |
|  * @param obj The PKCS#12 PFX in ASN.1 notation.
 | |
|  * @param strict true to use strict DER decoding, false not to (default: true).
 | |
|  * @param {String} password Password to decrypt with (optional).
 | |
|  *
 | |
|  * @return PKCS#12 PFX object.
 | |
|  */
 | |
| p12.pkcs12FromAsn1 = function(obj, strict, password) {
 | |
|   // handle args
 | |
|   if(typeof strict === 'string') {
 | |
|     password = strict;
 | |
|     strict = true;
 | |
|   } else if(strict === undefined) {
 | |
|     strict = true;
 | |
|   }
 | |
| 
 | |
|   // validate PFX and capture data
 | |
|   var capture = {};
 | |
|   var errors = [];
 | |
|   if(!asn1.validate(obj, pfxValidator, capture, errors)) {
 | |
|     var error = new Error('Cannot read PKCS#12 PFX. ' +
 | |
|       'ASN.1 object is not an PKCS#12 PFX.');
 | |
|     error.errors = error;
 | |
|     throw error;
 | |
|   }
 | |
| 
 | |
|   var pfx = {
 | |
|     version: capture.version.charCodeAt(0),
 | |
|     safeContents: [],
 | |
| 
 | |
|     /**
 | |
|      * Gets bags with matching attributes.
 | |
|      *
 | |
|      * @param filter the attributes to filter by:
 | |
|      *          [localKeyId] the localKeyId to search for.
 | |
|      *          [localKeyIdHex] the localKeyId in hex to search for.
 | |
|      *          [friendlyName] the friendly name to search for.
 | |
|      *          [bagType] bag type to narrow each attribute search by.
 | |
|      *
 | |
|      * @return a map of attribute type to an array of matching bags or, if no
 | |
|      *           attribute was given but a bag type, the map key will be the
 | |
|      *           bag type.
 | |
|      */
 | |
|     getBags: function(filter) {
 | |
|       var rval = {};
 | |
| 
 | |
|       var localKeyId;
 | |
|       if('localKeyId' in filter) {
 | |
|         localKeyId = filter.localKeyId;
 | |
|       } else if('localKeyIdHex' in filter) {
 | |
|         localKeyId = forge.util.hexToBytes(filter.localKeyIdHex);
 | |
|       }
 | |
| 
 | |
|       // filter on bagType only
 | |
|       if(localKeyId === undefined && !('friendlyName' in filter) &&
 | |
|         'bagType' in filter) {
 | |
|         rval[filter.bagType] = _getBagsByAttribute(
 | |
|           pfx.safeContents, null, null, filter.bagType);
 | |
|       }
 | |
| 
 | |
|       if(localKeyId !== undefined) {
 | |
|         rval.localKeyId = _getBagsByAttribute(
 | |
|           pfx.safeContents, 'localKeyId',
 | |
|           localKeyId, filter.bagType);
 | |
|       }
 | |
|       if('friendlyName' in filter) {
 | |
|         rval.friendlyName = _getBagsByAttribute(
 | |
|           pfx.safeContents, 'friendlyName',
 | |
|           filter.friendlyName, filter.bagType);
 | |
|       }
 | |
| 
 | |
|       return rval;
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * DEPRECATED: use getBags() instead.
 | |
|      *
 | |
|      * Get bags with matching friendlyName attribute.
 | |
|      *
 | |
|      * @param friendlyName the friendly name to search for.
 | |
|      * @param [bagType] bag type to narrow search by.
 | |
|      *
 | |
|      * @return an array of bags with matching friendlyName attribute.
 | |
|      */
 | |
|     getBagsByFriendlyName: function(friendlyName, bagType) {
 | |
|       return _getBagsByAttribute(
 | |
|         pfx.safeContents, 'friendlyName', friendlyName, bagType);
 | |
|     },
 | |
| 
 | |
|     /**
 | |
|      * DEPRECATED: use getBags() instead.
 | |
|      *
 | |
|      * Get bags with matching localKeyId attribute.
 | |
|      *
 | |
|      * @param localKeyId the localKeyId to search for.
 | |
|      * @param [bagType] bag type to narrow search by.
 | |
|      *
 | |
|      * @return an array of bags with matching localKeyId attribute.
 | |
|      */
 | |
|     getBagsByLocalKeyId: function(localKeyId, bagType) {
 | |
|       return _getBagsByAttribute(
 | |
|         pfx.safeContents, 'localKeyId', localKeyId, bagType);
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   if(capture.version.charCodeAt(0) !== 3) {
 | |
|     var error = new Error('PKCS#12 PFX of version other than 3 not supported.');
 | |
|     error.version = capture.version.charCodeAt(0);
 | |
|     throw error;
 | |
|   }
 | |
| 
 | |
|   if(asn1.derToOid(capture.contentType) !== pki.oids.data) {
 | |
|     var error = new Error('Only PKCS#12 PFX in password integrity mode supported.');
 | |
|     error.oid = asn1.derToOid(capture.contentType);
 | |
|     throw error;
 | |
|   }
 | |
| 
 | |
|   var data = capture.content.value[0];
 | |
|   if(data.tagClass !== asn1.Class.UNIVERSAL ||
 | |
|      data.type !== asn1.Type.OCTETSTRING) {
 | |
|     throw new Error('PKCS#12 authSafe content data is not an OCTET STRING.');
 | |
|   }
 | |
|   data = _decodePkcs7Data(data);
 | |
| 
 | |
|   // check for MAC
 | |
|   if(capture.mac) {
 | |
|     var md = null;
 | |
|     var macKeyBytes = 0;
 | |
|     var macAlgorithm = asn1.derToOid(capture.macAlgorithm);
 | |
|     switch(macAlgorithm) {
 | |
|     case pki.oids.sha1:
 | |
|       md = forge.md.sha1.create();
 | |
|       macKeyBytes = 20;
 | |
|       break;
 | |
|     case pki.oids.sha256:
 | |
|       md = forge.md.sha256.create();
 | |
|       macKeyBytes = 32;
 | |
|       break;
 | |
|     case pki.oids.sha384:
 | |
|       md = forge.md.sha384.create();
 | |
|       macKeyBytes = 48;
 | |
|       break;
 | |
|     case pki.oids.sha512:
 | |
|       md = forge.md.sha512.create();
 | |
|       macKeyBytes = 64;
 | |
|       break;
 | |
|     case pki.oids.md5:
 | |
|       md = forge.md.md5.create();
 | |
|       macKeyBytes = 16;
 | |
|       break;
 | |
|     }
 | |
|     if(md === null) {
 | |
|       throw new Error('PKCS#12 uses unsupported MAC algorithm: ' + macAlgorithm);
 | |
|     }
 | |
| 
 | |
|     // verify MAC (iterations default to 1)
 | |
|     var macSalt = new forge.util.ByteBuffer(capture.macSalt);
 | |
|     var macIterations = (('macIterations' in capture) ?
 | |
|       parseInt(forge.util.bytesToHex(capture.macIterations), 16) : 1);
 | |
|     var macKey = p12.generateKey(
 | |
|       password, macSalt, 3, macIterations, macKeyBytes, md);
 | |
|     var mac = forge.hmac.create();
 | |
|     mac.start(md, macKey);
 | |
|     mac.update(data.value);
 | |
|     var macValue = mac.getMac();
 | |
|     if(macValue.getBytes() !== capture.macDigest) {
 | |
|       throw new Error('PKCS#12 MAC could not be verified. Invalid password?');
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   _decodeAuthenticatedSafe(pfx, data.value, strict, password);
 | |
|   return pfx;
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Decodes PKCS#7 Data. PKCS#7 (RFC 2315) defines "Data" as an OCTET STRING,
 | |
|  * but it is sometimes an OCTET STRING that is composed/constructed of chunks,
 | |
|  * each its own OCTET STRING. This is BER-encoding vs. DER-encoding. This
 | |
|  * function transforms this corner-case into the usual simple,
 | |
|  * non-composed/constructed OCTET STRING.
 | |
|  *
 | |
|  * This function may be moved to ASN.1 at some point to better deal with
 | |
|  * more BER-encoding issues, should they arise.
 | |
|  *
 | |
|  * @param data the ASN.1 Data object to transform.
 | |
|  */
 | |
| function _decodePkcs7Data(data) {
 | |
|   // handle special case of "chunked" data content: an octet string composed
 | |
|   // of other octet strings
 | |
|   if(data.composed || data.constructed) {
 | |
|     var value = forge.util.createBuffer();
 | |
|     for(var i = 0; i < data.value.length; ++i) {
 | |
|       value.putBytes(data.value[i].value);
 | |
|     }
 | |
|     data.composed = data.constructed = false;
 | |
|     data.value = value.getBytes();
 | |
|   }
 | |
|   return data;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Decode PKCS#12 AuthenticatedSafe (BER encoded) into PFX object.
 | |
|  *
 | |
|  * The AuthenticatedSafe is a BER-encoded SEQUENCE OF ContentInfo.
 | |
|  *
 | |
|  * @param pfx The PKCS#12 PFX object to fill.
 | |
|  * @param {String} authSafe BER-encoded AuthenticatedSafe.
 | |
|  * @param strict true to use strict DER decoding, false not to.
 | |
|  * @param {String} password Password to decrypt with (optional).
 | |
|  */
 | |
| function _decodeAuthenticatedSafe(pfx, authSafe, strict, password) {
 | |
|   authSafe = asn1.fromDer(authSafe, strict);  /* actually it's BER encoded */
 | |
| 
 | |
|   if(authSafe.tagClass !== asn1.Class.UNIVERSAL ||
 | |
|      authSafe.type !== asn1.Type.SEQUENCE ||
 | |
|      authSafe.constructed !== true) {
 | |
|     throw new Error('PKCS#12 AuthenticatedSafe expected to be a ' +
 | |
|       'SEQUENCE OF ContentInfo');
 | |
|   }
 | |
| 
 | |
|   for(var i = 0; i < authSafe.value.length; i++) {
 | |
|     var contentInfo = authSafe.value[i];
 | |
| 
 | |
|     // validate contentInfo and capture data
 | |
|     var capture = {};
 | |
|     var errors = [];
 | |
|     if(!asn1.validate(contentInfo, contentInfoValidator, capture, errors)) {
 | |
|       var error = new Error('Cannot read ContentInfo.');
 | |
|       error.errors = errors;
 | |
|       throw error;
 | |
|     }
 | |
| 
 | |
|     var obj = {
 | |
|       encrypted: false
 | |
|     };
 | |
|     var safeContents = null;
 | |
|     var data = capture.content.value[0];
 | |
|     switch(asn1.derToOid(capture.contentType)) {
 | |
|     case pki.oids.data:
 | |
|       if(data.tagClass !== asn1.Class.UNIVERSAL ||
 | |
|          data.type !== asn1.Type.OCTETSTRING) {
 | |
|         throw new Error('PKCS#12 SafeContents Data is not an OCTET STRING.');
 | |
|       }
 | |
|       safeContents = _decodePkcs7Data(data).value;
 | |
|       break;
 | |
|     case pki.oids.encryptedData:
 | |
|       safeContents = _decryptSafeContents(data, password);
 | |
|       obj.encrypted = true;
 | |
|       break;
 | |
|     default:
 | |
|       var error = new Error('Unsupported PKCS#12 contentType.');
 | |
|       error.contentType = asn1.derToOid(capture.contentType);
 | |
|       throw error;
 | |
|     }
 | |
| 
 | |
|     obj.safeBags = _decodeSafeContents(safeContents, strict, password);
 | |
|     pfx.safeContents.push(obj);
 | |
|   }
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Decrypt PKCS#7 EncryptedData structure.
 | |
|  *
 | |
|  * @param data ASN.1 encoded EncryptedContentInfo object.
 | |
|  * @param password The user-provided password.
 | |
|  *
 | |
|  * @return The decrypted SafeContents (ASN.1 object).
 | |
|  */
 | |
| function _decryptSafeContents(data, password) {
 | |
|   var capture = {};
 | |
|   var errors = [];
 | |
|   if(!asn1.validate(
 | |
|     data, forge.pkcs7.asn1.encryptedDataValidator, capture, errors)) {
 | |
|     var error = new Error('Cannot read EncryptedContentInfo.');
 | |
|     error.errors = errors;
 | |
|     throw error;
 | |
|   }
 | |
| 
 | |
|   var oid = asn1.derToOid(capture.contentType);
 | |
|   if(oid !== pki.oids.data) {
 | |
|     var error = new Error(
 | |
|       'PKCS#12 EncryptedContentInfo ContentType is not Data.');
 | |
|     error.oid = oid;
 | |
|     throw error;
 | |
|   }
 | |
| 
 | |
|   // get cipher
 | |
|   oid = asn1.derToOid(capture.encAlgorithm);
 | |
|   var cipher = pki.pbe.getCipher(oid, capture.encParameter, password);
 | |
| 
 | |
|   // get encrypted data
 | |
|   var encryptedContentAsn1 = _decodePkcs7Data(capture.encryptedContentAsn1);
 | |
|   var encrypted = forge.util.createBuffer(encryptedContentAsn1.value);
 | |
| 
 | |
|   cipher.update(encrypted);
 | |
|   if(!cipher.finish()) {
 | |
|     throw new Error('Failed to decrypt PKCS#12 SafeContents.');
 | |
|   }
 | |
| 
 | |
|   return cipher.output.getBytes();
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Decode PKCS#12 SafeContents (BER-encoded) into array of Bag objects.
 | |
|  *
 | |
|  * The safeContents is a BER-encoded SEQUENCE OF SafeBag.
 | |
|  *
 | |
|  * @param {String} safeContents BER-encoded safeContents.
 | |
|  * @param strict true to use strict DER decoding, false not to.
 | |
|  * @param {String} password Password to decrypt with (optional).
 | |
|  *
 | |
|  * @return {Array} Array of Bag objects.
 | |
|  */
 | |
| function _decodeSafeContents(safeContents, strict, password) {
 | |
|   // if strict and no safe contents, return empty safes
 | |
|   if(!strict && safeContents.length === 0) {
 | |
|     return [];
 | |
|   }
 | |
| 
 | |
|   // actually it's BER-encoded
 | |
|   safeContents = asn1.fromDer(safeContents, strict);
 | |
| 
 | |
|   if(safeContents.tagClass !== asn1.Class.UNIVERSAL ||
 | |
|     safeContents.type !== asn1.Type.SEQUENCE ||
 | |
|     safeContents.constructed !== true) {
 | |
|     throw new Error(
 | |
|       'PKCS#12 SafeContents expected to be a SEQUENCE OF SafeBag.');
 | |
|   }
 | |
| 
 | |
|   var res = [];
 | |
|   for(var i = 0; i < safeContents.value.length; i++) {
 | |
|     var safeBag = safeContents.value[i];
 | |
| 
 | |
|     // validate SafeBag and capture data
 | |
|     var capture = {};
 | |
|     var errors = [];
 | |
|     if(!asn1.validate(safeBag, safeBagValidator, capture, errors)) {
 | |
|       var error = new Error('Cannot read SafeBag.');
 | |
|       error.errors = errors;
 | |
|       throw error;
 | |
|     }
 | |
| 
 | |
|     /* Create bag object and push to result array. */
 | |
|     var bag = {
 | |
|       type: asn1.derToOid(capture.bagId),
 | |
|       attributes: _decodeBagAttributes(capture.bagAttributes)
 | |
|     };
 | |
|     res.push(bag);
 | |
| 
 | |
|     var validator, decoder;
 | |
|     var bagAsn1 = capture.bagValue.value[0];
 | |
|     switch(bag.type) {
 | |
|       case pki.oids.pkcs8ShroudedKeyBag:
 | |
|         /* bagAsn1 has a EncryptedPrivateKeyInfo, which we need to decrypt.
 | |
|            Afterwards we can handle it like a keyBag,
 | |
|            which is a PrivateKeyInfo. */
 | |
|         bagAsn1 = pki.decryptPrivateKeyInfo(bagAsn1, password);
 | |
|         if(bagAsn1 === null) {
 | |
|           throw new Error(
 | |
|             'Unable to decrypt PKCS#8 ShroudedKeyBag, wrong password?');
 | |
|         }
 | |
| 
 | |
|         /* fall through */
 | |
|       case pki.oids.keyBag:
 | |
|         /* A PKCS#12 keyBag is a simple PrivateKeyInfo as understood by our
 | |
|            PKI module, hence we don't have to do validation/capturing here,
 | |
|            just pass what we already got. */
 | |
|         try {
 | |
|           bag.key = pki.privateKeyFromAsn1(bagAsn1);
 | |
|         } catch(e) {
 | |
|           // ignore unknown key type, pass asn1 value
 | |
|           bag.key = null;
 | |
|           bag.asn1 = bagAsn1;
 | |
|         }
 | |
|         continue;  /* Nothing more to do. */
 | |
| 
 | |
|       case pki.oids.certBag:
 | |
|         /* A PKCS#12 certBag can wrap both X.509 and sdsi certificates.
 | |
|            Therefore put the SafeBag content through another validator to
 | |
|            capture the fields.  Afterwards check & store the results. */
 | |
|         validator = certBagValidator;
 | |
|         decoder = function() {
 | |
|           if(asn1.derToOid(capture.certId) !== pki.oids.x509Certificate) {
 | |
|             var error = new Error(
 | |
|               'Unsupported certificate type, only X.509 supported.');
 | |
|             error.oid = asn1.derToOid(capture.certId);
 | |
|             throw error;
 | |
|           }
 | |
| 
 | |
|           // true=produce cert hash
 | |
|           var certAsn1 = asn1.fromDer(capture.cert, strict);
 | |
|           try {
 | |
|             bag.cert = pki.certificateFromAsn1(certAsn1, true);
 | |
|           } catch(e) {
 | |
|             // ignore unknown cert type, pass asn1 value
 | |
|             bag.cert = null;
 | |
|             bag.asn1 = certAsn1;
 | |
|           }
 | |
|         };
 | |
|         break;
 | |
| 
 | |
|       default:
 | |
|         var error = new Error('Unsupported PKCS#12 SafeBag type.');
 | |
|         error.oid = bag.type;
 | |
|         throw error;
 | |
|     }
 | |
| 
 | |
|     /* Validate SafeBag value (i.e. CertBag, etc.) and capture data if needed. */
 | |
|     if(validator !== undefined &&
 | |
|        !asn1.validate(bagAsn1, validator, capture, errors)) {
 | |
|       var error = new Error('Cannot read PKCS#12 ' + validator.name);
 | |
|       error.errors = errors;
 | |
|       throw error;
 | |
|     }
 | |
| 
 | |
|     /* Call decoder function from above to store the results. */
 | |
|     decoder();
 | |
|   }
 | |
| 
 | |
|   return res;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Decode PKCS#12 SET OF PKCS12Attribute into JavaScript object.
 | |
|  *
 | |
|  * @param attributes SET OF PKCS12Attribute (ASN.1 object).
 | |
|  *
 | |
|  * @return the decoded attributes.
 | |
|  */
 | |
| function _decodeBagAttributes(attributes) {
 | |
|   var decodedAttrs = {};
 | |
| 
 | |
|   if(attributes !== undefined) {
 | |
|     for(var i = 0; i < attributes.length; ++i) {
 | |
|       var capture = {};
 | |
|       var errors = [];
 | |
|       if(!asn1.validate(attributes[i], attributeValidator, capture, errors)) {
 | |
|         var error = new Error('Cannot read PKCS#12 BagAttribute.');
 | |
|         error.errors = errors;
 | |
|         throw error;
 | |
|       }
 | |
| 
 | |
|       var oid = asn1.derToOid(capture.oid);
 | |
|       if(pki.oids[oid] === undefined) {
 | |
|         // unsupported attribute type, ignore.
 | |
|         continue;
 | |
|       }
 | |
| 
 | |
|       decodedAttrs[pki.oids[oid]] = [];
 | |
|       for(var j = 0; j < capture.values.length; ++j) {
 | |
|         decodedAttrs[pki.oids[oid]].push(capture.values[j].value);
 | |
|       }
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return decodedAttrs;
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * Wraps a private key and certificate in a PKCS#12 PFX wrapper. If a
 | |
|  * password is provided then the private key will be encrypted.
 | |
|  *
 | |
|  * An entire certificate chain may also be included. To do this, pass
 | |
|  * an array for the "cert" parameter where the first certificate is
 | |
|  * the one that is paired with the private key and each subsequent one
 | |
|  * verifies the previous one. The certificates may be in PEM format or
 | |
|  * have been already parsed by Forge.
 | |
|  *
 | |
|  * @todo implement password-based-encryption for the whole package
 | |
|  *
 | |
|  * @param key the private key.
 | |
|  * @param cert the certificate (may be an array of certificates in order
 | |
|  *          to specify a certificate chain).
 | |
|  * @param password the password to use, null for none.
 | |
|  * @param options:
 | |
|  *          algorithm the encryption algorithm to use
 | |
|  *            ('aes128', 'aes192', 'aes256', '3des'), defaults to 'aes128'.
 | |
|  *          count the iteration count to use.
 | |
|  *          saltSize the salt size to use.
 | |
|  *          useMac true to include a MAC, false not to, defaults to true.
 | |
|  *          localKeyId the local key ID to use, in hex.
 | |
|  *          friendlyName the friendly name to use.
 | |
|  *          generateLocalKeyId true to generate a random local key ID,
 | |
|  *            false not to, defaults to true.
 | |
|  *
 | |
|  * @return the PKCS#12 PFX ASN.1 object.
 | |
|  */
 | |
| p12.toPkcs12Asn1 = function(key, cert, password, options) {
 | |
|   // set default options
 | |
|   options = options || {};
 | |
|   options.saltSize = options.saltSize || 8;
 | |
|   options.count = options.count || 2048;
 | |
|   options.algorithm = options.algorithm || options.encAlgorithm || 'aes128';
 | |
|   if(!('useMac' in options)) {
 | |
|     options.useMac = true;
 | |
|   }
 | |
|   if(!('localKeyId' in options)) {
 | |
|     options.localKeyId = null;
 | |
|   }
 | |
|   if(!('generateLocalKeyId' in options)) {
 | |
|     options.generateLocalKeyId = true;
 | |
|   }
 | |
| 
 | |
|   var localKeyId = options.localKeyId;
 | |
|   var bagAttrs;
 | |
|   if(localKeyId !== null) {
 | |
|     localKeyId = forge.util.hexToBytes(localKeyId);
 | |
|   } else if(options.generateLocalKeyId) {
 | |
|     // use SHA-1 of paired cert, if available
 | |
|     if(cert) {
 | |
|       var pairedCert = forge.util.isArray(cert) ? cert[0] : cert;
 | |
|       if(typeof pairedCert === 'string') {
 | |
|         pairedCert = pki.certificateFromPem(pairedCert);
 | |
|       }
 | |
|       var sha1 = forge.md.sha1.create();
 | |
|       sha1.update(asn1.toDer(pki.certificateToAsn1(pairedCert)).getBytes());
 | |
|       localKeyId = sha1.digest().getBytes();
 | |
|     } else {
 | |
|       // FIXME: consider using SHA-1 of public key (which can be generated
 | |
|       // from private key components), see: cert.generateSubjectKeyIdentifier
 | |
|       // generate random bytes
 | |
|       localKeyId = forge.random.getBytes(20);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   var attrs = [];
 | |
|   if(localKeyId !== null) {
 | |
|     attrs.push(
 | |
|       // localKeyID
 | |
|       asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | |
|         // attrId
 | |
|         asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | |
|           asn1.oidToDer(pki.oids.localKeyId).getBytes()),
 | |
|         // attrValues
 | |
|         asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
 | |
|           asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
 | |
|             localKeyId)
 | |
|         ])
 | |
|       ]));
 | |
|   }
 | |
|   if('friendlyName' in options) {
 | |
|     attrs.push(
 | |
|       // friendlyName
 | |
|       asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | |
|         // attrId
 | |
|         asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | |
|           asn1.oidToDer(pki.oids.friendlyName).getBytes()),
 | |
|         // attrValues
 | |
|         asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
 | |
|           asn1.create(asn1.Class.UNIVERSAL, asn1.Type.BMPSTRING, false,
 | |
|             options.friendlyName)
 | |
|         ])
 | |
|       ]));
 | |
|   }
 | |
| 
 | |
|   if(attrs.length > 0) {
 | |
|     bagAttrs = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, attrs);
 | |
|   }
 | |
| 
 | |
|   // collect contents for AuthenticatedSafe
 | |
|   var contents = [];
 | |
| 
 | |
|   // create safe bag(s) for certificate chain
 | |
|   var chain = [];
 | |
|   if(cert !== null) {
 | |
|     if(forge.util.isArray(cert)) {
 | |
|       chain = cert;
 | |
|     } else {
 | |
|       chain = [cert];
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   var certSafeBags = [];
 | |
|   for(var i = 0; i < chain.length; ++i) {
 | |
|     // convert cert from PEM as necessary
 | |
|     cert = chain[i];
 | |
|     if(typeof cert === 'string') {
 | |
|       cert = pki.certificateFromPem(cert);
 | |
|     }
 | |
| 
 | |
|     // SafeBag
 | |
|     var certBagAttrs = (i === 0) ? bagAttrs : undefined;
 | |
|     var certAsn1 = pki.certificateToAsn1(cert);
 | |
|     var certSafeBag =
 | |
|       asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | |
|         // bagId
 | |
|         asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | |
|           asn1.oidToDer(pki.oids.certBag).getBytes()),
 | |
|         // bagValue
 | |
|         asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
 | |
|           // CertBag
 | |
|           asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | |
|             // certId
 | |
|             asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | |
|               asn1.oidToDer(pki.oids.x509Certificate).getBytes()),
 | |
|             // certValue (x509Certificate)
 | |
|             asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
 | |
|               asn1.create(
 | |
|                 asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
 | |
|                 asn1.toDer(certAsn1).getBytes())
 | |
|             ])])]),
 | |
|         // bagAttributes (OPTIONAL)
 | |
|         certBagAttrs
 | |
|       ]);
 | |
|     certSafeBags.push(certSafeBag);
 | |
|   }
 | |
| 
 | |
|   if(certSafeBags.length > 0) {
 | |
|     // SafeContents
 | |
|     var certSafeContents = asn1.create(
 | |
|       asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, certSafeBags);
 | |
| 
 | |
|     // ContentInfo
 | |
|     var certCI =
 | |
|       // PKCS#7 ContentInfo
 | |
|       asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | |
|         // contentType
 | |
|         asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | |
|           // OID for the content type is 'data'
 | |
|           asn1.oidToDer(pki.oids.data).getBytes()),
 | |
|         // content
 | |
|         asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
 | |
|           asn1.create(
 | |
|             asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
 | |
|             asn1.toDer(certSafeContents).getBytes())
 | |
|         ])
 | |
|       ]);
 | |
|     contents.push(certCI);
 | |
|   }
 | |
| 
 | |
|   // create safe contents for private key
 | |
|   var keyBag = null;
 | |
|   if(key !== null) {
 | |
|     // SafeBag
 | |
|     var pkAsn1 = pki.wrapRsaPrivateKey(pki.privateKeyToAsn1(key));
 | |
|     if(password === null) {
 | |
|       // no encryption
 | |
|       keyBag = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | |
|         // bagId
 | |
|         asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | |
|           asn1.oidToDer(pki.oids.keyBag).getBytes()),
 | |
|         // bagValue
 | |
|         asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
 | |
|           // PrivateKeyInfo
 | |
|           pkAsn1
 | |
|         ]),
 | |
|         // bagAttributes (OPTIONAL)
 | |
|         bagAttrs
 | |
|       ]);
 | |
|     } else {
 | |
|       // encrypted PrivateKeyInfo
 | |
|       keyBag = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | |
|         // bagId
 | |
|         asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | |
|           asn1.oidToDer(pki.oids.pkcs8ShroudedKeyBag).getBytes()),
 | |
|         // bagValue
 | |
|         asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
 | |
|           // EncryptedPrivateKeyInfo
 | |
|           pki.encryptPrivateKeyInfo(pkAsn1, password, options)
 | |
|         ]),
 | |
|         // bagAttributes (OPTIONAL)
 | |
|         bagAttrs
 | |
|       ]);
 | |
|     }
 | |
| 
 | |
|     // SafeContents
 | |
|     var keySafeContents =
 | |
|       asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [keyBag]);
 | |
| 
 | |
|     // ContentInfo
 | |
|     var keyCI =
 | |
|       // PKCS#7 ContentInfo
 | |
|       asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | |
|         // contentType
 | |
|         asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | |
|           // OID for the content type is 'data'
 | |
|           asn1.oidToDer(pki.oids.data).getBytes()),
 | |
|         // content
 | |
|         asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
 | |
|           asn1.create(
 | |
|             asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
 | |
|             asn1.toDer(keySafeContents).getBytes())
 | |
|         ])
 | |
|       ]);
 | |
|     contents.push(keyCI);
 | |
|   }
 | |
| 
 | |
|   // create AuthenticatedSafe by stringing together the contents
 | |
|   var safe = asn1.create(
 | |
|     asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, contents);
 | |
| 
 | |
|   var macData;
 | |
|   if(options.useMac) {
 | |
|     // MacData
 | |
|     var sha1 = forge.md.sha1.create();
 | |
|     var macSalt = new forge.util.ByteBuffer(
 | |
|       forge.random.getBytes(options.saltSize));
 | |
|     var count = options.count;
 | |
|     // 160-bit key
 | |
|     var key = p12.generateKey(password, macSalt, 3, count, 20);
 | |
|     var mac = forge.hmac.create();
 | |
|     mac.start(sha1, key);
 | |
|     mac.update(asn1.toDer(safe).getBytes());
 | |
|     var macValue = mac.getMac();
 | |
|     macData = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | |
|       // mac DigestInfo
 | |
|       asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | |
|         // digestAlgorithm
 | |
|         asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | |
|           // algorithm = SHA-1
 | |
|           asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | |
|             asn1.oidToDer(pki.oids.sha1).getBytes()),
 | |
|           // parameters = Null
 | |
|           asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
 | |
|         ]),
 | |
|         // digest
 | |
|         asn1.create(
 | |
|           asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING,
 | |
|           false, macValue.getBytes())
 | |
|       ]),
 | |
|       // macSalt OCTET STRING
 | |
|       asn1.create(
 | |
|         asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, macSalt.getBytes()),
 | |
|       // iterations INTEGER (XXX: Only support count < 65536)
 | |
|       asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
 | |
|         asn1.integerToDer(count).getBytes()
 | |
|       )
 | |
|     ]);
 | |
|   }
 | |
| 
 | |
|   // PFX
 | |
|   return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | |
|     // version (3)
 | |
|     asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
 | |
|       asn1.integerToDer(3).getBytes()),
 | |
|     // PKCS#7 ContentInfo
 | |
|     asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | |
|       // contentType
 | |
|       asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | |
|         // OID for the content type is 'data'
 | |
|         asn1.oidToDer(pki.oids.data).getBytes()),
 | |
|       // content
 | |
|       asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
 | |
|         asn1.create(
 | |
|           asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
 | |
|           asn1.toDer(safe).getBytes())
 | |
|       ])
 | |
|     ]),
 | |
|     macData
 | |
|   ]);
 | |
| };
 | |
| 
 | |
| /**
 | |
|  * Derives a PKCS#12 key.
 | |
|  *
 | |
|  * @param password the password to derive the key material from, null or
 | |
|  *          undefined for none.
 | |
|  * @param salt the salt, as a ByteBuffer, to use.
 | |
|  * @param id the PKCS#12 ID byte (1 = key material, 2 = IV, 3 = MAC).
 | |
|  * @param iter the iteration count.
 | |
|  * @param n the number of bytes to derive from the password.
 | |
|  * @param md the message digest to use, defaults to SHA-1.
 | |
|  *
 | |
|  * @return a ByteBuffer with the bytes derived from the password.
 | |
|  */
 | |
| p12.generateKey = forge.pbe.generatePkcs12Key;
 |