| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074 | /** * 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 APIvar asn1 = forge.asn1;var pki = forge.pki;// shortcut for PKCS#12 APIvar 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;
 |