text: /**
   * @file Implements the Cryptographic Message Syntax as specified in RFC-2630.
   * @version 1.76
   * @copyright 2014-2016, Rudolf Nickolaev. All rights reserved.
   */
  
  /*
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *     http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   *    
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions are met:
   * 
   * 1. Redistributions of source code must retain the above copyright notice, this 
   *    list of conditions and the following disclaimer.
   *    
   * 2. Redistributions in binary form must reproduce the above copyright notice,
   *    this list of conditions and the following disclaimer in the documentation
   *    and/or other materials provided with the distribution.
   *    
   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
   * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
   * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
   * 
   */
  
  (function (root, factory) {
  
      /*
       * Module imports and exports
       * 
       */ // <editor-fold defaultstate="collapsed">
      if (typeof define === 'function' && define.amd) {
          define(['./gostCrypto', './gostASN1', './gostCert'], factory);
      } else if (typeof exports === 'object') {
          module.exports = factory(require('./gostCrypto'), require('./gostASN1'), require('./gostCert'));
      } else {
          root.GostCMS = factory(root.gostCrypto, root.GostASN1, root.GostCert);
      }
      // </editor-fold>
  
  }(this, function (gostCrypto) {
  
      /*
       * Common algorithms
       */ // <editor-fold defaultstate="collapsed">
      var root = this;
      var Promise = root.Promise;
      var Object = root.Object;
      var CryptoOperationData = root.ArrayBuffer;
      var Date = root.Date;
  
      var subtle = gostCrypto.subtle;
      var asn1 = gostCrypto.asn1;
      var coding = gostCrypto.coding;
      var cert = gostCrypto.cert;
      var providers = gostCrypto.security.providers;
  
      // Expand javascript object
      function expand() {
          var r = {};
          for (var i = 0, n = arguments.length; i < n; i++) {
              var item = arguments[i];
              if (typeof item === 'object')
                  for (var name in item)
                      if (item.hasOwnProperty(name))
                          r[name] = item[name];
          }
          return r;
      }
  
      function defineProperty(object, name, descriptor, enumerable) {
          if (typeof descriptor !== 'object')
              descriptor = {value: descriptor};
          if (enumerable !== undefined)
              descriptor.enumerable = enumerable;
          Object.defineProperty(object, name, descriptor);
      }
  
      function defineProperties(object, properties, enumerable) {
          for (var name in properties)
              defineProperty(object, name, properties[name], enumerable);
      }
  
      // Extend javascript class
      function extend(Super, Class, propertiesObject, propertiesClass) {
          // If constructor not defined
          if (typeof Class !== 'function') {
              propertiesClass = propertiesObject;
              propertiesObject = Class;
              Class = function () {
                  Super.apply(this, arguments);
              };
          }
          // Create prototype properties
          Class.prototype = Object.create(Super.prototype, {
              constructor: {
                  value: Class
              },
              superclass: {
                  value: Super.prototype
              }
          });
          if (propertiesObject)
              defineProperties(Class.prototype, propertiesObject, true);
          // Inherites super class properties
          if (Super !== Object)
              for (var name in Super)
                  Class[name] = Super[name];
          Class.super = Super;
          if (propertiesClass)
              defineProperties(Class, propertiesClass, true);
          return Class;
      }
  
      // Self resolver
      function call(callback) {
          try {
              callback();
          } catch (e) {
          }
      }
  
      // Check the buffers to equal
      function equalBuffers(r1, r2) {
          var s1 = new Uint8Array(r1),
                  s2 = new Uint8Array(r2);
          if (s1.length !== s2.length)
              return false;
          for (var i = 0, n = s1.length; i < n; i++)
              if (s1[i] !== s2[i])
                  return false;
          return true;
      }
  
      // True if equal numbers
      var equalNumbers = (function () {
          // Convert number to bigendian hex string
          var hex = function (s) {
              var t = typeof s;
              return t === 'undefined' || s === '' ? '0' :
                      t === 'number' || s instanceof Number ? s.toString(16).toLowerCase() :
                      s.replace('0x', '').toLowerCase();
          };
          // Zero left padding
          var lpad = function (s, size) {
              return (new Array(size + 1).join('0') + s).slice(-size);
          };
          return function (s1, s2) {
              s1 = hex(s1);
              s2 = hex(s2);
              var len = Math.max(s1.length, s2.length);
              return lpad(s1, len) === lpad(s2, len);
          };
      })();
  
      // Check equal names
      function equalNames(name1, name2) {
          for (var key in name1)
              if (name1[key] !== name2[key])
                  return false;
          for (var key in name2)
              if (name1[key] !== name2[key])
                  return false;
          return true;
      }
  
      // Add unique value to array
      function addUnique(array, item, comparator) {
          var found = false;
          for (var i = 0, n = array.length; i < n; i++)
              if (comparator(array[i], item)) {
                  found = true;
                  break;
              }
          if (!found)
              array.push(item);
      }
  
      // Set content data
      function setContentData(object, data) {
          var content = object.content;
          switch (object.contentType) {
              case 'data':
                  object.content = data.content;
                  break;
              case 'digestedData':
              case 'signedData':
              case 'authData':
                  content.encapContentInfo = {
                      eContentType: data.contentType,
                      eContent: data.content
                  };
                  break;
              case 'envelopedData':
              case 'encryptedData':
                  content.encryptedContentInfo = {
                      contentType: data.contentType,
                      encryptedContent: data.content
                  };
                  break;
          }
      }
  
      // Get content data
      function getContentData(object) {
          var content = object.content;
          switch (object.contentType) {
              case 'data':
                  return {
                      contentType: object.contentType,
                      content: object.content
                  };
              case 'digestedData':
              case 'signedData':
              case 'authData':
                  var encap = content.encapContentInfo;
                  return  {
                      contentType: encap.eContentType,
                      content: encap.eContent
                  };
              case 'envelopedData':
              case 'encryptedData':
                  var enc = content.encryptedContentInfo;
                  return {
                      contentType: enc.contentType,
                      content: enc.encryptedContent
                  };
          }
      }
  
      // Check content info type
      function checkContentInfo(contentInfo) {
          var content, contentType;
          if (contentInfo) {
              if (typeof contentInfo === 'string')
                  try {
                      contentInfo = coding.PEM.decode(contentInfo);
                  } catch (e1) {
                      contentInfo = coding.Chars.decode(contentInfo);
                  }
              if (contentInfo instanceof CryptoOperationData)
                  try {
                      contentInfo = asn1.ContentInfo.decode(contentInfo);
                  } catch (e) {
                      contentInfo = {contentType: 'data', content: contentInfo};
                  }
              contentType = contentInfo.contentType;
              if (!contentType)
                  throw new Error('Invalid content object');
              content = contentInfo.content;
              if (!(content instanceof CryptoOperationData))
                  content = content.encode();
              return {contentType: contentType, content: content};
          } else
              contentInfo = {contentType: 'data'};
          return contentInfo;
      }
  
      function createContentInfo(contentInfo) {
          try {
              // Some provider has mistake to envelop ContentInfo enstead 
              // content field of ContentInfo
              contentInfo = new asn1.ContentInfo(contentInfo.content, true);
          } catch (e) {
          }
          // Create situable content info object
          switch (contentInfo.contentType) {
              case 'data':
                  return new DataContentInfo(contentInfo);
              case 'digestedData':
                  return new DigestedDataContentInfo(contentInfo);
              case 'signedData':
                  return new SignedDataContentInfo(contentInfo);
              case 'encryptedData':
                  return new EncryptedDataContentInfo(contentInfo);
              case 'envelopedData':
                  return new EnvelopedDataContentInfo(contentInfo);
              default:
                  return new asn1.ContentInfo(contentInfo);
          }
      }
      ;
  
      function matchCert(id, cert) {
          return (id instanceof CryptoOperationData ? cert.extensions &&
                  equalBuffers(id, cert.extensions.subjectKeyIdentifier) :
                  equalNames(cert.issuer, id.issuer) &&
                  equalNumbers(cert.serialNumber, id.serialNumber));
      }
  
      // Get random values
      function getSeed(length) {
          var seed = new Uint8Array(length);
          gostCrypto.getRandomValues(seed);
          return seed.buffer;
      }
  
      // Salt size
      function saltSize(algorithm) {
          switch (algorithm.id) {
              case 'pbeWithSHAAnd40BitRC2-CBC':
              case 'pbeWithSHAAnd128BitRC2-CBC':
                  return 8;
              case 'pbeUnknownGost':
                  return 16;
              case 'sha1':
                  return 20;
              default:
                  return 32;
          }
      }
  
      // Password to bytes
      function passwordData(derivation, password) {
          if (!password)
              return new CryptoOperationData(0);
          if (password instanceof CryptoOperationData)
              return password;
          if (typeof password !== 'string')
              throw new Error('The password must be string or raw data type');
          if (derivation.name.indexOf('CPKDF') >= 0) {
              // CryptoPro store password
              var r = [];
              for (var i = 0; i < password.length; i++) {
                  var c = password.charCodeAt(i);
                  r.push(c & 0xff);
                  r.push(c >>> 8 & 0xff);
                  r.push(0);
                  r.push(0);
              }
              return new Uint8Array(r).buffer;
          } else if (derivation.name.indexOf('PFXKDF') >= 0)
              // PKCS#12 unicode password
              return coding.Chars.decode(password + '\0', 'unicode');
          else
              // PKCS#5 password mode
              return coding.Chars.decode(password, 'utf8');
      }
  
      // Define provider for encription algorithm
      function encryptionProvider(algorithm) {
          var id = algorithm.id;
          for (var name in providers) {
              var provider = providers[name];
              if (provider.encryption.id === id)
                  return provider;
          }
      }
  
      // </editor-fold>
  
      /**
       * Provides facilities for handling certificates, CRLs, etc.
       * @class GostCMS
       */
      function GostCMS() {
      }
  
      /**
       * Message templates
       * <ul>
       *      <li>providerName - provider name for key generation, default 'CP-01'</li>
       *      <li>autoAddCert - automatic add signer certificate to signature, default false</li>
       *      <li>useKeyIdentifier - true to add Signer as the SignerIdentifier (v3), otherwise, as the IssuerAndSerialNumber (v1) (default false).</li>
       * </ul>
       * 
       * @memberOf GostCMS
       * @instance
       */
      var options = {// <editor-fold defaultstate="collapsed">
          providerName: 'CP-01',
          autoAddCert: false,
          useKeyIdentifier: false // </editor-fold>
      };
  
      GostCMS.prototype.options = options;
  
      /**
       * The base class for all CMS objects.<br><br>
       * 
       * A CMS object consists of a content type, and content.<br><br>
       * 
       * @class GostCMS.DataContentInfo
       * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The content object.
       * @param {string} defaultSet The default object initialization set.
       * @extends GostASN1.ContentInfo
       */
      function DataContentInfo(contentInfo, defaultSet) // <editor-fold defaultstate="collapsed">
      {
          asn1.ContentInfo.call(this, contentInfo || defaultSet || {contentType: 'data'});
          if (defaultSet && this.contentType !== (defaultSet.contentType || 'data'))
              throw new Error('Invalid content type');
      } // </editor-fold>
  
      extend(asn1.ContentInfo, DataContentInfo, {
          /**
           * Indicates if this is a detached CMS object.
           * 
           * @memberOf GostCMS.DataContentInfo
           * @instance
           * @returns {boolean} true if detached; false otherwise.
           */
          isDetached: {// <editor-fold defaultstate="collapsed">
              value: false,
              enumerable: true,
              writable: true // </editor-fold>
          },
          /**
           * Indicates if an external (detached) signature must be created.
           * 
           * @memberOf GostCMS.DataContentInfo
           * @instance
           * @param {boolean} createDetached True if detached; false otherwise.
           */
          writeDetached: function (createDetached) // <editor-fold defaultstate="collapsed">
          {
              // Define external signature mode
              this.isDetached = createDetached;
          }, // </editor-fold> 
          /**
           * Encode the message to binary format 'DER' or 'PEM'
           * 
           * @memberOf GostCMS.DataContentInfo
           * @instance
           * @param {string} format
           * @returns {FormatedData}
           */ // <editor-fold defaultstate="collapsed">
          encode: function (format) // <editor-fold defaultstate="collapsed">
          {
              if (this.isDetached) {
                  var data = getContentData(this);
                  setContentData(this, {contentType: data.contentType});
                  var result = asn1.ContentInfo.method('encode').call(this, format);
                  setContentData(this, data);
                  return result;
              } else
                  return asn1.ContentInfo.method('encode').call(this, format);
          }, // </editor-fold> 
          /**
           * Enclose content to document. 
           * 
           * @memberOf GostCMS.DataContentInfo
           * @instance
           * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo
           * @returns {Promise} Promise to return self object after enclose content
           */
          encloseContent: function (contentInfo) // <editor-fold defaultstate="collapsed">
          {
              var self = this;
              return new Promise(call).then(function () {
                  self.setEnclosed(contentInfo);
                  return self;
              });
          }, // </editor-fold> 
          /**
           * Sets the content of attached document.<br><br>
           * 
           * This is necessary only in detached mode.
           * 
           * @memberOf GostCMS.DataContentInfo
           * @instance
           * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo - The encapsulated CMS Object.
           */
          setEnclosed: function (contentInfo) // <editor-fold defaultstate="collapsed">
          {
              setContentData(this, checkContentInfo(contentInfo));
          }, // </editor-fold> 
          /**
           * Returns the document which attached. If the content is not attached, the CMS object 
           * which is returned will be degenerate.
           * 
           * @memberOf GostCMS.DataContentInfo
           * @instance
           * @returns {GostASN1.ContentInfo} The encapsulated CMS Object.
           */
          getEnclosed: function () // <editor-fold defaultstate="collapsed">
          {
              return createContentInfo(getContentData(this));
          } // </editor-fold>
      });
  
      /**
       * This class encapsulates a CMS object of content type binary data.
       * 
       * @memberOf GostCMS
       * @type GostCMS.DigestedDataContentInfo
       */
      GostCMS.prototype.DataContentInfo = DataContentInfo;
  
      /**
       * This class encapsulates a CMS object of content type digested-data.
       * 
       * @class GostCMS.DigestedDataContentInfo
       * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo - The content that is to be signed.
       * @extends GostCMS.DataContentInfo
       * @extends GostASN1.DigestedData
       */
      function DigestedDataContentInfo(contentInfo) // <editor-fold defaultstate="collapsed">
      {
          DataContentInfo.call(this, contentInfo, {
              contentType: 'digestedData',
              version: 0,
              digestAlgorithm: providers[options.providerName].digest,
              encapContentInfo: {
                  eContentType: 'data'
              },
              digest: new CryptoOperationData(0)
          });
      }  // </editor-fold>
  
      extend(DataContentInfo, DigestedDataContentInfo, {
          /**
           * Enclose the content and calculate the message digest with given digest algorithm
           * 
           * @memberOf GostCMS.DigestedDataContentInfo
           * @instance
           * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo Content data to be enclosed.
           * @param {(AlgorithmIdentifier|string)} digestAlgorithm Digest algorithm or provider name
           * @returns {Promise} 
           */
          encloseContent: function (contentInfo, digestAlgorithm) // <editor-fold defaultstate="collapsed"> 
          {
              var self = this;
              return new Promise(call).then(function () {
                  // Set enclosed data
                  self.setEnclosed(contentInfo);
  
                  // Define digest algorithm
                  if (digestAlgorithm) {
                      var digestProvider = providers[digestAlgorithm];
                      self.digestAlgorithm = (digestProvider && digestProvider.digest) || digestAlgorithm;
                  }
  
                  // Calculate digest
                  return subtle.digest(self.digestAlgorithm, self.encapContentInfo.eContent);
              }).then(function (digest) {
  
                  // Set digest attribute
                  self.digest = digest;
              });
          }, // </editor-fold>
          /**
           * Verify the Message Digest. <br><br>
           * 
           * @memberOf GostCMS.DigestedDataContentInfo
           * @instance
           * @param contentInfo Detached content (optional)
           * @returns {Promise} Promise to return enclosed object {@link GostASN1.ContentInfo} if digest verified
           */
          verify: function (contentInfo) // <editor-fold defaultstate="collapsed">
          {
              var self = this;
              return new Promise(call).then(function () {
                  // Append attached
                  if (contentInfo)
                      self.setEnclosed(contentInfo);
  
                  // Check data
                  var dataToVerify = self.encapContentInfo &&
                          self.encapContentInfo.eContent;
                  if (!dataToVerify)
                      throw new Error('Detached content is not found');
  
                  // Calculate digest
                  return subtle.digest(self.digestAlgorithm, self.encapContentInfo.eContent);
              }).then(function (digest) {
                  if (!equalBuffers(digest, self.digest))
                      throw Error('Message digest is not verified');
                  // Return content
                  return createContentInfo({
                      contentType: self.encapContentInfo.eContentType,
                      content: self.encapContentInfo.eContent
                  });
              });
          } // </editor-fold>
      });
  
      /**
       * This class encapsulates a CMS object of content type digested-data.
       * 
       * @memberOf GostCMS
       * @type GostCMS.DigestedDataContentInfo
       */
      GostCMS.prototype.DigestedDataContentInfo = DigestedDataContentInfo;
  
      /**
       * This class encapsulates a CMS object of content type signed-data.
       * 
       * Use encloseContent or setEnclosed methods to add a enclosed content before add signatures
       * 
       * @class GostCMS.SignedDataContentInfo
       * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo - The signed content. 
       * @extends GostCMS.DataContentInfo
       * @extends GostASN1.SignedData
       */
      function SignedDataContentInfo(contentInfo) // <editor-fold defaultstate="collapsed">
      {
          DataContentInfo.call(this, contentInfo, {
              contentType: 'signedData',
              version: 1,
              digestAlgorithms: [],
              encapContentInfo: {
                  eContentType: 'data'
              },
              signerInfos: []
          });
      }  // </editor-fold>
  
      extend(DataContentInfo, SignedDataContentInfo, {
          /**
           * Add a Signer using the the IssuerAndSerialNumber as the SignerIdentifier i.e a Version1 CMSSignerInfo 
           * or SubjectPublicKeyIdentifier as the SignerIdentifier i.e a Version3 CMSSignerInfo.
           * 
           * @memberOf GostCMS.SignedDataContentInfo
           * @instance
           * @param {GostASN1.PrivateKeyInfo} signerKey Private Key of the signer.
           * @param {GostCert.X509} signerCert Signer certificate or certificate chain
           * @param {GostASN1.SignedAttributes} signedAttrs The set of signed attributes. Default undefined. If true or {} standard attributes will be appended: contentType and messageDigest
           * @param {GostASN1.UnsignedAttributes} unsignedAttrs  The set of unsigned attributes. Default undefined.
           * @returns {Promise} Promise to return self object after add signature
           */
          addSignature: function (signerKey, signerCert, signedAttrs, unsignedAttrs) // <editor-fold defaultstate="collapsed"> 
          {
              var self = this, signerInfo, dataToSign, signerCertChain;
              return new Promise(call).then(function () {
                  // Check attribures
                  if (!signerKey || !signerCert)
                      throw new Error('Signer key or certificate is not defined');
                  // Cert chain
                  if (signerCert instanceof Array) {
                      signerCertChain = signerCert;
                      signerCert = signerCertChain[0];
                  } else
                      signerCertChain = [signerCert];
                  // Signature algorithm provider
                  var provider = signerCert.getProvider() ||
                          providers[options.providerName];
                  var useKeyIdentifier = options.useKeyIdentifier && signerCert.extensions &&
                          signerCert.extensions.subjectKeyIdentifier;
                  // Get enclosed data
                  dataToSign = self.encapContentInfo.eContent;
                  // Prepare signer info structure
                  signerInfo = {
                      version: useKeyIdentifier ? 2 : 0,
                      sid: useKeyIdentifier ? signerCert.extensions.subjectKeyIdentifier : {
                          issuer: signerCert.issuer,
                          serialNumber: signerCert.serialNumber},
                      digestAlgorithm: provider.digest,
                      signatureAlgorithm: signerCert.subjectPublicKeyInfo.algorithm};
                  // Set an unsigned attributes
                  if (unsignedAttrs)
                      signerInfo.unsignedAttrs = unsignedAttrs;
                  // For a signed attributes calculate digest
                  if (signedAttrs) {
                      if (typeof signedAttrs !== 'object')
                          signedAttrs = {};
                      return subtle.digest(signerInfo.digestAlgorithm, dataToSign);
                  }
              }).then(function (digest) {
                  if (digest) {
                      // Add standard signed attributes
                      signedAttrs.contentType = self.encapContentInfo.eContentType,
                              signedAttrs.messageDigest = digest,
                              signedAttrs.signingTime = new Date();
                      signerInfo.signedAttrs = signedAttrs,
                              // Now data to sign = attributes
                              dataToSign = asn1.SignedAttributes.encode(signerInfo.signedAttrs);
                  }
  
                  // Import the private key
                  return subtle.importKey('pkcs8', asn1.PrivateKeyInfo.encode(signerKey),
                          signerKey.privateKeyAlgorithm, false, ['sign']);
              }).then(function (key) {
  
                  // Sign data
                  var algorithm = expand(signerInfo.signatureAlgorithm, {hash: signerInfo.digestAlgorithm});
                  return subtle.sign(algorithm, key, dataToSign);
              }).then(function (signatureValue) {
                  signerInfo.signatureValue = signatureValue;
  
                  // Add digest algorithm
                  addUnique(self.digestAlgorithms, signerInfo.digestAlgorithm, function (algorithm1, algorithm2) {
                      return algorithm1.id === algorithm2.id;
                  });
  
                  // Add signer certificate
                  if (options.autoAddCert) {
                      if (!self.certificates)
                          self.certificates = [];
                      for (var i = 0, n = signerCertChain.length; i < n; i++) {
                          addUnique(self.certificates, signerCertChain[i], function (cert1, cert2) {
                              return equalNames(cert1.issuer, cert2.issuer) &&
                                      equalNumbers(cert1.serialNumber, cert2.serialNumber);
                          });
                      }
                  }
  
                  // Add signer info
                  self.signerInfos.push(signerInfo);
              });
          }, // </editor-fold>
          /**
           * Indicates if this object has any signers i.e. checks for the absence of any SignerInfo structures.
           * CMS (RFC-2630) defines a degenerate object as one which has no signers.
           * 
           * @memberOf GostCMS.SignedDataContentInfo
           * @instance
           * @returns {boolean} True if this object has no signers; false otherwise.
           */
          isDegenerate: {// <editor-fold defaultstate="collapsed">
              get: function () {
                  return !(this.signerInfos && this.signerInfos.length > 0);
              } // </editor-fold>
          },
          /**
           * Returns normally if this CMS signed data object contains at least one valid signature, 
           * according to the given trust policy; otherwise throws an Error.<br><br>
           * 
           * In order to be considered valid, there must be at least one signature on this CMS 
           * message which is validated by one of the certificates included with it; furthermore, 
           * the validating certificate must itself be valid according to the given certificate 
           * trust policy. This latter validation process may involve examining the other certificates 
           * or CRLs included with this object, if called for by the trust policy.<br><br>
           * 
           * If a signature is encountered for which a certification path can be found, but is 
           * invalid, an Error will be created, but will not be thrown until 
           * all other signatures have been checked. If another signature is found which is valid, 
           * then the method simply returns and no exception at all is thrown. 
           * 
           * @memberOf GostCMS.SignedDataContentInfo
           * @instance
           * @param {GostCert.CertificateTrustPolicy} trustPolicy The trust prolicy for verification
           * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The content that was signed (optional)
           * @returns {Promise} Promise to return enclosed object {@link GostASN1.ContentInfo} if signature verified
           */
          verify: function (trustPolicy, contentInfo) // <editor-fold defaultstate="collapsed">
          {
              var self = this, result;
              return new Promise(call).then(function () {
                  // Append attached
                  if (contentInfo)
                      self.setEnclosed(contentInfo);
                  if (!self.signerInfos || self.signerInfos.length === 0)
                      throw new Error('No signatures found');
                  // Validate certificate of signers
                  return Promise.all(self.signerInfos.map(function (signerInfo, i) {
                      var sid = signerInfo.sid, selector = sid instanceof CryptoOperationData ? {
                          subjectKeyIdentifier: sid
                      } : {
                          issuer: sid.issuer,
                          serialNumber: sid.serialNumber};
                      // Signing date
                      var date;
                      if (signerInfo.signedAttrs && signerInfo.signedAttrs.signingTime)
                          date = signerInfo.signedAttrs.signingTime;
                      // Use certificate trust policy validation
                      return trustPolicy.getValidCertificate(selector,
                              self.certificates, self.crls, date).catch(function () {
                          return; // Ignore error
                      });
                  }));
  
              }).then(function (certs) {
                  // Get encapsulated data
                  var verifiers = [];
                  // Verify signatures for each signers
                  certs.forEach(function (signerCert) {
                      if (signerCert)
                          verifiers.push(self.verifySignature(signerCert).then(function (data) {
                              result = data; // Enough one valid signature
                          }, function () {
                              return; // Ignore error
                          }));
                  });
                  if (verifiers.length === 0)
                      throw new Error('Valid verification path not found');
                  return Promise.all(verifiers);
              }).then(function () {
                  if (!result)
                      throw Error('Verification path found but no valid signature');
                  // Return content
                  return result;
              });
          }, // </editor-fold>
          /**
           * Returns successfully if this CMS signed data object contains a signature which is 
           * validated by the given certificate and data; otherwise throws an Error.<br><br>
           * 
           * This method verifies the specified signature directly and ignores any certificates 
           * or CRLs which may be contained in this CMS object. A more complex verification process, 
           * which does make use of attached certificates and CRLs, is provided by the verify method.
           * 
           * @memberOf GostCMS.SignedDataContentInfo
           * @instance
           * @param {GostCert.X509} signerCert The signer certificate
           * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The content that was signed (optional)
           * @returns {Promise} Promise to return enclosed object {@link GostASN1.ContentInfo} if signature verified
           */
          verifySignature: function (signerCert, contentInfo) // <editor-fold defaultstate="collapsed">
          {
              var self = this, signerInfo, dataToVerify, dataDigest;
              return new Promise(call).then(function () {
                  // Append attached
                  if (contentInfo)
                      self.setEnclosed(contentInfo);
                  dataToVerify = self.encapContentInfo && self.encapContentInfo.eContent;
                  if (!dataToVerify)
                      throw new Error('Detached content is not found');
                  // Find signer
                  for (var i = 0; i < self.signerInfos.length; i++) {
                      var sid = self.signerInfos[i].sid;
                      if (matchCert(sid, signerCert)) {
                          signerInfo = self.signerInfos[i];
                          break;
                      }
                  }
                  if (!signerInfo)
                      throw new Error('Signature not found for the certificate');
                  // Choice data for verification
                  if (signerInfo.signedAttrs) {
                      dataDigest = signerInfo.signedAttrs.messageDigest;
                      if (!dataDigest)
                          throw new Error('Message digest must present in signed attributes');
  
                      // To exclude implicit [0] need to reassemble signed attributes (auto on CTX object)
                      dataToVerify = signerInfo.signedAttrs.encode();
                  }
                  if (!dataToVerify)
                      throw new Error('Data for verification not found');
                  // Verify signature
                  var algorithm = expand(signerInfo.signatureAlgorithm, {hash: signerInfo.digestAlgorithm});
                  return signerCert.verifySignature(dataToVerify, signerInfo.signatureValue, algorithm);
              }).then(function (result) {
                  if (!result)
                      throw new Error('Signature not verified');
                  // Verify digest
                  if (signerInfo.signedAttrs)
                      return subtle.digest(signerInfo.digestAlgorithm, self.encapContentInfo.eContent);
              }).then(function (digest) {
                  if (digest && !equalBuffers(digest, dataDigest))
                      throw new Error('Message digest not verified');
                  // Return content
                  return createContentInfo({
                      contentType: self.encapContentInfo.eContentType,
                      content: self.encapContentInfo.eContent
                  });
              });
          }  // </editor-fold>
      });
  
      /**
       * This class encapsulates a CMS object of content type signed-data.
       * 
       * @memberOf GostCMS
       * @type GostCMS.SignedDataContentInfo
       */
      GostCMS.prototype.SignedDataContentInfo = SignedDataContentInfo;
  
      /**
       * This class encapsulates a CMS object of content type encrypted-data.
       * 
       * @class GostCMS.EncryptedDataContentInfo
       * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The encrypted data content.
       * @extends GostCMS.DataContentInfo
       * @extends GostASN1.EncryptedData
       */
      function EncryptedDataContentInfo(contentInfo) // <editor-fold defaultstate="collapsed">
      {
          DataContentInfo.call(this, contentInfo, {
              contentType: 'encryptedData',
              version: 0,
              encryptedContentInfo: {
                  contentType: 'data',
                  contentEncryptionAlgorithm: providers[options.providerName].encryption
              }
          });
      }  // </editor-fold>
  
      extend(DataContentInfo, EncryptedDataContentInfo, {
          /**
           * Encrypt the content with given encryption algorithm, secret key or password
           * 
           * @memberOf GostCMS.EncryptedDataContentInfo
           * @instance
           * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The content data to be enclosed.
           * @param {Key|string} contentEncryptionKey content The encryption key or password for derive key
           * @param {(AlgorithmIdentifier|string)} encryptionAlgorithm The encryption algorithm or provider name
           * @returns {Promise} Promise to return self object after encrypt content
           */
          encloseContent: function (contentInfo, contentEncryptionKey, encryptionAlgorithm) // <editor-fold defaultstate="collapsed"> 
          {
              var self = this, encryption, derivation;
              return new Promise(call).then(function () {
                  // Check content info
                  contentInfo = checkContentInfo(contentInfo);
                  if (!contentInfo.content)
                      throw new Error('Content for encryption must be specified');
  
                  // Define encryption algorithm
                  var type = typeof contentEncryptionKey === 'string' ? 'pbes' : 'encryption';
                  if (encryptionAlgorithm) {
                      var provider = providers[encryptionAlgorithm];
                      encryptionAlgorithm = (provider && provider[type]) || encryptionAlgorithm;
                  } else
                      encryptionAlgorithm = providers[options.providerName][type];
                  // Prepare content encryption key
                  if (encryptionAlgorithm.derivation) {
                      // Encrypt with password
                      derivation = expand(encryptionAlgorithm.derivation);
                      encryption = expand(encryptionAlgorithm.encryption);
                      derivation.salt = getSeed(saltSize(encryptionAlgorithm));
                      // Import password for key generation
                      var integrityKey;
                      return subtle.importKey('raw', passwordData(derivation, contentEncryptionKey),
                              derivation, false, ['deriveKey', 'deriveBits']).then(function (key) {
                          integrityKey = key;
                          // Derive IV
                          if (derivation.name.indexOf('PFXKDF') >= 0) {
                              derivation.diversifier = 2;
                              return subtle.deriveBits(derivation, integrityKey, 64);
                          }
                      }).then(function (iv) {
                          if (iv)
                              encryption.iv = iv;
                          // Generate key from password 
                          derivation.diversifier = 1;
                          return subtle.deriveKey(derivation, integrityKey, encryption, false, ['encrypt']);
                      }).then(function (encryptionKey) {
                          // Content encryption with key
                          return encryptionKey;
                      });
                  } else {
                      // Base encryption
                      encryption = expand(encryptionAlgorithm);
                      if (contentEncryptionKey instanceof CryptoOperationData) {
                          // Import key
                          return subtle.importKey('raw', contentEncryptionKey, encryption, false, ['encrypt']);
                      } else if (contentEncryptionKey.type === 'secret') {
                          return contentEncryptionKey;
                      } else
                          throw new Error('Content encryption key must be raw data or secret key type');
                  }
              }).then(function (encryptionKey) {
                  // Initial vector
                  if (!encryption.iv)
                      encryption.iv = getSeed(8);
  
                  return subtle.encrypt(encryption, encryptionKey, contentInfo.content);
              }).then(function (encryptedContent) {
                  if (encryptionAlgorithm.derivation) {
                      delete derivation.diversifier;
                      encryptionAlgorithm = expand(encryptionAlgorithm, {
                          derivation: derivation,
                          encryption: encryption
                      });
                  } else
                      encryptionAlgorithm = encryption;
                  // Set enclosed data
                  self.encryptedContentInfo = {
                      contentType: contentInfo.contentType,
                      contentEncryptionAlgorithm: encryptionAlgorithm,
                      encryptedContent: encryptedContent
                  };
                  return self;
              });
          }, // </editor-fold>
          /**
           * Returns the decrypted content. 
           * 
           * @memberOf GostCMS.EncryptedDataContentInfo
           * @instance
           * @param {Key|string} decryptionKey The decryption key or password for derive key   
           * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The detached content (optional).
           * @returns {Promise} Promise to return enclosed object {@ling GostASN1.ContentInfo} after decrypt content
           */
          getEnclosed: function (decryptionKey, contentInfo) // <editor-fold defaultstate="collapsed">
          {
              var self = this, encryption, derivation, encryptedContent;
              return new Promise(call).then(function () {
                  // Append attached
                  if (contentInfo)
                      self.setEnclosed(contentInfo);
                  encryptedContent = self.encryptedContentInfo.encryptedContent;
                  if (!encryptedContent)
                      throw new Error('Encrypted content must be specified');
  
                  encryption = expand(self.encryptedContentInfo.contentEncryptionAlgorithm);
                  if (encryption.derivation) {
                      // Decrypt with password
                      derivation = expand(encryption.derivation);
                      encryption = expand(encryption.encryption);
                      // Derive encryption key from password
                      var integrityKey;
                      return subtle.importKey('raw', passwordData(derivation, decryptionKey),
                              derivation, false, ['deriveKey', 'deriveBits']).then(function (key) {
                          integrityKey = key;
                          // Derive iv for PFX
                          if (derivation.name.indexOf('PFXKDF') >= 0) {
                              derivation.diversifier = 2;
                              return subtle.deriveBits(derivation, integrityKey, 64);
                          }
                      }).then(function (iv) {
                          if (iv)
                              encryption.iv = iv;
                          // Generate key from password 
                          derivation.diversifier = 1;
                          return subtle.deriveKey(derivation, integrityKey, encryption, false, ['decrypt']);
                      });
                  } else {
                      // Base encryption. Password should be secret key
                      if (decryptionKey instanceof CryptoOperationData) {
                          // Import key
                          return subtle.importKey('raw', decryptionKey, encryption, false, ['decrypt']);
                      } else if (decryptionKey.type === 'secret') {
                          return decryptionKey;
                      } else
                          throw new Error('Decryption key must be raw data or secret key type');
                  }
              }).then(function (encryptionKey) {
                  // Decrypt key with encryption key
                  return subtle.decrypt(encryption, encryptionKey, encryptedContent);
              }).then(function (decryptedContent) {
                  // Create content info object
                  return createContentInfo({
                      contentType: self.encryptedContentInfo.contentType,
                      content: decryptedContent
                  });
              });
          } // </editor-fold>
      });
  
      /**
       * This class encapsulates a CMS object of content type encrypted-data.
       * 
       * @memberOf GostCMS
       * @type GostCMS.EncryptedDataContentInfo
       */
      GostCMS.prototype.EncryptedDataContentInfo = EncryptedDataContentInfo;
      /**
       * This class encapsulates a CMS object of content type enveloped-data.
       * 
       * @class GostCMS.EnvelopedDataContentInfo
       * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The encrypted data content.
       * @extends GostCMS.DataContentInfo
       * @extends GostASN1.EnvelopedData
       */
      function EnvelopedDataContentInfo(contentInfo) // <editor-fold defaultstate="collapsed">
      {
          DataContentInfo.call(this, contentInfo, {
              contentType: 'envelopedData',
              version: 0,
              recipientInfos: [],
              encryptedContentInfo: {
                  contentType: 'data',
                  contentEncryptionAlgorithm: providers[options.providerName].encryption
              }
          });
      }  // </editor-fold>
  
      extend(DataContentInfo, EnvelopedDataContentInfo, {
          /**
           * Generate content encryption key with given encryption algorithm and encrypt the content
           * 
           * @memberOf GostCMS.EnvelopedDataContentInfo
           * @instance
           * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The content data to be enclosed.
           * @param {(AlgorithmIdentifier|string)} encryptionAlgorithm The encryption algorithm or provider name
           * @returns {Promise} Promise to return self object after encrypt content
           */
          encloseContent: function (contentInfo, encryptionAlgorithm) // <editor-fold defaultstate="collapsed"> 
          {
              var self = this;
              return new Promise(call).then(function () {
                  // Check content info
                  contentInfo = checkContentInfo(contentInfo);
                  if (!contentInfo.content)
                      throw new Error('Content for encryption must be specified');
                  // Define encryption algorithm
                  if (encryptionAlgorithm) {
                      var provider = providers[encryptionAlgorithm];
                      encryptionAlgorithm = (provider && provider.encryption) || encryptionAlgorithm;
                  } else
                      encryptionAlgorithm = providers[options.providerName].encryption;
                  // Generate key for encryption content
                  return subtle.generateKey(encryptionAlgorithm, true, ['encrypt']);
              }).then(function (encryptionKey) {
                  // Encrypt content
                  self.contentEncryptionKey = encryptionKey;
                  // Initial vector
                  if (!encryptionAlgorithm.iv)
                      encryptionAlgorithm.iv = getSeed(8);
                  return subtle.encrypt(encryptionAlgorithm, encryptionKey, contentInfo.content);
              }).then(function (encryptedContent) {
                  self.encryptedContentInfo = {
                      contentType: contentInfo.contentType,
                      contentEncryptionAlgorithm: encryptionAlgorithm,
                      encryptedContent: encryptedContent
                  };
                  return self;
              });
          }, // </editor-fold>
          /**
           * Add a recipient. <br><br>
           * 
           * Uses the Recipient Information with IssuerAndSerialNumber as the Recipient Identifier.
           * Note: If senderCert specified uses the Key Agreement algorithm overwise Key Transport algorithm.
           * 
           * @memberOf GostCMS.EnvelopedDataContentInfo
           * @instance
           * @param {GostCert.X509} recipientCert The certificate of recepient
           * @param {(AlgorithmIdentifier|string)} keyEncryptionAlgorithm Key encryption algorithm or provider name
           * @param {GostASN1.PrivateKeyInfo} senderKey The private key of sender for key agreement protocol
           * @param {GostCert.X509} senderCert The certificate of sender for key agreement protocol
           * @returns {Promise} Promise to return self object after add recipient
           */
          addRecipient: function (recipientCert, keyEncryptionAlgorithm, senderKey, senderCert) // <editor-fold defaultstate="collapsed">
          {
              var self = this, privateKey, encryptionProvider, derivation, wrapping;
              return new Promise(call).then(function () {
                  // Check for recepient cert
                  recipientCert = new cert.X509(recipientCert);
                  if (keyEncryptionAlgorithm && typeof keyEncryptionAlgorithm !== 'string' &&
                          !keyEncryptionAlgorithm.algorithm) {
                      // Sender parameters
                      senderCert = senderKey;
                      senderKey = keyEncryptionAlgorithm;
                      keyEncryptionAlgorithm = undefined;
                  }
                  if (keyEncryptionAlgorithm) {
                      encryptionProvider = providers[keyEncryptionAlgorithm];
                  } else
                      encryptionProvider = recipientCert.getProvider();
  
                  // Check for content encryption key
                  if (!self.contentEncryptionKey)
                      throw new Error('The content encryption key is not assigned');
  
                  if (senderCert) {
                      // Sender certificate for agreement protocol
                      var senderCertChain;
                      if (senderCert instanceof Array) {
                          senderCertChain = senderCert;
                          senderCert = senderCertChain[0];
                      } else
                          senderCertChain = [senderCert];
  
                      // Add sender certificate
                      if (options.autoAddCert) {
                          if (!self.originatorInfo)
                              self.originatorInfo = {certs: []};
                          else if (!self.originatorInfo.certs)
                              self.originatorInfo.certs = [];
                          for (var i = 0, n = senderCertChain.length; i < n; i++) {
                              addUnique(self.originatorInfo.certs, senderCertChain[i], function (cert1, cert2) {
                                  return equalNames(cert1.issuer, cert2.issuer) &&
                                          equalNumbers(cert1.serialNumber, cert2.serialNumber);
                              });
                          }
                      }
                      // Key Agreement
                      if (encryptionProvider)
                          keyEncryptionAlgorithm = expand(encryptionProvider.agreement);
                      else
                          encryptionProvider = recipientCert.getProvider();
                      // Certificates must have similar curve parameters
                      if (recipientCert.subjectPublicKeyInfo.algorithm.namedCurve !==
                              senderCert.subjectPublicKeyInfo.algorithm.namedCurve)
                          throw new Error('The sender and the recipient have different public key algorithms');
                      // Get private sender key
                      return subtle.importKey('pkcs8', senderKey.encode(), senderKey.privateKeyAlgorithm,
                              false, ['deriveKey']);
                  } else {
                      // Key Transport
                      if (encryptionProvider)
                          keyEncryptionAlgorithm = expand(recipientCert.subjectPublicKeyInfo.algorithm);
                      else
                          encryptionProvider = recipientCert.getProvider();
                      // Generate key pair
                      return subtle.generateKey(keyEncryptionAlgorithm, true, ['deriveKey']).then(function (keyPair) {
                          keyEncryptionAlgorithm['public'] = keyPair.publicKey;
                          return keyPair.privateKey;
                      });
                  }
              }).then(function (key) {
                  privateKey = key;
                  // Get public key from recipient certificate
                  return subtle.importKey('spki', recipientCert.subjectPublicKeyInfo.encode(),
                          recipientCert.subjectPublicKeyInfo.algorithm, false, ['deriveKey', 'deriveBits']);
              }).then(function (publicKey) {
                  // Derivate key encryption key
                  keyEncryptionAlgorithm.ukm = getSeed(8);
                  derivation = expand(encryptionProvider.agreement,
                          {sBox: keyEncryptionAlgorithm.sBox, ukm: keyEncryptionAlgorithm.ukm, 'public': publicKey});
                  wrapping = expand(keyEncryptionAlgorithm.wrapping || encryptionProvider.wrapping,
                          {ukm: keyEncryptionAlgorithm.ukm});
                  return subtle.deriveKey(derivation, privateKey, wrapping, true, ['wrapKey']);
              }).then(function (wrappingKey) {
                  // Wrap content encryption key 
                  keyEncryptionAlgorithm.wrapping = wrapping;
                  return subtle.wrapKey('raw', self.contentEncryptionKey, wrappingKey, wrapping);
              }).then(function (wrappedKey) {
                  // Create recipient info
                  var recipientInfo;
                  var useKeyIdentifier = options.useKeyIdentifier && recipientCert.extensions &&
                          recipientCert.extensions.subjectKeyIdentifier,
                          rid = useKeyIdentifier ? recipientCert.extensions.subjectKeyIdentifier : {
                              issuer: recipientCert.issuer,
                              serialNumber: recipientCert.serialNumber};
                  if (senderCert) {
                      var spki = senderCert.subjectPublicKeyInfo;
                      recipientInfo = {// KeyAgreeRecipientInfo
                          version: 3, // always set to 3
                          originator: {
                              algorithm: spki.algorithm,
                              publicKey: spki.subjectPublicKey
                          },
                          ukm: keyEncryptionAlgorithm.ukm,
                          keyEncryptionAlgorithm: keyEncryptionAlgorithm,
                          recipientEncryptedKeys: [{// use only one recipient in domain
                                  rid: rid,
                                  encryptedKey: asn1.GostEncryptedKey(keyEncryptionAlgorithm).encode(wrappedKey)
                              }]
                      };
                  } else {
                      recipientInfo = {
                          version: 0, // always set to 0 or 2
                          rid: rid,
                          keyEncryptionAlgorithm: keyEncryptionAlgorithm,
                          encryptedKey: asn1.GostEncryptedKey(keyEncryptionAlgorithm).encode({
                              algorithm: keyEncryptionAlgorithm,
                              sessionEncryptedKey: wrappedKey
                          })
                      };
                  }
                  self.recipientInfos.push(recipientInfo);
                  return self;
              });
          }, // </editor-fold>
          /**
           * Returns the decrypted content. 
           * 
           * @memberOf GostCMS.EnvelopedDataContentInfo
           * @instance
           * @param {GostASN1.PrivateKeyInfo} recipientKey The decryption key or password for derive key   
           * @param {GostCert.X509} recipientCert  The decryption key or password for derive key   
           * @param {(FormatedData|GostASN1.ContentInfo)} contentInfo The detached content (optional).
           * @param {GostCert.X509} originatorCert The originator certificate (optional).
           * @returns {Promise} Promise to return enclosed object {@ling GostASN1.ContentInfo} after decrypt content
           */
          getEnclosed: function (recipientKey, recipientCert, contentInfo, originatorCert) // <editor-fold defaultstate="collapsed">
          {
              var self = this, wrappedKey, encryptedContent, derivation, wrapping, encryption;
              return new Promise(call).then(function () {
                  var encryptionProvider = recipientCert.getProvider();
                  // Append attached
                  if (contentInfo)
                      self.setEnclosed(contentInfo);
                  encryptedContent = self.encryptedContentInfo.encryptedContent;
                  if (!encryptedContent)
                      throw new Error('Encrypted content must be specified');
  
                  encryption = self.encryptedContentInfo.contentEncryptionAlgorithm;
  
                  // Find receiver
                  for (var i = 0; i < self.recipientInfos.length; i++) {
                      var recipientInfo = self.recipientInfos[i],
                              algorithm = expand(recipientInfo.keyEncryptionAlgorithm);
                      if (recipientInfo.rid) {
                          if (matchCert(recipientInfo.rid, recipientCert)) {
                              // Algorithm and wrapped key
                              var transportKey = asn1.GostEncryptedKey(algorithm).decode(recipientInfo.encryptedKey).object;
                              wrappedKey = transportKey.sessionEncryptedKey;
                              algorithm = expand(algorithm, transportKey.algorithm);
                              derivation = expand(encryptionProvider.agreement, {ukm: algorithm.ukm, sBox: algorithm.sBox});
                              wrapping = expand(encryptionProvider.wrapping, algorithm.wrapping, {ukm: algorithm.ukm});
                              return algorithm['public'];
                          }
                      } else {
                          var keys = recipientInfo.recipientEncryptedKeys;
                          if (keys) {
                              for (var j = 0; j < keys.length; j++) {
                                  if (matchCert(keys[j].rid, recipientCert)) {
                                      // Algorithm and wrapped key
                                      algorithm = expand(encryptionProvider.agreement, algorithm, {ukm: recipientInfo.ukm});
                                      wrappedKey = asn1.GostEncryptedKey(algorithm).decode(keys[j].encryptedKey).object;
                                      derivation = algorithm;
                                      wrapping = expand(algorithm.wrapping || encryptionProvider.wrapping, {ukm: recipientInfo.ukm});
                                      // Check originator
                                      var originator = recipientInfo.originator;
                                      if (originator.algorithm) {
                                          var spki = new asn1.SubjectPublicKeyInfo({
                                              algorithm: originator.algorithm,
                                              subjectPublicKey: originator.publicKey});
                                          return subtle.importKey('spki', spki.encode(), spki.algorithm, false, ['deriveKey', 'deriveBits']);
                                      } else if (originatorCert && matchCert(originator, originatorCert))
                                          return importKey('pkcs', originatorCert.subjectPublicKeyInfo.encode(),
                                                  originatorCert.subjectPublicKeyInfo.algorithm, false, ['deriveKey', 'deriveBits']);
                                      else
                                          throw Error('Originator certificate not specified or not valid');
                                  }
                              }
                          }
                      }
                  }
                  throw new Error('Recipient not found or format not supported');
              }).then(function (publicKey) {
                  derivation['public'] = publicKey;
                  // Import private key
                  return subtle.importKey('pkcs8', recipientKey.encode(), recipientKey.privateKeyAlgorithm,
                          false, ['deriveKey', 'deriveBits']);
              }).then(function (privateKey) {
                  // Derive key
                  return subtle.deriveKey(derivation, privateKey, wrapping, true, ['unwrapKey']);
              }).then(function (unwrappingKey) {
                  // Unwrap key
                  return subtle.unwrapKey('raw', wrappedKey, unwrappingKey,
                          wrapping, encryption, false, ['decrypt']);
              }).then(function (encryptionKey) {
                  // Decrypt content
                  return subtle.decrypt(encryption, encryptionKey, encryptedContent);
              }).then(function (decryptedContent) {
                  return createContentInfo({
                      contentType: self.encryptedContentInfo.contentType,
                      content: decryptedContent
                  });
              });
          } // </editor-fold>
      });
  
      /**
       * This class encapsulates a CMS object of content type enveloped-data.
       * 
       * @memberOf GostCMS
       * @type GostCMS.EnvelopedDataContentInfo
       */
      GostCMS.prototype.EnvelopedDataContentInfo = EnvelopedDataContentInfo;
  
      /**
       * Implements the Cryptographic Message Syntax as specified in RFC-2630.
       * 
       * @memberOf gostCrypto
       * @type GostCMS
       */
      gostCrypto.cms = new GostCMS();
  
      return GostCMS;
  
  }));
  
  