You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							1261 lines
						
					
					
						
							39 KiB
						
					
					
				
			
		
		
	
	
							1261 lines
						
					
					
						
							39 KiB
						
					
					
				/**
 | 
						|
 * Javascript implementation of PKCS#7 v1.5.
 | 
						|
 *
 | 
						|
 * @author Stefan Siegl
 | 
						|
 * @author Dave Longley
 | 
						|
 *
 | 
						|
 * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
 | 
						|
 * Copyright (c) 2012-2015 Digital Bazaar, Inc.
 | 
						|
 *
 | 
						|
 * Currently this implementation only supports ContentType of EnvelopedData,
 | 
						|
 * EncryptedData, or SignedData at the root level. The top level elements may
 | 
						|
 * contain only a ContentInfo of ContentType Data, i.e. plain data. Further
 | 
						|
 * nesting is not (yet) supported.
 | 
						|
 *
 | 
						|
 * The Forge validators for PKCS #7's ASN.1 structures are available from
 | 
						|
 * a separate file pkcs7asn1.js, since those are referenced from other
 | 
						|
 * PKCS standards like PKCS #12.
 | 
						|
 */
 | 
						|
var forge = require('./forge');
 | 
						|
require('./aes');
 | 
						|
require('./asn1');
 | 
						|
require('./des');
 | 
						|
require('./oids');
 | 
						|
require('./pem');
 | 
						|
require('./pkcs7asn1');
 | 
						|
require('./random');
 | 
						|
require('./util');
 | 
						|
require('./x509');
 | 
						|
 | 
						|
// shortcut for ASN.1 API
 | 
						|
var asn1 = forge.asn1;
 | 
						|
 | 
						|
// shortcut for PKCS#7 API
 | 
						|
var p7 = module.exports = forge.pkcs7 = forge.pkcs7 || {};
 | 
						|
 | 
						|
/**
 | 
						|
 * Converts a PKCS#7 message from PEM format.
 | 
						|
 *
 | 
						|
 * @param pem the PEM-formatted PKCS#7 message.
 | 
						|
 *
 | 
						|
 * @return the PKCS#7 message.
 | 
						|
 */
 | 
						|
