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