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.
		
		
		
		
		
			
		
			
				
					
					
						
							1257 lines
						
					
					
						
							39 KiB
						
					
					
				
			
		
		
	
	
							1257 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.value, | 
						|
      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) | 
						|
      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; | 
						|
  } | 
						|
}
 | 
						|
 |