p7.messageFromPem = function(pem) {
 | 
						|
  var msg = forge.pem.decode(pem)[0];
 | 
						|
 | 
						|
  if(msg.type !== 'PKCS7') {
 | 
						|
    var error = new Error('Could not convert PKCS#7 message from PEM; PEM ' +
 | 
						|
      'header type is not "PKCS#7".');
 | 
						|
    error.headerType = msg.type;
 | 
						|
    throw error;
 | 
						|
  }
 | 
						|
  if(msg.procType && msg.procType.type === 'ENCRYPTED') {
 | 
						|
    throw new Error('Could not convert PKCS#7 message from PEM; PEM is encrypted.');
 | 
						|
  }
 | 
						|
 | 
						|
  // convert DER to ASN.1 object
 | 
						|
  var obj = asn1.fromDer(msg.body);
 | 
						|
 | 
						|
  return p7.messageFromAsn1(obj);
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Converts a PKCS#7 message to PEM format.
 | 
						|
 *
 | 
						|
 * @param msg The PKCS#7 message object
 | 
						|
 * @param maxline The maximum characters per line, defaults to 64.
 | 
						|
 *
 | 
						|
 * @return The PEM-formatted PKCS#7 message.
 | 
						|
 */
 | 
						|
p7.messageToPem = function(msg, maxline) {
 | 
						|
  // convert to ASN.1, then DER, then PEM-encode
 | 
						|
  var pemObj = {
 | 
						|
    type: 'PKCS7',
 | 
						|
    body: asn1.toDer(msg.toAsn1()).getBytes()
 | 
						|
  };
 | 
						|
  return forge.pem.encode(pemObj, {maxline: maxline});
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Converts a PKCS#7 message from an ASN.1 object.
 | 
						|
 *
 | 
						|
 * @param obj the ASN.1 representation of a ContentInfo.
 | 
						|
 *
 | 
						|
 * @return the PKCS#7 message.
 | 
						|
 */
 | 
						|
p7.messageFromAsn1 = function(obj) {
 | 
						|
  // validate root level ContentInfo and capture data
 | 
						|
  var capture = {};
 | 
						|
  var errors = [];
 | 
						|
  if(!asn1.validate(obj, p7.asn1.contentInfoValidator, capture, errors)) {
 | 
						|
    var error = new Error('Cannot read PKCS#7 message. ' +
 | 
						|
      'ASN.1 object is not an PKCS#7 ContentInfo.');
 | 
						|
    error.errors = errors;
 | 
						|
    throw error;
 | 
						|
  }
 | 
						|
 | 
						|
  var contentType = asn1.derToOid(capture.contentType);
 | 
						|
  var msg;
 | 
						|
 | 
						|
  switch(contentType) {
 | 
						|
    case forge.pki.oids.envelopedData:
 | 
						|
      msg = p7.createEnvelopedData();
 | 
						|
      break;
 | 
						|
 | 
						|
    case forge.pki.oids.encryptedData:
 | 
						|
      msg = p7.createEncryptedData();
 | 
						|
      break;
 | 
						|
 | 
						|
    case forge.pki.oids.signedData:
 | 
						|
      msg = p7.createSignedData();
 | 
						|
      break;
 | 
						|
 | 
						|
    default:
 | 
						|
      throw new Error('Cannot read PKCS#7 message. ContentType with OID ' +
 | 
						|
        contentType + ' is not (yet) supported.');
 | 
						|
  }
 | 
						|
 | 
						|
  msg.fromAsn1(capture.content.value[0]);
 | 
						|
  return msg;
 | 
						|
};
 | 
						|
 | 
						|
p7.createSignedData = function() {
 | 
						|
  var msg = null;
 | 
						|
  msg = {
 | 
						|
    type: forge.pki.oids.signedData,
 | 
						|
    version: 1,
 | 
						|
    certificates: [],
 | 
						|
    crls: [],
 | 
						|
    // TODO: add json-formatted signer stuff here?
 | 
						|
    signers: [],
 | 
						|
    // populated during sign()
 | 
						|
    digestAlgorithmIdentifiers: [],
 | 
						|
    contentInfo: null,
 | 
						|
    signerInfos: [],
 | 
						|
 | 
						|
    fromAsn1: function(obj) {
 | 
						|
      // validate SignedData content block and capture data.
 | 
						|
      _fromAsn1(msg, obj, p7.asn1.signedDataValidator);
 | 
						|
      msg.certificates = [];
 | 
						|
      msg.crls = [];
 | 
						|
      msg.digestAlgorithmIdentifiers = [];
 | 
						|
      msg.contentInfo = null;
 | 
						|
      msg.signerInfos = [];
 | 
						|
 | 
						|
      if(msg.rawCapture.certificates) {
 | 
						|
        var certs = msg.rawCapture.certificates.value;
 | 
						|
        for(var i = 0; i < certs.length; ++i) {
 | 
						|
          msg.certificates.push(forge.pki.certificateFromAsn1(certs[i]));
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      // TODO: parse crls
 | 
						|
    },
 | 
						|
 | 
						|
    toAsn1: function() {
 | 
						|
      // degenerate case with no content
 | 
						|
      if(!msg.contentInfo) {
 | 
						|
        msg.sign();
 | 
						|
      }
 | 
						|
 | 
						|
      var certs = [];
 | 
						|
      for(var i = 0; i < msg.certificates.length; ++i) {
 | 
						|
        certs.push(forge.pki.certificateToAsn1(msg.certificates[i]));
 | 
						|
      }
 | 
						|
 | 
						|
      var crls = [];
 | 
						|
      // TODO: implement CRLs
 | 
						|
 | 
						|
      // [0] SignedData
 | 
						|
      var signedData = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
 | 
						|
        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
          // Version
 | 
						|
          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
 | 
						|
            asn1.integerToDer(msg.version).getBytes()),
 | 
						|
          // DigestAlgorithmIdentifiers
 | 
						|
          asn1.create(
 | 
						|
            asn1.Class.UNIVERSAL, asn1.Type.SET, true,
 | 
						|
            msg.digestAlgorithmIdentifiers),
 | 
						|
          // ContentInfo
 | 
						|
          msg.contentInfo
 | 
						|
        ])
 | 
						|
      ]);
 | 
						|
      if(certs.length > 0) {
 | 
						|
        // [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL
 | 
						|
        signedData.value[0].value.push(
 | 
						|
          asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, certs));
 | 
						|
      }
 | 
						|
      if(crls.length > 0) {
 | 
						|
        // [1] IMPLICIT CertificateRevocationLists OPTIONAL
 | 
						|
        signedData.value[0].value.push(
 | 
						|
          asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, crls));
 | 
						|
      }
 | 
						|
      // SignerInfos
 | 
						|
      signedData.value[0].value.push(
 | 
						|
        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true,
 | 
						|
          msg.signerInfos));
 | 
						|
 | 
						|
      // ContentInfo
 | 
						|
      return asn1.create(
 | 
						|
        asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
          // ContentType
 | 
						|
          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | 
						|
            asn1.oidToDer(msg.type).getBytes()),
 | 
						|
          // [0] SignedData
 | 
						|
          signedData
 | 
						|
        ]);
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Add (another) entity to list of signers.
 | 
						|
     *
 | 
						|
     * Note: If authenticatedAttributes are provided, then, per RFC 2315,
 | 
						|
     * they must include at least two attributes: content type and
 | 
						|
     * message digest. The message digest attribute value will be
 | 
						|
     * auto-calculated during signing and will be ignored if provided.
 | 
						|
     *
 | 
						|
     * Here's an example of providing these two attributes:
 | 
						|
     *
 | 
						|
     * forge.pkcs7.createSignedData();
 | 
						|
     * p7.addSigner({
 | 
						|
     *   issuer: cert.issuer.attributes,
 | 
						|
     *   serialNumber: cert.serialNumber,
 | 
						|
     *   key: privateKey,
 | 
						|
     *   digestAlgorithm: forge.pki.oids.sha1,
 | 
						|
     *   authenticatedAttributes: [{
 | 
						|
     *     type: forge.pki.oids.contentType,
 | 
						|
     *     value: forge.pki.oids.data
 | 
						|
     *   }, {
 | 
						|
     *     type: forge.pki.oids.messageDigest
 | 
						|
     *   }]
 | 
						|
     * });
 | 
						|
     *
 | 
						|
     * TODO: Support [subjectKeyIdentifier] as signer's ID.
 | 
						|
     *
 | 
						|
     * @param signer the signer information:
 | 
						|
     *          key the signer's private key.
 | 
						|
     *          [certificate] a certificate containing the public key
 | 
						|
     *            associated with the signer's private key; use this option as
 | 
						|
     *            an alternative to specifying signer.issuer and
 | 
						|
     *            signer.serialNumber.
 | 
						|
     *          [issuer] the issuer attributes (eg: cert.issuer.attributes).
 | 
						|
     *          [serialNumber] the signer's certificate's serial number in
 | 
						|
     *           hexadecimal (eg: cert.serialNumber).
 | 
						|
     *          [digestAlgorithm] the message digest OID, as a string, to use
 | 
						|
     *            (eg: forge.pki.oids.sha1).
 | 
						|
     *          [authenticatedAttributes] an optional array of attributes
 | 
						|
     *            to also sign along with the content.
 | 
						|
     */
 | 
						|
    addSigner: function(signer) {
 | 
						|
      var issuer = signer.issuer;
 | 
						|
      var serialNumber = signer.serialNumber;
 | 
						|
      if(signer.certificate) {
 | 
						|
        var cert = signer.certificate;
 | 
						|
        if(typeof cert === 'string') {
 | 
						|
          cert = forge.pki.certificateFromPem(cert);
 | 
						|
        }
 | 
						|
        issuer = cert.issuer.attributes;
 | 
						|
        serialNumber = cert.serialNumber;
 | 
						|
      }
 | 
						|
      var key = signer.key;
 | 
						|
      if(!key) {
 | 
						|
        throw new Error(
 | 
						|
          'Could not add PKCS#7 signer; no private key specified.');
 | 
						|
      }
 | 
						|
      if(typeof key === 'string') {
 | 
						|
        key = forge.pki.privateKeyFromPem(key);
 | 
						|
      }
 | 
						|
 | 
						|
      // ensure OID known for digest algorithm
 | 
						|
      var digestAlgorithm = signer.digestAlgorithm || forge.pki.oids.sha1;
 | 
						|
      switch(digestAlgorithm) {
 | 
						|
      case forge.pki.oids.sha1:
 | 
						|
      case forge.pki.oids.sha256:
 | 
						|
      case forge.pki.oids.sha384:
 | 
						|
      case forge.pki.oids.sha512:
 | 
						|
      case forge.pki.oids.md5:
 | 
						|
        break;
 | 
						|
      default:
 | 
						|
        throw new Error(
 | 
						|
          'Could not add PKCS#7 signer; unknown message digest algorithm: ' +
 | 
						|
          digestAlgorithm);
 | 
						|
      }
 | 
						|
 | 
						|
      // if authenticatedAttributes is present, then the attributes
 | 
						|
      // must contain at least PKCS #9 content-type and message-digest
 | 
						|
      var authenticatedAttributes = signer.authenticatedAttributes || [];
 | 
						|
      if(authenticatedAttributes.length > 0) {
 | 
						|
        var contentType = false;
 | 
						|
        var messageDigest = false;
 | 
						|
        for(var i = 0; i < authenticatedAttributes.length; ++i) {
 | 
						|
          var attr = authenticatedAttributes[i];
 | 
						|
          if(!contentType && attr.type === forge.pki.oids.contentType) {
 | 
						|
            contentType = true;
 | 
						|
            if(messageDigest) {
 | 
						|
              break;
 | 
						|
            }
 | 
						|
            continue;
 | 
						|
          }
 | 
						|
          if(!messageDigest && attr.type === forge.pki.oids.messageDigest) {
 | 
						|
            messageDigest = true;
 | 
						|
            if(contentType) {
 | 
						|
              break;
 | 
						|
            }
 | 
						|
            continue;
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        if(!contentType || !messageDigest) {
 | 
						|
          throw new Error('Invalid signer.authenticatedAttributes. If ' +
 | 
						|
            'signer.authenticatedAttributes is specified, then it must ' +
 | 
						|
            'contain at least two attributes, PKCS #9 content-type and ' +
 | 
						|
            'PKCS #9 message-digest.');
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      msg.signers.push({
 | 
						|
        key: key,
 | 
						|
        version: 1,
 | 
						|
        issuer: issuer,
 | 
						|
        serialNumber: serialNumber,
 | 
						|
        digestAlgorithm: digestAlgorithm,
 | 
						|
        signatureAlgorithm: forge.pki.oids.rsaEncryption,
 | 
						|
        signature: null,
 | 
						|
        authenticatedAttributes: authenticatedAttributes,
 | 
						|
        unauthenticatedAttributes: []
 | 
						|
      });
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Signs the content.
 | 
						|
     * @param options Options to apply when signing:
 | 
						|
     *    [detached] boolean. If signing should be done in detached mode. Defaults to false.
 | 
						|
     */
 | 
						|
    sign: function(options) {
 | 
						|
      options = options || {};
 | 
						|
      // auto-generate content info
 | 
						|
      if(typeof msg.content !== 'object' || msg.contentInfo === null) {
 | 
						|
        // use Data ContentInfo
 | 
						|
        msg.contentInfo = asn1.create(
 | 
						|
          asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
            // ContentType
 | 
						|
            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | 
						|
              asn1.oidToDer(forge.pki.oids.data).getBytes())
 | 
						|
          ]);
 | 
						|
 | 
						|
        // add actual content, if present
 | 
						|
        if('content' in msg) {
 | 
						|
          var content;
 | 
						|
          if(msg.content instanceof forge.util.ByteBuffer) {
 | 
						|
            content = msg.content.bytes();
 | 
						|
          } else if(typeof msg.content === 'string') {
 | 
						|
            content = forge.util.encodeUtf8(msg.content);
 | 
						|
          }
 | 
						|
 | 
						|
          if (options.detached) {
 | 
						|
            msg.detachedContent = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, content);
 | 
						|
          } else {
 | 
						|
            msg.contentInfo.value.push(
 | 
						|
              // [0] EXPLICIT content
 | 
						|
              asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
 | 
						|
                asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
 | 
						|
                  content)
 | 
						|
              ]));
 | 
						|
          }
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      // no signers, return early (degenerate case for certificate container)
 | 
						|
      if(msg.signers.length === 0) {
 | 
						|
        return;
 | 
						|
      }
 | 
						|
 | 
						|
      // generate digest algorithm identifiers
 | 
						|
      var mds = addDigestAlgorithmIds();
 | 
						|
 | 
						|
      // generate signerInfos
 | 
						|
      addSignerInfos(mds);
 | 
						|
    },
 | 
						|
 | 
						|
    verify: function() {
 | 
						|
      throw new Error('PKCS#7 signature verification not yet implemented.');
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Add a certificate.
 | 
						|
     *
 | 
						|
     * @param cert the certificate to add.
 | 
						|
     */
 | 
						|
    addCertificate: function(cert) {
 | 
						|
      // convert from PEM
 | 
						|
      if(typeof cert === 'string') {
 | 
						|
        cert = forge.pki.certificateFromPem(cert);
 | 
						|
      }
 | 
						|
      msg.certificates.push(cert);
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Add a certificate revokation list.
 | 
						|
     *
 | 
						|
     * @param crl the certificate revokation list to add.
 | 
						|
     */
 | 
						|
    addCertificateRevokationList: function(crl) {
 | 
						|
      throw new Error('PKCS#7 CRL support not yet implemented.');
 | 
						|
    }
 | 
						|
  };
 | 
						|
  return msg;
 | 
						|
 | 
						|
  function addDigestAlgorithmIds() {
 | 
						|
    var mds = {};
 | 
						|
 | 
						|
    for(var i = 0; i < msg.signers.length; ++i) {
 | 
						|
      var signer = msg.signers[i];
 | 
						|
      var oid = signer.digestAlgorithm;
 | 
						|
      if(!(oid in mds)) {
 | 
						|
        // content digest
 | 
						|
        mds[oid] = forge.md[forge.pki.oids[oid]].create();
 | 
						|
      }
 | 
						|
      if(signer.authenticatedAttributes.length === 0) {
 | 
						|
        // no custom attributes to digest; use content message digest
 | 
						|
        signer.md = mds[oid];
 | 
						|
      } else {
 | 
						|
        // custom attributes to be digested; use own message digest
 | 
						|
        // TODO: optimize to just copy message digest state if that
 | 
						|
        // feature is ever supported with message digests
 | 
						|
        signer.md = forge.md[forge.pki.oids[oid]].create();
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    // add unique digest algorithm identifiers
 | 
						|
    msg.digestAlgorithmIdentifiers = [];
 | 
						|
    for(var oid in mds) {
 | 
						|
      msg.digestAlgorithmIdentifiers.push(
 | 
						|
        // AlgorithmIdentifier
 | 
						|
        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
          // algorithm
 | 
						|
          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | 
						|
            asn1.oidToDer(oid).getBytes()),
 | 
						|
          // parameters (null)
 | 
						|
          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
 | 
						|
        ]));
 | 
						|
    }
 | 
						|
 | 
						|
    return mds;
 | 
						|
  }
 | 
						|
 | 
						|
  function addSignerInfos(mds) {
 | 
						|
    var content;
 | 
						|
 | 
						|
    if (msg.detachedContent) {
 | 
						|
      // Signature has been made in detached mode.
 | 
						|
      content = msg.detachedContent;
 | 
						|
    } else {
 | 
						|
      // Note: ContentInfo is a SEQUENCE with 2 values, second value is
 | 
						|
      // the content field and is optional for a ContentInfo but required here
 | 
						|
      // since signers are present
 | 
						|
      // get ContentInfo content
 | 
						|
      content = msg.contentInfo.value[1];
 | 
						|
      // skip [0] EXPLICIT content wrapper
 | 
						|
      content = content.value[0];
 | 
						|
    }
 | 
						|
 | 
						|
    if(!content) {
 | 
						|
      throw new Error(
 | 
						|
        'Could not sign PKCS#7 message; there is no content to sign.');
 | 
						|
    }
 | 
						|
 | 
						|
    // get ContentInfo content type
 | 
						|
    var contentType = asn1.derToOid(msg.contentInfo.value[0].value);
 | 
						|
 | 
						|
    // serialize content
 | 
						|
    var bytes = asn1.toDer(content);
 | 
						|
 | 
						|
    // skip identifier and length per RFC 2315 9.3
 | 
						|
    // skip identifier (1 byte)
 | 
						|
    bytes.getByte();
 | 
						|
    // read and discard length bytes
 | 
						|
    asn1.getBerValueLength(bytes);
 | 
						|
    bytes = bytes.getBytes();
 | 
						|
 | 
						|
    // digest content DER value bytes
 | 
						|
    for(var oid in mds) {
 | 
						|
      mds[oid].start().update(bytes);
 | 
						|
    }
 | 
						|
 | 
						|
    // sign content
 | 
						|
    var signingTime = new Date();
 | 
						|
    for(var i = 0; i < msg.signers.length; ++i) {
 | 
						|
      var signer = msg.signers[i];
 | 
						|
 | 
						|
      if(signer.authenticatedAttributes.length === 0) {
 | 
						|
        // if ContentInfo content type is not "Data", then
 | 
						|
        // authenticatedAttributes must be present per RFC 2315
 | 
						|
        if(contentType !== forge.pki.oids.data) {
 | 
						|
          throw new Error(
 | 
						|
            'Invalid signer; authenticatedAttributes must be present ' +
 | 
						|
            'when the ContentInfo content type is not PKCS#7 Data.');
 | 
						|
        }
 | 
						|
      } else {
 | 
						|
        // process authenticated attributes
 | 
						|
        // [0] IMPLICIT
 | 
						|
        signer.authenticatedAttributesAsn1 = asn1.create(
 | 
						|
          asn1.Class.CONTEXT_SPECIFIC, 0, true, []);
 | 
						|
 | 
						|
        // per RFC 2315, attributes are to be digested using a SET container
 | 
						|
        // not the above [0] IMPLICIT container
 | 
						|
        var attrsAsn1 = asn1.create(
 | 
						|
          asn1.Class.UNIVERSAL, asn1.Type.SET, true, []);
 | 
						|
 | 
						|
        for(var ai = 0; ai < signer.authenticatedAttributes.length; ++ai) {
 | 
						|
          var attr = signer.authenticatedAttributes[ai];
 | 
						|
          if(attr.type === forge.pki.oids.messageDigest) {
 | 
						|
            // use content message digest as value
 | 
						|
            attr.value = mds[signer.digestAlgorithm].digest();
 | 
						|
          } else if(attr.type === forge.pki.oids.signingTime) {
 | 
						|
            // auto-populate signing time if not already set
 | 
						|
            if(!attr.value) {
 | 
						|
              attr.value = signingTime;
 | 
						|
            }
 | 
						|
          }
 | 
						|
 | 
						|
          // convert to ASN.1 and push onto Attributes SET (for signing) and
 | 
						|
          // onto authenticatedAttributesAsn1 to complete SignedData ASN.1
 | 
						|
          // TODO: optimize away duplication
 | 
						|
          attrsAsn1.value.push(_attributeToAsn1(attr));
 | 
						|
          signer.authenticatedAttributesAsn1.value.push(_attributeToAsn1(attr));
 | 
						|
        }
 | 
						|
 | 
						|
        // DER-serialize and digest SET OF attributes only
 | 
						|
        bytes = asn1.toDer(attrsAsn1).getBytes();
 | 
						|
        signer.md.start().update(bytes);
 | 
						|
      }
 | 
						|
 | 
						|
      // sign digest
 | 
						|
      signer.signature = signer.key.sign(signer.md, 'RSASSA-PKCS1-V1_5');
 | 
						|
    }
 | 
						|
 | 
						|
    // add signer info
 | 
						|
    msg.signerInfos = _signersToAsn1(msg.signers);
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Creates an empty PKCS#7 message of type EncryptedData.
 | 
						|
 *
 | 
						|
 * @return the message.
 | 
						|
 */
 | 
						|
p7.createEncryptedData = function() {
 | 
						|
  var msg = null;
 | 
						|
  msg = {
 | 
						|
    type: forge.pki.oids.encryptedData,
 | 
						|
    version: 0,
 | 
						|
    encryptedContent: {
 | 
						|
      algorithm: forge.pki.oids['aes256-CBC']
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Reads an EncryptedData content block (in ASN.1 format)
 | 
						|
     *
 | 
						|
     * @param obj The ASN.1 representation of the EncryptedData content block
 | 
						|
     */
 | 
						|
    fromAsn1: function(obj) {
 | 
						|
      // Validate EncryptedData content block and capture data.
 | 
						|
      _fromAsn1(msg, obj, p7.asn1.encryptedDataValidator);
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Decrypt encrypted content
 | 
						|
     *
 | 
						|
     * @param key The (symmetric) key as a byte buffer
 | 
						|
     */
 | 
						|
    decrypt: function(key) {
 | 
						|
      if(key !== undefined) {
 | 
						|
        msg.encryptedContent.key = key;
 | 
						|
      }
 | 
						|
      _decryptContent(msg);
 | 
						|
    }
 | 
						|
  };
 | 
						|
  return msg;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Creates an empty PKCS#7 message of type EnvelopedData.
 | 
						|
 *
 | 
						|
 * @return the message.
 | 
						|
 */
 | 
						|
p7.createEnvelopedData = function() {
 | 
						|
  var msg = null;
 | 
						|
  msg = {
 | 
						|
    type: forge.pki.oids.envelopedData,
 | 
						|
    version: 0,
 | 
						|
    recipients: [],
 | 
						|
    encryptedContent: {
 | 
						|
      algorithm: forge.pki.oids['aes256-CBC']
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Reads an EnvelopedData content block (in ASN.1 format)
 | 
						|
     *
 | 
						|
     * @param obj the ASN.1 representation of the EnvelopedData content block.
 | 
						|
     */
 | 
						|
    fromAsn1: function(obj) {
 | 
						|
      // validate EnvelopedData content block and capture data
 | 
						|
      var capture = _fromAsn1(msg, obj, p7.asn1.envelopedDataValidator);
 | 
						|
      msg.recipients = _recipientsFromAsn1(capture.recipientInfos.value);
 | 
						|
    },
 | 
						|
 | 
						|
    toAsn1: function() {
 | 
						|
      // ContentInfo
 | 
						|
      return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
        // ContentType
 | 
						|
        asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | 
						|
          asn1.oidToDer(msg.type).getBytes()),
 | 
						|
        // [0] EnvelopedData
 | 
						|
        asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
 | 
						|
          asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
            // Version
 | 
						|
            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
 | 
						|
              asn1.integerToDer(msg.version).getBytes()),
 | 
						|
            // RecipientInfos
 | 
						|
            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true,
 | 
						|
              _recipientsToAsn1(msg.recipients)),
 | 
						|
            // EncryptedContentInfo
 | 
						|
            asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true,
 | 
						|
              _encryptedContentToAsn1(msg.encryptedContent))
 | 
						|
          ])
 | 
						|
        ])
 | 
						|
      ]);
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Find recipient by X.509 certificate's issuer.
 | 
						|
     *
 | 
						|
     * @param cert the certificate with the issuer to look for.
 | 
						|
     *
 | 
						|
     * @return the recipient object.
 | 
						|
     */
 | 
						|
    findRecipient: function(cert) {
 | 
						|
      var sAttr = cert.issuer.attributes;
 | 
						|
 | 
						|
      for(var i = 0; i < msg.recipients.length; ++i) {
 | 
						|
        var r = msg.recipients[i];
 | 
						|
        var rAttr = r.issuer;
 | 
						|
 | 
						|
        if(r.serialNumber !== cert.serialNumber) {
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
 | 
						|
        if(rAttr.length !== sAttr.length) {
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
 | 
						|
        var match = true;
 | 
						|
        for(var j = 0; j < sAttr.length; ++j) {
 | 
						|
          if(rAttr[j].type !== sAttr[j].type ||
 | 
						|
            rAttr[j].value !== sAttr[j].value) {
 | 
						|
            match = false;
 | 
						|
            break;
 | 
						|
          }
 | 
						|
        }
 | 
						|
 | 
						|
        if(match) {
 | 
						|
          return r;
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      return null;
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Decrypt enveloped content
 | 
						|
     *
 | 
						|
     * @param recipient The recipient object related to the private key
 | 
						|
     * @param privKey The (RSA) private key object
 | 
						|
     */
 | 
						|
    decrypt: function(recipient, privKey) {
 | 
						|
      if(msg.encryptedContent.key === undefined && recipient !== undefined &&
 | 
						|
        privKey !== undefined) {
 | 
						|
        switch(recipient.encryptedContent.algorithm) {
 | 
						|
          case forge.pki.oids.rsaEncryption:
 | 
						|
          case forge.pki.oids.desCBC:
 | 
						|
            var key = privKey.decrypt(recipient.encryptedContent.content);
 | 
						|
            msg.encryptedContent.key = forge.util.createBuffer(key);
 | 
						|
            break;
 | 
						|
 | 
						|
          default:
 | 
						|
            throw new Error('Unsupported asymmetric cipher, ' +
 | 
						|
              'OID ' + recipient.encryptedContent.algorithm);
 | 
						|
        }
 | 
						|
      }
 | 
						|
 | 
						|
      _decryptContent(msg);
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Add (another) entity to list of recipients.
 | 
						|
     *
 | 
						|
     * @param cert The certificate of the entity to add.
 | 
						|
     */
 | 
						|
    addRecipient: function(cert) {
 | 
						|
      msg.recipients.push({
 | 
						|
        version: 0,
 | 
						|
        issuer: cert.issuer.attributes,
 | 
						|
        serialNumber: cert.serialNumber,
 | 
						|
        encryptedContent: {
 | 
						|
          // We simply assume rsaEncryption here, since forge.pki only
 | 
						|
          // supports RSA so far.  If the PKI module supports other
 | 
						|
          // ciphers one day, we need to modify this one as well.
 | 
						|
          algorithm: forge.pki.oids.rsaEncryption,
 | 
						|
          key: cert.publicKey
 | 
						|
        }
 | 
						|
      });
 | 
						|
    },
 | 
						|
 | 
						|
    /**
 | 
						|
     * Encrypt enveloped content.
 | 
						|
     *
 | 
						|
     * This function supports two optional arguments, cipher and key, which
 | 
						|
     * can be used to influence symmetric encryption.  Unless cipher is
 | 
						|
     * provided, the cipher specified in encryptedContent.algorithm is used
 | 
						|
     * (defaults to AES-256-CBC).  If no key is provided, encryptedContent.key
 | 
						|
     * is (re-)used.  If that one's not set, a random key will be generated
 | 
						|
     * automatically.
 | 
						|
     *
 | 
						|
     * @param [key] The key to be used for symmetric encryption.
 | 
						|
     * @param [cipher] The OID of the symmetric cipher to use.
 | 
						|
     */
 | 
						|
    encrypt: function(key, cipher) {
 | 
						|
      // Part 1: Symmetric encryption
 | 
						|
      if(msg.encryptedContent.content === undefined) {
 | 
						|
        cipher = cipher || msg.encryptedContent.algorithm;
 | 
						|
        key = key || msg.encryptedContent.key;
 | 
						|
 | 
						|
        var keyLen, ivLen, ciphFn;
 | 
						|
        switch(cipher) {
 | 
						|
          case forge.pki.oids['aes128-CBC']:
 | 
						|
            keyLen = 16;
 | 
						|
            ivLen = 16;
 | 
						|
            ciphFn = forge.aes.createEncryptionCipher;
 | 
						|
            break;
 | 
						|
 | 
						|
          case forge.pki.oids['aes192-CBC']:
 | 
						|
            keyLen = 24;
 | 
						|
            ivLen = 16;
 | 
						|
            ciphFn = forge.aes.createEncryptionCipher;
 | 
						|
            break;
 | 
						|
 | 
						|
          case forge.pki.oids['aes256-CBC']:
 | 
						|
            keyLen = 32;
 | 
						|
            ivLen = 16;
 | 
						|
            ciphFn = forge.aes.createEncryptionCipher;
 | 
						|
            break;
 | 
						|
 | 
						|
          case forge.pki.oids['des-EDE3-CBC']:
 | 
						|
            keyLen = 24;
 | 
						|
            ivLen = 8;
 | 
						|
            ciphFn = forge.des.createEncryptionCipher;
 | 
						|
            break;
 | 
						|
 | 
						|
          default:
 | 
						|
            throw new Error('Unsupported symmetric cipher, OID ' + cipher);
 | 
						|
        }
 | 
						|
 | 
						|
        if(key === undefined) {
 | 
						|
          key = forge.util.createBuffer(forge.random.getBytes(keyLen));
 | 
						|
        } else if(key.length() != keyLen) {
 | 
						|
          throw new Error('Symmetric key has wrong length; ' +
 | 
						|
            'got ' + key.length() + ' bytes, expected ' + keyLen + '.');
 | 
						|
        }
 | 
						|
 | 
						|
        // Keep a copy of the key & IV in the object, so the caller can
 | 
						|
        // use it for whatever reason.
 | 
						|
        msg.encryptedContent.algorithm = cipher;
 | 
						|
        msg.encryptedContent.key = key;
 | 
						|
        msg.encryptedContent.parameter = forge.util.createBuffer(
 | 
						|
          forge.random.getBytes(ivLen));
 | 
						|
 | 
						|
        var ciph = ciphFn(key);
 | 
						|
        ciph.start(msg.encryptedContent.parameter.copy());
 | 
						|
        ciph.update(msg.content);
 | 
						|
 | 
						|
        // The finish function does PKCS#7 padding by default, therefore
 | 
						|
        // no action required by us.
 | 
						|
        if(!ciph.finish()) {
 | 
						|
          throw new Error('Symmetric encryption failed.');
 | 
						|
        }
 | 
						|
 | 
						|
        msg.encryptedContent.content = ciph.output;
 | 
						|
      }
 | 
						|
 | 
						|
      // Part 2: asymmetric encryption for each recipient
 | 
						|
      for(var i = 0; i < msg.recipients.length; ++i) {
 | 
						|
        var recipient = msg.recipients[i];
 | 
						|
 | 
						|
        // Nothing to do, encryption already done.
 | 
						|
        if(recipient.encryptedContent.content !== undefined) {
 | 
						|
          continue;
 | 
						|
        }
 | 
						|
 | 
						|
        switch(recipient.encryptedContent.algorithm) {
 | 
						|
          case forge.pki.oids.rsaEncryption:
 | 
						|
            recipient.encryptedContent.content =
 | 
						|
              recipient.encryptedContent.key.encrypt(
 | 
						|
                msg.encryptedContent.key.data);
 | 
						|
            break;
 | 
						|
 | 
						|
          default:
 | 
						|
            throw new Error('Unsupported asymmetric cipher, OID ' +
 | 
						|
              recipient.encryptedContent.algorithm);
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
  };
 | 
						|
  return msg;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Converts a single recipient from an ASN.1 object.
 | 
						|
 *
 | 
						|
 * @param obj the ASN.1 RecipientInfo.
 | 
						|
 *
 | 
						|
 * @return the recipient object.
 | 
						|
 */
 | 
						|
function _recipientFromAsn1(obj) {
 | 
						|
  // validate EnvelopedData content block and capture data
 | 
						|
  var capture = {};
 | 
						|
  var errors = [];
 | 
						|
  if(!asn1.validate(obj, p7.asn1.recipientInfoValidator, capture, errors)) {
 | 
						|
    var error = new Error('Cannot read PKCS#7 RecipientInfo. ' +
 | 
						|
      'ASN.1 object is not an PKCS#7 RecipientInfo.');
 | 
						|
    error.errors = errors;
 | 
						|
    throw error;
 | 
						|
  }
 | 
						|
 | 
						|
  return {
 | 
						|
    version: capture.version.charCodeAt(0),
 | 
						|
    issuer: forge.pki.RDNAttributesAsArray(capture.issuer),
 | 
						|
    serialNumber: forge.util.createBuffer(capture.serial).toHex(),
 | 
						|
    encryptedContent: {
 | 
						|
      algorithm: asn1.derToOid(capture.encAlgorithm),
 | 
						|
      parameter: capture.encParameter ? capture.encParameter.value : undefined,
 | 
						|
      content: capture.encKey
 | 
						|
    }
 | 
						|
  };
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Converts a single recipient object to an ASN.1 object.
 | 
						|
 *
 | 
						|
 * @param obj the recipient object.
 | 
						|
 *
 | 
						|
 * @return the ASN.1 RecipientInfo.
 | 
						|
 */
 | 
						|
function _recipientToAsn1(obj) {
 | 
						|
  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
    // Version
 | 
						|
    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
 | 
						|
      asn1.integerToDer(obj.version).getBytes()),
 | 
						|
    // IssuerAndSerialNumber
 | 
						|
    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
      // Name
 | 
						|
      forge.pki.distinguishedNameToAsn1({attributes: obj.issuer}),
 | 
						|
      // Serial
 | 
						|
      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
 | 
						|
        forge.util.hexToBytes(obj.serialNumber))
 | 
						|
    ]),
 | 
						|
    // KeyEncryptionAlgorithmIdentifier
 | 
						|
    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
      // Algorithm
 | 
						|
      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | 
						|
        asn1.oidToDer(obj.encryptedContent.algorithm).getBytes()),
 | 
						|
      // Parameter, force NULL, only RSA supported for now.
 | 
						|
      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
 | 
						|
    ]),
 | 
						|
    // EncryptedKey
 | 
						|
    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
 | 
						|
      obj.encryptedContent.content)
 | 
						|
  ]);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Map a set of RecipientInfo ASN.1 objects to recipient objects.
 | 
						|
 *
 | 
						|
 * @param infos an array of ASN.1 representations RecipientInfo (i.e. SET OF).
 | 
						|
 *
 | 
						|
 * @return an array of recipient objects.
 | 
						|
 */
 | 
						|
