summaryrefslogtreecommitdiff
path: root/node_modules/node-forge/lib/pkcs7.js
diff options
context:
space:
mode:
authorakiyamn2023-09-24 23:22:21 +1000
committerakiyamn2023-09-24 23:22:21 +1000
commit4e87195739f2a5d9a05451b48773c8afdc680765 (patch)
tree9cba501844a4a11dcbdffc4050ed8189561c55ed /node_modules/node-forge/lib/pkcs7.js
downloadprice-tracker-worker-4e87195739f2a5d9a05451b48773c8afdc680765.tar.gz
price-tracker-worker-4e87195739f2a5d9a05451b48773c8afdc680765.zip
Initial commit (by create-cloudflare CLI)
Diffstat (limited to 'node_modules/node-forge/lib/pkcs7.js')
-rw-r--r--node_modules/node-forge/lib/pkcs7.js1260
1 files changed, 1260 insertions, 0 deletions
diff --git a/node_modules/node-forge/lib/pkcs7.js b/node_modules/node-forge/lib/pkcs7.js
new file mode 100644
index 0000000..3a5d845
--- /dev/null
+++ b/node_modules/node-forge/lib/pkcs7.js
@@ -0,0 +1,1260 @@
+/**
+ * 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;
+ }
+}