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.
		
		
		
		
		
			
		
			
				
					
					
						
							1074 lines
						
					
					
						
							33 KiB
						
					
					
				
			
		
		
	
	
							1074 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;
 | 
						|
 |