function _recipientsFromAsn1(infos) {
 | 
						|
  var ret = [];
 | 
						|
  for(var i = 0; i < infos.length; ++i) {
 | 
						|
    ret.push(_recipientFromAsn1(infos[i]));
 | 
						|
  }
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Map an array of recipient objects to ASN.1 RecipientInfo objects.
 | 
						|
 *
 | 
						|
 * @param recipients an array of recipientInfo objects.
 | 
						|
 *
 | 
						|
 * @return an array of ASN.1 RecipientInfos.
 | 
						|
 */
 | 
						|
function _recipientsToAsn1(recipients) {
 | 
						|
  var ret = [];
 | 
						|
  for(var i = 0; i < recipients.length; ++i) {
 | 
						|
    ret.push(_recipientToAsn1(recipients[i]));
 | 
						|
  }
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Converts a single signer from an ASN.1 object.
 | 
						|
 *
 | 
						|
 * @param obj the ASN.1 representation of a SignerInfo.
 | 
						|
 *
 | 
						|
 * @return the signer object.
 | 
						|
 */
 | 
						|
function _signerFromAsn1(obj) {
 | 
						|
  // validate EnvelopedData content block and capture data
 | 
						|
  var capture = {};
 | 
						|
  var errors = [];
 | 
						|
  if(!asn1.validate(obj, p7.asn1.signerInfoValidator, capture, errors)) {
 | 
						|
    var error = new Error('Cannot read PKCS#7 SignerInfo. ' +
 | 
						|
      'ASN.1 object is not an PKCS#7 SignerInfo.');
 | 
						|
    error.errors = errors;
 | 
						|
    throw error;
 | 
						|
  }
 | 
						|
 | 
						|
  var rval = {
 | 
						|
    version: capture.version.charCodeAt(0),
 | 
						|
    issuer: forge.pki.RDNAttributesAsArray(capture.issuer),
 | 
						|
    serialNumber: forge.util.createBuffer(capture.serial).toHex(),
 | 
						|
    digestAlgorithm: asn1.derToOid(capture.digestAlgorithm),
 | 
						|
    signatureAlgorithm: asn1.derToOid(capture.signatureAlgorithm),
 | 
						|
    signature: capture.signature,
 | 
						|
    authenticatedAttributes: [],
 | 
						|
    unauthenticatedAttributes: []
 | 
						|
  };
 | 
						|
 | 
						|
  // TODO: convert attributes
 | 
						|
  var authenticatedAttributes = capture.authenticatedAttributes || [];
 | 
						|
  var unauthenticatedAttributes = capture.unauthenticatedAttributes || [];
 | 
						|
 | 
						|
  return rval;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Converts a single signerInfo object to an ASN.1 object.
 | 
						|
 *
 | 
						|
 * @param obj the signerInfo object.
 | 
						|
 *
 | 
						|
 * @return the ASN.1 representation of a SignerInfo.
 | 
						|
 */
 | 
						|
function _signerToAsn1(obj) {
 | 
						|
  // SignerInfo
 | 
						|
  var rval = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
    // version
 | 
						|
    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
 | 
						|
      asn1.integerToDer(obj.version).getBytes()),
 | 
						|
    // issuerAndSerialNumber
 | 
						|
    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
      // name
 | 
						|
      forge.pki.distinguishedNameToAsn1({attributes: obj.issuer}),
 | 
						|
      // serial
 | 
						|
      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
 | 
						|
        forge.util.hexToBytes(obj.serialNumber))
 | 
						|
    ]),
 | 
						|
    // digestAlgorithm
 | 
						|
    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
      // algorithm
 | 
						|
      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | 
						|
        asn1.oidToDer(obj.digestAlgorithm).getBytes()),
 | 
						|
      // parameters (null)
 | 
						|
      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
 | 
						|
    ])
 | 
						|
  ]);
 | 
						|
 | 
						|
  // authenticatedAttributes (OPTIONAL)
 | 
						|
  if(obj.authenticatedAttributesAsn1) {
 | 
						|
    // add ASN.1 previously generated during signing
 | 
						|
    rval.value.push(obj.authenticatedAttributesAsn1);
 | 
						|
  }
 | 
						|
 | 
						|
  // digestEncryptionAlgorithm
 | 
						|
  rval.value.push(asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
    // algorithm
 | 
						|
    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | 
						|
      asn1.oidToDer(obj.signatureAlgorithm).getBytes()),
 | 
						|
    // parameters (null)
 | 
						|
    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
 | 
						|
  ]));
 | 
						|
 | 
						|
  // encryptedDigest
 | 
						|
  rval.value.push(asn1.create(
 | 
						|
    asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, obj.signature));
 | 
						|
 | 
						|
  // unauthenticatedAttributes (OPTIONAL)
 | 
						|
  if(obj.unauthenticatedAttributes.length > 0) {
 | 
						|
    // [1] IMPLICIT
 | 
						|
    var attrsAsn1 = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, []);
 | 
						|
    for(var i = 0; i < obj.unauthenticatedAttributes.length; ++i) {
 | 
						|
      var attr = obj.unauthenticatedAttributes[i];
 | 
						|
      attrsAsn1.values.push(_attributeToAsn1(attr));
 | 
						|
    }
 | 
						|
    rval.value.push(attrsAsn1);
 | 
						|
  }
 | 
						|
 | 
						|
  return rval;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Map a set of SignerInfo ASN.1 objects to an array of signer objects.
 | 
						|
 *
 | 
						|
 * @param signerInfoAsn1s an array of ASN.1 SignerInfos (i.e. SET OF).
 | 
						|
 *
 | 
						|
 * @return an array of signers objects.
 | 
						|
 */
 | 
						|
