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;
 |