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
			| 
											2 years ago
										 | /** | ||
|  |  * 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; | ||
|  |   } | ||
|  | } |