function _signersFromAsn1(signerInfoAsn1s) {
 | 
						|
  var ret = [];
 | 
						|
  for(var i = 0; i < signerInfoAsn1s.length; ++i) {
 | 
						|
    ret.push(_signerFromAsn1(signerInfoAsn1s[i]));
 | 
						|
  }
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Map an array of signer objects to ASN.1 objects.
 | 
						|
 *
 | 
						|
 * @param signers an array of signer objects.
 | 
						|
 *
 | 
						|
 * @return an array of ASN.1 SignerInfos.
 | 
						|
 */
 | 
						|
function _signersToAsn1(signers) {
 | 
						|
  var ret = [];
 | 
						|
  for(var i = 0; i < signers.length; ++i) {
 | 
						|
    ret.push(_signerToAsn1(signers[i]));
 | 
						|
  }
 | 
						|
  return ret;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Convert an attribute object to an ASN.1 Attribute.
 | 
						|
 *
 | 
						|
 * @param attr the attribute object.
 | 
						|
 *
 | 
						|
 * @return the ASN.1 Attribute.
 | 
						|
 */
 | 
						|
function _attributeToAsn1(attr) {
 | 
						|
  var value;
 | 
						|
 | 
						|
  // TODO: generalize to support more attributes
 | 
						|
  if(attr.type === forge.pki.oids.contentType) {
 | 
						|
    value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | 
						|
      asn1.oidToDer(attr.value).getBytes());
 | 
						|
  } else if(attr.type === forge.pki.oids.messageDigest) {
 | 
						|
    value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
 | 
						|
      attr.value.bytes());
 | 
						|
  } else if(attr.type === forge.pki.oids.signingTime) {
 | 
						|
    /* Note per RFC 2985: Dates between 1 January 1950 and 31 December 2049
 | 
						|
      (inclusive) MUST be encoded as UTCTime. Any dates with year values
 | 
						|
      before 1950 or after 2049 MUST be encoded as GeneralizedTime. [Further,]
 | 
						|
      UTCTime values MUST be expressed in Greenwich Mean Time (Zulu) and MUST
 | 
						|
      include seconds (i.e., times are YYMMDDHHMMSSZ), even where the
 | 
						|
      number of seconds is zero.  Midnight (GMT) must be represented as
 | 
						|
      "YYMMDD000000Z". */
 | 
						|
    // TODO: make these module-level constants
 | 
						|
    var jan_1_1950 = new Date('1950-01-01T00:00:00Z');
 | 
						|
    var jan_1_2050 = new Date('2050-01-01T00:00:00Z');
 | 
						|
    var date = attr.value;
 | 
						|
    if(typeof date === 'string') {
 | 
						|
      // try to parse date
 | 
						|
      var timestamp = Date.parse(date);
 | 
						|
      if(!isNaN(timestamp)) {
 | 
						|
        date = new Date(timestamp);
 | 
						|
      } else if(date.length === 13) {
 | 
						|
        // YYMMDDHHMMSSZ (13 chars for UTCTime)
 | 
						|
        date = asn1.utcTimeToDate(date);
 | 
						|
      } else {
 | 
						|
        // assume generalized time
 | 
						|
        date = asn1.generalizedTimeToDate(date);
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if(date >= jan_1_1950 && date < jan_1_2050) {
 | 
						|
      value = asn1.create(
 | 
						|
        asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false,
 | 
						|
        asn1.dateToUtcTime(date));
 | 
						|
    } else {
 | 
						|
      value = asn1.create(
 | 
						|
        asn1.Class.UNIVERSAL, asn1.Type.GENERALIZEDTIME, false,
 | 
						|
        asn1.dateToGeneralizedTime(date));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  // TODO: expose as common API call
 | 
						|
  // create a RelativeDistinguishedName set
 | 
						|
  // each value in the set is an AttributeTypeAndValue first
 | 
						|
  // containing the type (an OID) and second the value
 | 
						|
  return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
    // AttributeType
 | 
						|
    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | 
						|
      asn1.oidToDer(attr.type).getBytes()),
 | 
						|
    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
 | 
						|
      // AttributeValue
 | 
						|
      value
 | 
						|
    ])
 | 
						|
  ]);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Map messages encrypted content to ASN.1 objects.
 | 
						|
 *
 | 
						|
 * @param ec The encryptedContent object of the message.
 | 
						|
 *
 | 
						|
 * @return ASN.1 representation of the encryptedContent object (SEQUENCE).
 | 
						|
 */
 | 
						|
function _encryptedContentToAsn1(ec) {
 | 
						|
  return [
 | 
						|
    // ContentType, always Data for the moment
 | 
						|
    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | 
						|
      asn1.oidToDer(forge.pki.oids.data).getBytes()),
 | 
						|
    // ContentEncryptionAlgorithmIdentifier
 | 
						|
    asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
 | 
						|
      // Algorithm
 | 
						|
      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
 | 
						|
        asn1.oidToDer(ec.algorithm).getBytes()),
 | 
						|
      // Parameters (IV)
 | 
						|
      !ec.parameter ?
 | 
						|
        undefined :
 | 
						|
        asn1.create(
 | 
						|
          asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
 | 
						|
          ec.parameter.getBytes())
 | 
						|
    ]),
 | 
						|
    // [0] EncryptedContent
 | 
						|
    asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
 | 
						|
      asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
 | 
						|
        ec.content.getBytes())
 | 
						|
    ])
 | 
						|
  ];
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Reads the "common part" of an PKCS#7 content block (in ASN.1 format)
 | 
						|
 *
 | 
						|
 * This function reads the "common part" of the PKCS#7 content blocks
 | 
						|
 * EncryptedData and EnvelopedData, i.e. version number and symmetrically
 | 
						|
 * encrypted content block.
 | 
						|
 *
 | 
						|
 * The result of the ASN.1 validate and capture process is returned
 | 
						|
 * to allow the caller to extract further data, e.g. the list of recipients
 | 
						|
 * in case of a EnvelopedData object.
 | 
						|
 *
 | 
						|
 * @param msg the PKCS#7 object to read the data to.
 | 
						|
 * @param obj the ASN.1 representation of the content block.
 | 
						|
 * @param validator the ASN.1 structure validator object to use.
 | 
						|
 *
 | 
						|
 * @return the value map captured by validator object.
 | 
						|
 */
 | 
						|
function _fromAsn1(msg, obj, validator) {
 | 
						|
  var capture = {};
 | 
						|
  var errors = [];
 | 
						|
  if(!asn1.validate(obj, validator, capture, errors)) {
 | 
						|
    var error = new Error('Cannot read PKCS#7 message. ' +
 | 
						|
      'ASN.1 object is not a supported PKCS#7 message.');
 | 
						|
    error.errors = error;
 | 
						|
    throw error;
 | 
						|
  }
 | 
						|
 | 
						|
  // Check contentType, so far we only support (raw) Data.
 | 
						|
  var contentType = asn1.derToOid(capture.contentType);
 | 
						|
  if(contentType !== forge.pki.oids.data) {
 | 
						|
    throw new Error('Unsupported PKCS#7 message. ' +
 | 
						|
      'Only wrapped ContentType Data supported.');
 | 
						|
  }
 | 
						|
 | 
						|
  if(capture.encryptedContent) {
 | 
						|
    var content = '';
 | 
						|
    if(forge.util.isArray(capture.encryptedContent)) {
 | 
						|
      for(var i = 0; i < capture.encryptedContent.length; ++i) {
 | 
						|
        if(capture.encryptedContent[i].type !== asn1.Type.OCTETSTRING) {
 | 
						|
          throw new Error('Malformed PKCS#7 message, expecting encrypted ' +
 | 
						|
            'content constructed of only OCTET STRING objects.');
 | 
						|
        }
 | 
						|
        content += capture.encryptedContent[i].value;
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      content = capture.encryptedContent;
 | 
						|
    }
 | 
						|
    msg.encryptedContent = {
 | 
						|
      algorithm: asn1.derToOid(capture.encAlgorithm),
 | 
						|
      parameter: forge.util.createBuffer(capture.encParameter.value),
 | 
						|
      content: forge.util.createBuffer(content)
 | 
						|
    };
 | 
						|
  }
 | 
						|
 | 
						|
  if(capture.content) {
 | 
						|
    var content = '';
 | 
						|
    if(forge.util.isArray(capture.content)) {
 | 
						|
      for(var i = 0; i < capture.content.length; ++i) {
 | 
						|
        if(capture.content[i].type !== asn1.Type.OCTETSTRING) {
 | 
						|
          throw new Error('Malformed PKCS#7 message, expecting ' +
 | 
						|
            'content constructed of only OCTET STRING objects.');
 | 
						|
        }
 | 
						|
        content += capture.content[i].value;
 | 
						|
      }
 | 
						|
    } else {
 | 
						|
      content = capture.content;
 | 
						|
    }
 | 
						|
    msg.content = forge.util.createBuffer(content);
 | 
						|
  }
 | 
						|
 | 
						|
  msg.version = capture.version.charCodeAt(0);
 | 
						|
  msg.rawCapture = capture;
 | 
						|
 | 
						|
  return capture;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Decrypt the symmetrically encrypted content block of the PKCS#7 message.
 | 
						|
 *
 | 
						|
 * Decryption is skipped in case the PKCS#7 message object already has a
 | 
						|
 * (decrypted) content attribute.  The algorithm, key and cipher parameters
 | 
						|
 * (probably the iv) are taken from the encryptedContent attribute of the
 | 
						|
 * message object.
 | 
						|
 *
 | 
						|
 * @param The PKCS#7 message object.
 | 
						|
 */
 | 
						|
function _decryptContent(msg) {
 | 
						|
  if(msg.encryptedContent.key === undefined) {
 | 
						|
    throw new Error('Symmetric key not available.');
 | 
						|
  }
 | 
						|
 | 
						|
  if(msg.content === undefined) {
 | 
						|
    var ciph;
 | 
						|
 | 
						|
    switch(msg.encryptedContent.algorithm) {
 | 
						|
      case forge.pki.oids['aes128-CBC']:
 | 
						|
      case forge.pki.oids['aes192-CBC']:
 | 
						|
      case forge.pki.oids['aes256-CBC']:
 | 
						|
        ciph = forge.aes.createDecryptionCipher(msg.encryptedContent.key);
 | 
						|
        break;
 | 
						|
 | 
						|
      case forge.pki.oids['desCBC']:
 | 
						|
      case forge.pki.oids['des-EDE3-CBC']:
 | 
						|
        ciph = forge.des.createDecryptionCipher(msg.encryptedContent.key);
 | 
						|
        break;
 | 
						|
 | 
						|
      default:
 | 
						|
        throw new Error('Unsupported symmetric cipher, OID ' +
 | 
						|
          msg.encryptedContent.algorithm);
 | 
						|
    }
 | 
						|
    ciph.start(msg.encryptedContent.parameter);
 | 
						|
    ciph.update(msg.encryptedContent.content);
 | 
						|
 | 
						|
    if(!ciph.finish()) {
 | 
						|
      throw new Error('Symmetric decryption failed.');
 | 
						|
    }
 | 
						|
 | 
						|
    msg.content = ciph.output;
 | 
						|
  }
 | 
						|
}
 |