More authenticode.js improvements.

Ylian Saint-Hilaire committed May 27, 2022 at 17:34 UTC fb83290c63358928848ba126a629e1d2eb369e84
2 files changed +1321 -26
authenticode.js
+58 -26
@@ -12,7 +12,7 @@ function createAuthenticodeHandler(path) {
12 const crypto = require('crypto');
13 const forge = require('node-forge');
14 const pki = forge.pki;
15 - const p7 = forge.pkcs7;
15 + const p7 = require('./pkcs7-modified');
16 obj.header = { path: path }
17
18 // Read a file slice
@@ -116,12 +116,17 @@ function createAuthenticodeHandler(path) {
116 if (!pkcs7.verify(caStore)) { throw ('Executable file has an invalid signature.'); }
117 */
118
119 - // ucs2/ucs-2/utf16le/utf-16le
119 + // Get the signing attributes
120 obj.signingAttribs = [];
121 for (var i in pkcs7.rawCapture.authenticatedAttributes) {
122 if (forge.asn1.derToOid(pkcs7.rawCapture.authenticatedAttributes[i].value[0].value) == obj.Oids.SPC_SP_OPUS_INFO_OBJID) {
123 for (var j in pkcs7.rawCapture.authenticatedAttributes[i].value[1].value[0].value) {
124 - obj.signingAttribs.push(pkcs7.rawCapture.authenticatedAttributes[i].value[1].value[0].value[j].value[0].value);
124 + var v = pkcs7.rawCapture.authenticatedAttributes[i].value[1].value[0].value[j].value[0].value;
125 + if (v.startsWith('http://') || v.startsWith('https://') || ((v.length % 2) == 1)) { obj.signingAttribs.push(v); } else {
126 + var r = ""; // This string value is in UCS2 format, convert it to a normal string.
127 + for (var k = 0; k < v.length; k += 2) { r += String.fromCharCode((v.charCodeAt(k + 8) << 8) + v.charCodeAt(k + 1)); }
128 + obj.signingAttribs.push(r);
129 + }
130 }
131 }
132 }
@@ -304,58 +309,85 @@ function createAuthenticodeHandler(path) {
309 }
310
311 function start() {
312 + // Parse the arguments
313 + const args = require('minimist')(process.argv.slice(2));
314 +
315 // Show tool help
308 - if (process.argv.length < 4) {
316 + if (process.argv.length < 3) {
317 console.log("MeshCentral Authenticode Tool.");
318 console.log("Usage:");
311 - console.log(" node authenticode.js [command] [exepath]");
319 + console.log(" node authenticode.js [command] [options]");
320 console.log("Commands:");
313 - console.log(" info - Show information about an executable.");
314 - console.log(" sign - Sign an executable using a dummy certificate.");
315 - console.log(" sign [exepath] (description) (url)");
316 - console.log(" unsign - Remove the signature from the executable.");
321 + console.log(" info: Show information about an executable.");
322 + console.log(" --json Optional, Show information in JSON format.");
323 + console.log(" sign: Sign an executable.");
324 + console.log(" --exe [file] Executable to sign.");
325 + console.log(" --out [file] Optional resulting signed executable.");
326 + console.log(" --cert [pemfile] Certificate to sign the executable with.");
327 + console.log(" --key [pemfile] Private key to use to sign the executable.");
328 + console.log(" --desc [description] Optional description string to embbed into signature.");
329 + console.log(" --url [url] Optional URL to embbed into signature.");
330 + console.log(" unsign: Remove the signature from the executable.");
331 + console.log(" --exe [file] Executable to un-sign.");
332 + console.log(" --out [file] Optional resulting executable with signature removed.");
333 + console.log(" createcert: Create a self-signed certificate and key.");
334 + console.log(" --cn [commonName] Certificate common name.");
335 return;
336 }
337
338 // Check that a valid command is passed in
321 - if (['info', 'sign', 'unsign'].indexOf(process.argv[2].toLowerCase()) == -1) {
339 + if (['info', 'sign', 'unsign', 'createcert'].indexOf(process.argv[2].toLowerCase()) == -1) {
340 console.log("Invalid command: " + process.argv[2]);
341 + console.log("Valid commands are: info, sign, unsign, createcert");
342 return;
343 }
344
326 - // Check the file exists
327 - var stats = null;
328 - try { stats = require('fs').statSync(process.argv[3]); } catch (ex) { }
329 - if (stats == null) {
330 - console.log("Unable to open file: " + process.argv[3]);
331 - return;
345 + var exe = null;
346 + if (args.exe) {
347 + // Check the file exists and open the file
348 + var stats = null;
349 + try { stats = require('fs').statSync(args.exe); } catch (ex) { }
350 + if (stats == null) { console.log("Unable to executable open file: " + args.exe); return; }
351 + exe = createAuthenticodeHandler(args.exe);
352 }
353
334 - // Open the file
335 - var exe = createAuthenticodeHandler(process.argv[3]);
336 -
354 // Execute the command
355 var command = process.argv[2].toLowerCase();
356 if (command == 'info') {
340 - console.log('Header', exe.header);
341 - if (exe.fileHashAlgo != null) { console.log('fileHashMethod:', exe.fileHashAlgo); }
342 - if (exe.fileHashSigned != null) { console.log('fileHashSigned:', exe.fileHashSigned.toString('hex')); }
343 - if (exe.fileHashActual != null) { console.log('fileHashActual:', exe.fileHashActual.toString('hex')); }
344 - if (exe.signingAttribs && exe.signingAttribs.length > 0) { console.log('Signature Attributes:'); for (var i in exe.signingAttribs) { console.log(' ' + exe.signingAttribs[i]); } }
345 - console.log('FileLen: ' + exe.filesize);
357 + if (exe == null) { console.log("Missing --exe [filename]"); return; }
358 + if (args.json) {
359 + var r = { header: exe.header, filesize: exe.filesize }
360 + if (exe.fileHashAlgo != null) { r.hashMethod = exe.fileHashAlgo; }
361 + if (exe.fileHashSigned != null) { r.hashSigned = exe.fileHashSigned.toString('hex'); }
362 + if (exe.fileHashActual != null) { r.hashActual = exe.fileHashActual.toString('hex'); }
363 + if (exe.signingAttribs && exe.signingAttribs.length > 0) { r.signAttributes = exe.signingAttribs; }
364 + console.log(JSON.stringify(r, null, 2));
365 + } else {
366 + console.log('Header', exe.header);
367 + if (exe.fileHashAlgo != null) { console.log('fileHashMethod:', exe.fileHashAlgo); }
368 + if (exe.fileHashSigned != null) { console.log('fileHashSigned:', exe.fileHashSigned.toString('hex')); }
369 + if (exe.fileHashActual != null) { console.log('fileHashActual:', exe.fileHashActual.toString('hex')); }
370 + if (exe.signingAttribs && exe.signingAttribs.length > 0) { console.log('Signature Attributes:'); for (var i in exe.signingAttribs) { console.log(' ' + exe.signingAttribs[i]); } }
371 + console.log('FileLen: ' + exe.filesize);
372 + }
373 }
374 if (command == 'sign') {
375 + if (exe == null) { console.log("Missing --exe [filename]"); return; }
376 var desc = null, url = null;
377 if (process.argv.length > 4) { desc = process.argv[4]; }
378 if (process.argv.length > 5) { url = process.argv[5]; }
379 console.log('Signing...'); exe.sign(null, null, desc, url); console.log('Done.');
380 }
381 if (command == 'unsign') {
382 + if (exe == null) { console.log("Missing --exe [filename]"); return; }
383 if (exe.header.signed) { console.log('Unsigning...'); exe.unsign(); console.log('Done.'); } else { console.log('Executable is not signed.'); }
384 }
385 + if (command == 'createcert') {
386 +
387 + }
388
389 // Close the file
358 - exe.close();
390 + if (exe != null) { exe.close(); }
391 }
392
393 start();
\ No newline at end of file
pkcs7-modified.js new
+1263
@@ -0,0 +1,1263 @@
1 +/**
2 + * Javascript implementation of PKCS#7 v1.5.
3 + *
4 + * @author Stefan Siegl
5 + * @author Dave Longley
6 + *
7 + * Copyright (c) 2012 Stefan Siegl <stesie@brokenpipe.de>
8 + * Copyright (c) 2012-2015 Digital Bazaar, Inc.
9 + *
10 + * Currently this implementation only supports ContentType of EnvelopedData,
11 + * EncryptedData, or SignedData at the root level. The top level elements may
12 + * contain only a ContentInfo of ContentType Data, i.e. plain data. Further
13 + * nesting is not (yet) supported.
14 + *
15 + * The Forge validators for PKCS #7's ASN.1 structures are available from
16 + * a separate file pkcs7asn1.js, since those are referenced from other
17 + * PKCS standards like PKCS #12.
18 + */
19 +var forge = require('./node_modules/node-forge/lib/forge');
20 +require('./node_modules/node-forge/lib/aes');
21 +require('./node_modules/node-forge/lib/asn1');
22 +require('./node_modules/node-forge/lib/des');
23 +require('./node_modules/node-forge/lib/oids');
24 +require('./node_modules/node-forge/lib/pem');
25 +require('./node_modules/node-forge/lib/pkcs7asn1');
26 +require('./node_modules/node-forge/lib/random');
27 +require('./node_modules/node-forge/lib/util');
28 +require('./node_modules/node-forge/lib/x509');
29 +
30 +// shortcut for ASN.1 API
31 +var asn1 = forge.asn1;
32 +
33 +// shortcut for PKCS#7 API
34 +var p7 = module.exports = forge.pkcs7 = forge.pkcs7 || {};
35 +
36 +/**
37 + * Converts a PKCS#7 message from PEM format.
38 + *
39 + * @param pem the PEM-formatted PKCS#7 message.
40 + *
41 + * @return the PKCS#7 message.
42 + */
43 +p7.messageFromPem = function(pem) {
44 + var msg = forge.pem.decode(pem)[0];
45 +
46 + if(msg.type !== 'PKCS7') {
47 + var error = new Error('Could not convert PKCS#7 message from PEM; PEM ' +
48 + 'header type is not "PKCS#7".');
49 + error.headerType = msg.type;
50 + throw error;
51 + }
52 + if(msg.procType && msg.procType.type === 'ENCRYPTED') {
53 + throw new Error('Could not convert PKCS#7 message from PEM; PEM is encrypted.');
54 + }
55 +
56 + // convert DER to ASN.1 object
57 + var obj = asn1.fromDer(msg.body);
58 +
59 + return p7.messageFromAsn1(obj);
60 +};
61 +
62 +/**
63 + * Converts a PKCS#7 message to PEM format.
64 + *
65 + * @param msg The PKCS#7 message object
66 + * @param maxline The maximum characters per line, defaults to 64.
67 + *
68 + * @return The PEM-formatted PKCS#7 message.
69 + */
70 +p7.messageToPem = function(msg, maxline) {
71 + // convert to ASN.1, then DER, then PEM-encode
72 + var pemObj = {
73 + type: 'PKCS7',
74 + body: asn1.toDer(msg.toAsn1()).getBytes()
75 + };
76 + return forge.pem.encode(pemObj, {maxline: maxline});
77 +};
78 +
79 +/**
80 + * Converts a PKCS#7 message from an ASN.1 object.
81 + *
82 + * @param obj the ASN.1 representation of a ContentInfo.
83 + *
84 + * @return the PKCS#7 message.
85 + */
86 +p7.messageFromAsn1 = function(obj) {
87 + // validate root level ContentInfo and capture data
88 + var capture = {};
89 + var errors = [];
90 + if(!asn1.validate(obj, p7.asn1.contentInfoValidator, capture, errors)) {
91 + var error = new Error('Cannot read PKCS#7 message. ' +
92 + 'ASN.1 object is not an PKCS#7 ContentInfo.');
93 + error.errors = errors;
94 + throw error;
95 + }
96 +
97 + var contentType = asn1.derToOid(capture.contentType);
98 + var msg;
99 +
100 + switch(contentType) {
101 + case forge.pki.oids.envelopedData:
102 + msg = p7.createEnvelopedData();
103 + break;
104 +
105 + case forge.pki.oids.encryptedData:
106 + msg = p7.createEncryptedData();
107 + break;
108 +
109 + case forge.pki.oids.signedData:
110 + msg = p7.createSignedData();
111 + break;
112 +
113 + default:
114 + throw new Error('Cannot read PKCS#7 message. ContentType with OID ' +
115 + contentType + ' is not (yet) supported.');
116 + }
117 +
118 + msg.fromAsn1(capture.content.value[0]);
119 + return msg;
120 +};
121 +
122 +p7.createSignedData = function() {
123 + var msg = null;
124 + msg = {
125 + type: forge.pki.oids.signedData,
126 + version: 1,
127 + certificates: [],
128 + crls: [],
129 + // TODO: add json-formatted signer stuff here?
130 + signers: [],
131 + // populated during sign()
132 + digestAlgorithmIdentifiers: [],
133 + contentInfo: null,
134 + signerInfos: [],
135 +
136 + fromAsn1: function(obj) {
137 + // validate SignedData content block and capture data.
138 + _fromAsn1(msg, obj, p7.asn1.signedDataValidator);
139 + msg.certificates = [];
140 + msg.crls = [];
141 + msg.digestAlgorithmIdentifiers = [];
142 + msg.contentInfo = null;
143 + msg.signerInfos = [];
144 +
145 + if(msg.rawCapture.certificates) {
146 + var certs = msg.rawCapture.certificates.value;
147 + for(var i = 0; i < certs.length; ++i) {
148 + msg.certificates.push(forge.pki.certificateFromAsn1(certs[i]));
149 + }
150 + }
151 +
152 + // TODO: parse crls
153 + },
154 +
155 + toAsn1: function() {
156 + // degenerate case with no content
157 + if(!msg.contentInfo) {
158 + msg.sign();
159 + }
160 +
161 + var certs = [];
162 + for(var i = 0; i < msg.certificates.length; ++i) {
163 + certs.push(forge.pki.certificateToAsn1(msg.certificates[i]));
164 + }
165 +
166 + var crls = [];
167 + // TODO: implement CRLs
168 +
169 + // [0] SignedData
170 + var signedData = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
171 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
172 + // Version
173 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
174 + asn1.integerToDer(msg.version).getBytes()),
175 + // DigestAlgorithmIdentifiers
176 + asn1.create(
177 + asn1.Class.UNIVERSAL, asn1.Type.SET, true,
178 + msg.digestAlgorithmIdentifiers),
179 + // ContentInfo
180 + msg.contentInfo
181 + ])
182 + ]);
183 + if(certs.length > 0) {
184 + // [0] IMPLICIT ExtendedCertificatesAndCertificates OPTIONAL
185 + signedData.value[0].value.push(
186 + asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, certs));
187 + }
188 + if(crls.length > 0) {
189 + // [1] IMPLICIT CertificateRevocationLists OPTIONAL
190 + signedData.value[0].value.push(
191 + asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, crls));
192 + }
193 + // SignerInfos
194 + signedData.value[0].value.push(
195 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true,
196 + msg.signerInfos));
197 +
198 + // ContentInfo
199 + return asn1.create(
200 + asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
201 + // ContentType
202 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
203 + asn1.oidToDer(msg.type).getBytes()),
204 + // [0] SignedData
205 + signedData
206 + ]);
207 + },
208 +
209 + /**
210 + * Add (another) entity to list of signers.
211 + *
212 + * Note: If authenticatedAttributes are provided, then, per RFC 2315,
213 + * they must include at least two attributes: content type and
214 + * message digest. The message digest attribute value will be
215 + * auto-calculated during signing and will be ignored if provided.
216 + *
217 + * Here's an example of providing these two attributes:
218 + *
219 + * forge.pkcs7.createSignedData();
220 + * p7.addSigner({
221 + * issuer: cert.issuer.attributes,
222 + * serialNumber: cert.serialNumber,
223 + * key: privateKey,
224 + * digestAlgorithm: forge.pki.oids.sha1,
225 + * authenticatedAttributes: [{
226 + * type: forge.pki.oids.contentType,
227 + * value: forge.pki.oids.data
228 + * }, {
229 + * type: forge.pki.oids.messageDigest
230 + * }]
231 + * });
232 + *
233 + * TODO: Support [subjectKeyIdentifier] as signer's ID.
234 + *
235 + * @param signer the signer information:
236 + * key the signer's private key.
237 + * [certificate] a certificate containing the public key
238 + * associated with the signer's private key; use this option as
239 + * an alternative to specifying signer.issuer and
240 + * signer.serialNumber.
241 + * [issuer] the issuer attributes (eg: cert.issuer.attributes).
242 + * [serialNumber] the signer's certificate's serial number in
243 + * hexadecimal (eg: cert.serialNumber).
244 + * [digestAlgorithm] the message digest OID, as a string, to use
245 + * (eg: forge.pki.oids.sha1).
246 + * [authenticatedAttributes] an optional array of attributes
247 + * to also sign along with the content.
248 + */
249 + addSigner: function(signer) {
250 + var issuer = signer.issuer;
251 + var serialNumber = signer.serialNumber;
252 + if(signer.certificate) {
253 + var cert = signer.certificate;
254 + if(typeof cert === 'string') {
255 + cert = forge.pki.certificateFromPem(cert);
256 + }
257 + issuer = cert.issuer.attributes;
258 + serialNumber = cert.serialNumber;
259 + }
260 + var key = signer.key;
261 + if(!key) {
262 + throw new Error(
263 + 'Could not add PKCS#7 signer; no private key specified.');
264 + }
265 + if(typeof key === 'string') {
266 + key = forge.pki.privateKeyFromPem(key);
267 + }
268 +
269 + // ensure OID known for digest algorithm
270 + var digestAlgorithm = signer.digestAlgorithm || forge.pki.oids.sha1;
271 + switch(digestAlgorithm) {
272 + case forge.pki.oids.sha1:
273 + case forge.pki.oids.sha256:
274 + case forge.pki.oids.sha384:
275 + case forge.pki.oids.sha512:
276 + case forge.pki.oids.md5:
277 + break;
278 + default:
279 + throw new Error(
280 + 'Could not add PKCS#7 signer; unknown message digest algorithm: ' +
281 + digestAlgorithm);
282 + }
283 +
284 + // if authenticatedAttributes is present, then the attributes
285 + // must contain at least PKCS #9 content-type and message-digest
286 + var authenticatedAttributes = signer.authenticatedAttributes || [];
287 + if(authenticatedAttributes.length > 0) {
288 + var contentType = false;
289 + var messageDigest = false;
290 + for(var i = 0; i < authenticatedAttributes.length; ++i) {
291 + var attr = authenticatedAttributes[i];
292 + if(!contentType && attr.type === forge.pki.oids.contentType) {
293 + contentType = true;
294 + if(messageDigest) {
295 + break;
296 + }
297 + continue;
298 + }
299 + if(!messageDigest && attr.type === forge.pki.oids.messageDigest) {
300 + messageDigest = true;
301 + if(contentType) {
302 + break;
303 + }
304 + continue;
305 + }
306 + }
307 +
308 + if(!contentType || !messageDigest) {
309 + throw new Error('Invalid signer.authenticatedAttributes. If ' +
310 + 'signer.authenticatedAttributes is specified, then it must ' +
311 + 'contain at least two attributes, PKCS #9 content-type and ' +
312 + 'PKCS #9 message-digest.');
313 + }
314 + }
315 +
316 + msg.signers.push({
317 + key: key,
318 + version: 1,
319 + issuer: issuer,
320 + serialNumber: serialNumber,
321 + digestAlgorithm: digestAlgorithm,
322 + signatureAlgorithm: forge.pki.oids.rsaEncryption,
323 + signature: null,
324 + authenticatedAttributes: authenticatedAttributes,
325 + unauthenticatedAttributes: []
326 + });
327 + },
328 +
329 + /**
330 + * Signs the content.
331 + * @param options Options to apply when signing:
332 + * [detached] boolean. If signing should be done in detached mode. Defaults to false.
333 + */
334 + sign: function(options) {
335 + options = options || {};
336 + // auto-generate content info
337 + if(typeof msg.content !== 'object' || msg.contentInfo === null) {
338 + // use Data ContentInfo
339 + msg.contentInfo = asn1.create(
340 + asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
341 + // ContentType
342 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
343 + asn1.oidToDer(forge.pki.oids.data).getBytes())
344 + ]);
345 +
346 + // add actual content, if present
347 + if('content' in msg) {
348 + var content;
349 + if(msg.content instanceof forge.util.ByteBuffer) {
350 + content = msg.content.bytes();
351 + } else if(typeof msg.content === 'string') {
352 + content = forge.util.encodeUtf8(msg.content);
353 + }
354 +
355 + if (options.detached) {
356 + msg.detachedContent = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, content);
357 + } else {
358 + msg.contentInfo.value.push(
359 + // [0] EXPLICIT content
360 + asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
361 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
362 + content)
363 + ]));
364 + }
365 + }
366 + }
367 +
368 + // no signers, return early (degenerate case for certificate container)
369 + if(msg.signers.length === 0) {
370 + return;
371 + }
372 +
373 + // generate digest algorithm identifiers
374 + var mds = addDigestAlgorithmIds();
375 +
376 + // generate signerInfos
377 + addSignerInfos(mds);
378 + },
379 +
380 + verify: function() {
381 + throw new Error('PKCS#7 signature verification not yet implemented.');
382 + },
383 +
384 + /**
385 + * Add a certificate.
386 + *
387 + * @param cert the certificate to add.
388 + */
389 + addCertificate: function(cert) {
390 + // convert from PEM
391 + if(typeof cert === 'string') {
392 + cert = forge.pki.certificateFromPem(cert);
393 + }
394 + msg.certificates.push(cert);
395 + },
396 +
397 + /**
398 + * Add a certificate revokation list.
399 + *
400 + * @param crl the certificate revokation list to add.
401 + */
402 + addCertificateRevokationList: function(crl) {
403 + throw new Error('PKCS#7 CRL support not yet implemented.');
404 + }
405 + };
406 + return msg;
407 +
408 + function addDigestAlgorithmIds() {
409 + var mds = {};
410 +
411 + for(var i = 0; i < msg.signers.length; ++i) {
412 + var signer = msg.signers[i];
413 + var oid = signer.digestAlgorithm;
414 + if(!(oid in mds)) {
415 + // content digest
416 + mds[oid] = forge.md[forge.pki.oids[oid]].create();
417 + }
418 + if(signer.authenticatedAttributes.length === 0) {
419 + // no custom attributes to digest; use content message digest
420 + signer.md = mds[oid];
421 + } else {
422 + // custom attributes to be digested; use own message digest
423 + // TODO: optimize to just copy message digest state if that
424 + // feature is ever supported with message digests
425 + signer.md = forge.md[forge.pki.oids[oid]].create();
426 + }
427 + }
428 +
429 + // add unique digest algorithm identifiers
430 + msg.digestAlgorithmIdentifiers = [];
431 + for(var oid in mds) {
432 + msg.digestAlgorithmIdentifiers.push(
433 + // AlgorithmIdentifier
434 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
435 + // algorithm
436 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
437 + asn1.oidToDer(oid).getBytes()),
438 + // parameters (null)
439 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
440 + ]));
441 + }
442 +
443 + return mds;
444 + }
445 +
446 + function addSignerInfos(mds) {
447 + var content;
448 +
449 + if (msg.detachedContent) {
450 + // Signature has been made in detached mode.
451 + content = msg.detachedContent;
452 + } else {
453 + // Note: ContentInfo is a SEQUENCE with 2 values, second value is
454 + // the content field and is optional for a ContentInfo but required here
455 + // since signers are present
456 + // get ContentInfo content
457 + content = msg.contentInfo.value[1];
458 + // skip [0] EXPLICIT content wrapper
459 + content = content.value[0];
460 + }
461 +
462 + if(!content) {
463 + throw new Error(
464 + 'Could not sign PKCS#7 message; there is no content to sign.');
465 + }
466 +
467 + // get ContentInfo content type
468 + var contentType = asn1.derToOid(msg.contentInfo.value[0].value);
469 +
470 + // serialize content
471 + var bytes = asn1.toDer(content);
472 +
473 + // skip identifier and length per RFC 2315 9.3
474 + // skip identifier (1 byte)
475 + bytes.getByte();
476 + // read and discard length bytes
477 + asn1.getBerValueLength(bytes);
478 + bytes = bytes.getBytes();
479 +
480 + // digest content DER value bytes
481 + for(var oid in mds) {
482 + mds[oid].start().update(bytes);
483 + }
484 +
485 + // sign content
486 + var signingTime = new Date();
487 + for(var i = 0; i < msg.signers.length; ++i) {
488 + var signer = msg.signers[i];
489 +
490 + if(signer.authenticatedAttributes.length === 0) {
491 + // if ContentInfo content type is not "Data", then
492 + // authenticatedAttributes must be present per RFC 2315
493 + if(contentType !== forge.pki.oids.data) {
494 + throw new Error(
495 + 'Invalid signer; authenticatedAttributes must be present ' +
496 + 'when the ContentInfo content type is not PKCS#7 Data.');
497 + }
498 + } else {
499 + // process authenticated attributes
500 + // [0] IMPLICIT
501 + signer.authenticatedAttributesAsn1 = asn1.create(
502 + asn1.Class.CONTEXT_SPECIFIC, 0, true, []);
503 +
504 + // per RFC 2315, attributes are to be digested using a SET container
505 + // not the above [0] IMPLICIT container
506 + var attrsAsn1 = asn1.create(
507 + asn1.Class.UNIVERSAL, asn1.Type.SET, true, []);
508 +
509 + for(var ai = 0; ai < signer.authenticatedAttributes.length; ++ai) {
510 + var attr = signer.authenticatedAttributes[ai];
511 + if(attr.type === forge.pki.oids.messageDigest) {
512 + // use content message digest as value
513 + attr.value = mds[signer.digestAlgorithm].digest();
514 + } else if(attr.type === forge.pki.oids.signingTime) {
515 + // auto-populate signing time if not already set
516 + if(!attr.value) {
517 + attr.value = signingTime;
518 + }
519 + }
520 +
521 + // convert to ASN.1 and push onto Attributes SET (for signing) and
522 + // onto authenticatedAttributesAsn1 to complete SignedData ASN.1
523 + // TODO: optimize away duplication
524 + attrsAsn1.value.push(_attributeToAsn1(attr));
525 + signer.authenticatedAttributesAsn1.value.push(_attributeToAsn1(attr));
526 + }
527 +
528 + // DER-serialize and digest SET OF attributes only
529 + bytes = asn1.toDer(attrsAsn1).getBytes();
530 + signer.md.start().update(bytes);
531 + }
532 +
533 + // sign digest
534 + signer.signature = signer.key.sign(signer.md, 'RSASSA-PKCS1-V1_5');
535 + }
536 +
537 + // add signer info
538 + msg.signerInfos = _signersToAsn1(msg.signers);
539 + }
540 +};
541 +
542 +/**
543 + * Creates an empty PKCS#7 message of type EncryptedData.
544 + *
545 + * @return the message.
546 + */
547 +p7.createEncryptedData = function() {
548 + var msg = null;
549 + msg = {
550 + type: forge.pki.oids.encryptedData,
551 + version: 0,
552 + encryptedContent: {
553 + algorithm: forge.pki.oids['aes256-CBC']
554 + },
555 +
556 + /**
557 + * Reads an EncryptedData content block (in ASN.1 format)
558 + *
559 + * @param obj The ASN.1 representation of the EncryptedData content block
560 + */
561 + fromAsn1: function(obj) {
562 + // Validate EncryptedData content block and capture data.
563 + _fromAsn1(msg, obj, p7.asn1.encryptedDataValidator);
564 + },
565 +
566 + /**
567 + * Decrypt encrypted content
568 + *
569 + * @param key The (symmetric) key as a byte buffer
570 + */
571 + decrypt: function(key) {
572 + if(key !== undefined) {
573 + msg.encryptedContent.key = key;
574 + }
575 + _decryptContent(msg);
576 + }
577 + };
578 + return msg;
579 +};
580 +
581 +/**
582 + * Creates an empty PKCS#7 message of type EnvelopedData.
583 + *
584 + * @return the message.
585 + */
586 +p7.createEnvelopedData = function() {
587 + var msg = null;
588 + msg = {
589 + type: forge.pki.oids.envelopedData,
590 + version: 0,
591 + recipients: [],
592 + encryptedContent: {
593 + algorithm: forge.pki.oids['aes256-CBC']
594 + },
595 +
596 + /**
597 + * Reads an EnvelopedData content block (in ASN.1 format)
598 + *
599 + * @param obj the ASN.1 representation of the EnvelopedData content block.
600 + */
601 + fromAsn1: function(obj) {
602 + // validate EnvelopedData content block and capture data
603 + var capture = _fromAsn1(msg, obj, p7.asn1.envelopedDataValidator);
604 + msg.recipients = _recipientsFromAsn1(capture.recipientInfos.value);
605 + },
606 +
607 + toAsn1: function() {
608 + // ContentInfo
609 + return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
610 + // ContentType
611 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
612 + asn1.oidToDer(msg.type).getBytes()),
613 + // [0] EnvelopedData
614 + asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
615 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
616 + // Version
617 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
618 + asn1.integerToDer(msg.version).getBytes()),
619 + // RecipientInfos
620 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true,
621 + _recipientsToAsn1(msg.recipients)),
622 + // EncryptedContentInfo
623 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true,
624 + _encryptedContentToAsn1(msg.encryptedContent))
625 + ])
626 + ])
627 + ]);
628 + },
629 +
630 + /**
631 + * Find recipient by X.509 certificate's issuer.
632 + *
633 + * @param cert the certificate with the issuer to look for.
634 + *
635 + * @return the recipient object.
636 + */
637 + findRecipient: function(cert) {
638 + var sAttr = cert.issuer.attributes;
639 +
640 + for(var i = 0; i < msg.recipients.length; ++i) {
641 + var r = msg.recipients[i];
642 + var rAttr = r.issuer;
643 +
644 + if(r.serialNumber !== cert.serialNumber) {
645 + continue;
646 + }
647 +
648 + if(rAttr.length !== sAttr.length) {
649 + continue;
650 + }
651 +
652 + var match = true;
653 + for(var j = 0; j < sAttr.length; ++j) {
654 + if(rAttr[j].type !== sAttr[j].type ||
655 + rAttr[j].value !== sAttr[j].value) {
656 + match = false;
657 + break;
658 + }
659 + }
660 +
661 + if(match) {
662 + return r;
663 + }
664 + }
665 +
666 + return null;
667 + },
668 +
669 + /**
670 + * Decrypt enveloped content
671 + *
672 + * @param recipient The recipient object related to the private key
673 + * @param privKey The (RSA) private key object
674 + */
675 + decrypt: function(recipient, privKey) {
676 + if(msg.encryptedContent.key === undefined && recipient !== undefined &&
677 + privKey !== undefined) {
678 + switch(recipient.encryptedContent.algorithm) {
679 + case forge.pki.oids.rsaEncryption:
680 + case forge.pki.oids.desCBC:
681 + var key = privKey.decrypt(recipient.encryptedContent.content);
682 + msg.encryptedContent.key = forge.util.createBuffer(key);
683 + break;
684 +
685 + default:
686 + throw new Error('Unsupported asymmetric cipher, ' +
687 + 'OID ' + recipient.encryptedContent.algorithm);
688 + }
689 + }
690 +
691 + _decryptContent(msg);
692 + },
693 +
694 + /**
695 + * Add (another) entity to list of recipients.
696 + *
697 + * @param cert The certificate of the entity to add.
698 + */
699 + addRecipient: function(cert) {
700 + msg.recipients.push({
701 + version: 0,
702 + issuer: cert.issuer.attributes,
703 + serialNumber: cert.serialNumber,
704 + encryptedContent: {
705 + // We simply assume rsaEncryption here, since forge.pki only
706 + // supports RSA so far. If the PKI module supports other
707 + // ciphers one day, we need to modify this one as well.
708 + algorithm: forge.pki.oids.rsaEncryption,
709 + key: cert.publicKey
710 + }
711 + });
712 + },
713 +
714 + /**
715 + * Encrypt enveloped content.
716 + *
717 + * This function supports two optional arguments, cipher and key, which
718 + * can be used to influence symmetric encryption. Unless cipher is
719 + * provided, the cipher specified in encryptedContent.algorithm is used
720 + * (defaults to AES-256-CBC). If no key is provided, encryptedContent.key
721 + * is (re-)used. If that one's not set, a random key will be generated
722 + * automatically.
723 + *
724 + * @param [key] The key to be used for symmetric encryption.
725 + * @param [cipher] The OID of the symmetric cipher to use.
726 + */
727 + encrypt: function(key, cipher) {
728 + // Part 1: Symmetric encryption
729 + if(msg.encryptedContent.content === undefined) {
730 + cipher = cipher || msg.encryptedContent.algorithm;
731 + key = key || msg.encryptedContent.key;
732 +
733 + var keyLen, ivLen, ciphFn;
734 + switch(cipher) {
735 + case forge.pki.oids['aes128-CBC']:
736 + keyLen = 16;
737 + ivLen = 16;
738 + ciphFn = forge.aes.createEncryptionCipher;
739 + break;
740 +
741 + case forge.pki.oids['aes192-CBC']:
742 + keyLen = 24;
743 + ivLen = 16;
744 + ciphFn = forge.aes.createEncryptionCipher;
745 + break;
746 +
747 + case forge.pki.oids['aes256-CBC']:
748 + keyLen = 32;
749 + ivLen = 16;
750 + ciphFn = forge.aes.createEncryptionCipher;
751 + break;
752 +
753 + case forge.pki.oids['des-EDE3-CBC']:
754 + keyLen = 24;
755 + ivLen = 8;
756 + ciphFn = forge.des.createEncryptionCipher;
757 + break;
758 +
759 + default:
760 + throw new Error('Unsupported symmetric cipher, OID ' + cipher);
761 + }
762 +
763 + if(key === undefined) {
764 + key = forge.util.createBuffer(forge.random.getBytes(keyLen));
765 + } else if(key.length() != keyLen) {
766 + throw new Error('Symmetric key has wrong length; ' +
767 + 'got ' + key.length() + ' bytes, expected ' + keyLen + '.');
768 + }
769 +
770 + // Keep a copy of the key & IV in the object, so the caller can
771 + // use it for whatever reason.
772 + msg.encryptedContent.algorithm = cipher;
773 + msg.encryptedContent.key = key;
774 + msg.encryptedContent.parameter = forge.util.createBuffer(
775 + forge.random.getBytes(ivLen));
776 +
777 + var ciph = ciphFn(key);
778 + ciph.start(msg.encryptedContent.parameter.copy());
779 + ciph.update(msg.content);
780 +
781 + // The finish function does PKCS#7 padding by default, therefore
782 + // no action required by us.
783 + if(!ciph.finish()) {
784 + throw new Error('Symmetric encryption failed.');
785 + }
786 +
787 + msg.encryptedContent.content = ciph.output;
788 + }
789 +
790 + // Part 2: asymmetric encryption for each recipient
791 + for(var i = 0; i < msg.recipients.length; ++i) {
792 + var recipient = msg.recipients[i];
793 +
794 + // Nothing to do, encryption already done.
795 + if(recipient.encryptedContent.content !== undefined) {
796 + continue;
797 + }
798 +
799 + switch(recipient.encryptedContent.algorithm) {
800 + case forge.pki.oids.rsaEncryption:
801 + recipient.encryptedContent.content =
802 + recipient.encryptedContent.key.encrypt(
803 + msg.encryptedContent.key.data);
804 + break;
805 +
806 + default:
807 + throw new Error('Unsupported asymmetric cipher, OID ' +
808 + recipient.encryptedContent.algorithm);
809 + }
810 + }
811 + }
812 + };
813 + return msg;
814 +};
815 +
816 +/**
817 + * Converts a single recipient from an ASN.1 object.
818 + *
819 + * @param obj the ASN.1 RecipientInfo.
820 + *
821 + * @return the recipient object.
822 + */
823 +function _recipientFromAsn1(obj) {
824 + // validate EnvelopedData content block and capture data
825 + var capture = {};
826 + var errors = [];
827 + if(!asn1.validate(obj, p7.asn1.recipientInfoValidator, capture, errors)) {
828 + var error = new Error('Cannot read PKCS#7 RecipientInfo. ' +
829 + 'ASN.1 object is not an PKCS#7 RecipientInfo.');
830 + error.errors = errors;
831 + throw error;
832 + }
833 +
834 + return {
835 + version: capture.version.charCodeAt(0),
836 + issuer: forge.pki.RDNAttributesAsArray(capture.issuer),
837 + serialNumber: forge.util.createBuffer(capture.serial).toHex(),
838 + encryptedContent: {
839 + algorithm: asn1.derToOid(capture.encAlgorithm),
840 + parameter: capture.encParameter ? capture.encParameter.value : undefined,
841 + content: capture.encKey
842 + }
843 + };
844 +}
845 +
846 +/**
847 + * Converts a single recipient object to an ASN.1 object.
848 + *
849 + * @param obj the recipient object.
850 + *
851 + * @return the ASN.1 RecipientInfo.
852 + */
853 +function _recipientToAsn1(obj) {
854 + return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
855 + // Version
856 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
857 + asn1.integerToDer(obj.version).getBytes()),
858 + // IssuerAndSerialNumber
859 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
860 + // Name
861 + forge.pki.distinguishedNameToAsn1({attributes: obj.issuer}),
862 + // Serial
863 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
864 + forge.util.hexToBytes(obj.serialNumber))
865 + ]),
866 + // KeyEncryptionAlgorithmIdentifier
867 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
868 + // Algorithm
869 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
870 + asn1.oidToDer(obj.encryptedContent.algorithm).getBytes()),
871 + // Parameter, force NULL, only RSA supported for now.
872 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
873 + ]),
874 + // EncryptedKey
875 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
876 + obj.encryptedContent.content)
877 + ]);
878 +}
879 +
880 +/**
881 + * Map a set of RecipientInfo ASN.1 objects to recipient objects.
882 + *
883 + * @param infos an array of ASN.1 representations RecipientInfo (i.e. SET OF).
884 + *
885 + * @return an array of recipient objects.
886 + */
887 +function _recipientsFromAsn1(infos) {
888 + var ret = [];
889 + for(var i = 0; i < infos.length; ++i) {
890 + ret.push(_recipientFromAsn1(infos[i]));
891 + }
892 + return ret;
893 +}
894 +
895 +/**
896 + * Map an array of recipient objects to ASN.1 RecipientInfo objects.
897 + *
898 + * @param recipients an array of recipientInfo objects.
899 + *
900 + * @return an array of ASN.1 RecipientInfos.
901 + */
902 +function _recipientsToAsn1(recipients) {
903 + var ret = [];
904 + for(var i = 0; i < recipients.length; ++i) {
905 + ret.push(_recipientToAsn1(recipients[i]));
906 + }
907 + return ret;
908 +}
909 +
910 +/**
911 + * Converts a single signer from an ASN.1 object.
912 + *
913 + * @param obj the ASN.1 representation of a SignerInfo.
914 + *
915 + * @return the signer object.
916 + */
917 +function _signerFromAsn1(obj) {
918 + // validate EnvelopedData content block and capture data
919 + var capture = {};
920 + var errors = [];
921 + if(!asn1.validate(obj, p7.asn1.signerInfoValidator, capture, errors)) {
922 + var error = new Error('Cannot read PKCS#7 SignerInfo. ' +
923 + 'ASN.1 object is not an PKCS#7 SignerInfo.');
924 + error.errors = errors;
925 + throw error;
926 + }
927 +
928 + var rval = {
929 + version: capture.version.charCodeAt(0),
930 + issuer: forge.pki.RDNAttributesAsArray(capture.issuer),
931 + serialNumber: forge.util.createBuffer(capture.serial).toHex(),
932 + digestAlgorithm: asn1.derToOid(capture.digestAlgorithm),
933 + signatureAlgorithm: asn1.derToOid(capture.signatureAlgorithm),
934 + signature: capture.signature,
935 + authenticatedAttributes: [],
936 + unauthenticatedAttributes: []
937 + };
938 +
939 + // TODO: convert attributes
940 + var authenticatedAttributes = capture.authenticatedAttributes || [];
941 + var unauthenticatedAttributes = capture.unauthenticatedAttributes || [];
942 +
943 + return rval;
944 +}
945 +
946 +/**
947 + * Converts a single signerInfo object to an ASN.1 object.
948 + *
949 + * @param obj the signerInfo object.
950 + *
951 + * @return the ASN.1 representation of a SignerInfo.
952 + */
953 +function _signerToAsn1(obj) {
954 + // SignerInfo
955 + var rval = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
956 + // version
957 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
958 + asn1.integerToDer(obj.version).getBytes()),
959 + // issuerAndSerialNumber
960 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
961 + // name
962 + forge.pki.distinguishedNameToAsn1({attributes: obj.issuer}),
963 + // serial
964 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.INTEGER, false,
965 + forge.util.hexToBytes(obj.serialNumber))
966 + ]),
967 + // digestAlgorithm
968 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
969 + // algorithm
970 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
971 + asn1.oidToDer(obj.digestAlgorithm).getBytes()),
972 + // parameters (null)
973 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
974 + ])
975 + ]);
976 +
977 + // authenticatedAttributes (OPTIONAL)
978 + if(obj.authenticatedAttributesAsn1) {
979 + // add ASN.1 previously generated during signing
980 + rval.value.push(obj.authenticatedAttributesAsn1);
981 + }
982 +
983 + // digestEncryptionAlgorithm
984 + rval.value.push(asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
985 + // algorithm
986 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
987 + asn1.oidToDer(obj.signatureAlgorithm).getBytes()),
988 + // parameters (null)
989 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.NULL, false, '')
990 + ]));
991 +
992 + // encryptedDigest
993 + rval.value.push(asn1.create(
994 + asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false, obj.signature));
995 +
996 + // unauthenticatedAttributes (OPTIONAL)
997 + if(obj.unauthenticatedAttributes.length > 0) {
998 + // [1] IMPLICIT
999 + var attrsAsn1 = asn1.create(asn1.Class.CONTEXT_SPECIFIC, 1, true, []);
1000 + for(var i = 0; i < obj.unauthenticatedAttributes.length; ++i) {
1001 + var attr = obj.unauthenticatedAttributes[i];
1002 + attrsAsn1.values.push(_attributeToAsn1(attr));
1003 + }
1004 + rval.value.push(attrsAsn1);
1005 + }
1006 +
1007 + return rval;
1008 +}
1009 +
1010 +/**
1011 + * Map a set of SignerInfo ASN.1 objects to an array of signer objects.
1012 + *
1013 + * @param signerInfoAsn1s an array of ASN.1 SignerInfos (i.e. SET OF).
1014 + *
1015 + * @return an array of signers objects.
1016 + */
1017 +function _signersFromAsn1(signerInfoAsn1s) {
1018 + var ret = [];
1019 + for(var i = 0; i < signerInfoAsn1s.length; ++i) {
1020 + ret.push(_signerFromAsn1(signerInfoAsn1s[i]));
1021 + }
1022 + return ret;
1023 +}
1024 +
1025 +/**
1026 + * Map an array of signer objects to ASN.1 objects.
1027 + *
1028 + * @param signers an array of signer objects.
1029 + *
1030 + * @return an array of ASN.1 SignerInfos.
1031 + */
1032 +function _signersToAsn1(signers) {
1033 + var ret = [];
1034 + for(var i = 0; i < signers.length; ++i) {
1035 + ret.push(_signerToAsn1(signers[i]));
1036 + }
1037 + return ret;
1038 +}
1039 +
1040 +/**
1041 + * Convert an attribute object to an ASN.1 Attribute.
1042 + *
1043 + * @param attr the attribute object.
1044 + *
1045 + * @return the ASN.1 Attribute.
1046 + */
1047 +function _attributeToAsn1(attr) {
1048 + var value;
1049 +
1050 + // TODO: generalize to support more attributes
1051 + if(attr.type === forge.pki.oids.contentType) {
1052 + value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
1053 + asn1.oidToDer(attr.value).getBytes());
1054 + } else if(attr.type === forge.pki.oids.messageDigest) {
1055 + value = asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
1056 + attr.value.bytes());
1057 + } else if(attr.type === forge.pki.oids.signingTime) {
1058 + /* Note per RFC 2985: Dates between 1 January 1950 and 31 December 2049
1059 + (inclusive) MUST be encoded as UTCTime. Any dates with year values
1060 + before 1950 or after 2049 MUST be encoded as GeneralizedTime. [Further,]
1061 + UTCTime values MUST be expressed in Greenwich Mean Time (Zulu) and MUST
1062 + include seconds (i.e., times are YYMMDDHHMMSSZ), even where the
1063 + number of seconds is zero. Midnight (GMT) must be represented as
1064 + "YYMMDD000000Z". */
1065 + // TODO: make these module-level constants
1066 + var jan_1_1950 = new Date('1950-01-01T00:00:00Z');
1067 + var jan_1_2050 = new Date('2050-01-01T00:00:00Z');
1068 + var date = attr.value;
1069 + if(typeof date === 'string') {
1070 + // try to parse date
1071 + var timestamp = Date.parse(date);
1072 + if(!isNaN(timestamp)) {
1073 + date = new Date(timestamp);
1074 + } else if(date.length === 13) {
1075 + // YYMMDDHHMMSSZ (13 chars for UTCTime)
1076 + date = asn1.utcTimeToDate(date);
1077 + } else {
1078 + // assume generalized time
1079 + date = asn1.generalizedTimeToDate(date);
1080 + }
1081 + }
1082 +
1083 + if(date >= jan_1_1950 && date < jan_1_2050) {
1084 + value = asn1.create(
1085 + asn1.Class.UNIVERSAL, asn1.Type.UTCTIME, false,
1086 + asn1.dateToUtcTime(date));
1087 + } else {
1088 + value = asn1.create(
1089 + asn1.Class.UNIVERSAL, asn1.Type.GENERALIZEDTIME, false,
1090 + asn1.dateToGeneralizedTime(date));
1091 + }
1092 + }
1093 +
1094 + // Added this line to support custom attributes
1095 + if ((value == null) && (attr != null) && (attr.value != null)) { value = attr.value; }
1096 +
1097 + // TODO: expose as common API call
1098 + // create a RelativeDistinguishedName set
1099 + // each value in the set is an AttributeTypeAndValue first
1100 + // containing the type (an OID) and second the value
1101 + return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
1102 + // AttributeType
1103 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
1104 + asn1.oidToDer(attr.type).getBytes()),
1105 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
1106 + // AttributeValue
1107 + value
1108 + ])
1109 + ]);
1110 +}
1111 +
1112 +/**
1113 + * Map messages encrypted content to ASN.1 objects.
1114 + *
1115 + * @param ec The encryptedContent object of the message.
1116 + *
1117 + * @return ASN.1 representation of the encryptedContent object (SEQUENCE).
1118 + */
1119 +function _encryptedContentToAsn1(ec) {
1120 + return [
1121 + // ContentType, always Data for the moment
1122 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
1123 + asn1.oidToDer(forge.pki.oids.data).getBytes()),
1124 + // ContentEncryptionAlgorithmIdentifier
1125 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
1126 + // Algorithm
1127 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
1128 + asn1.oidToDer(ec.algorithm).getBytes()),
1129 + // Parameters (IV)
1130 + !ec.parameter ?
1131 + undefined :
1132 + asn1.create(
1133 + asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
1134 + ec.parameter.getBytes())
1135 + ]),
1136 + // [0] EncryptedContent
1137 + asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
1138 + asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
1139 + ec.content.getBytes())
1140 + ])
1141 + ];
1142 +}
1143 +
1144 +/**
1145 + * Reads the "common part" of an PKCS#7 content block (in ASN.1 format)
1146 + *
1147 + * This function reads the "common part" of the PKCS#7 content blocks
1148 + * EncryptedData and EnvelopedData, i.e. version number and symmetrically
1149 + * encrypted content block.
1150 + *
1151 + * The result of the ASN.1 validate and capture process is returned
1152 + * to allow the caller to extract further data, e.g. the list of recipients
1153 + * in case of a EnvelopedData object.
1154 + *
1155 + * @param msg the PKCS#7 object to read the data to.
1156 + * @param obj the ASN.1 representation of the content block.
1157 + * @param validator the ASN.1 structure validator object to use.
1158 + *
1159 + * @return the value map captured by validator object.
1160 + */
1161 +function _fromAsn1(msg, obj, validator) {
1162 + var capture = {};
1163 + var errors = [];
1164 + if(!asn1.validate(obj, validator, capture, errors)) {
1165 + var error = new Error('Cannot read PKCS#7 message. ' +
1166 + 'ASN.1 object is not a supported PKCS#7 message.');
1167 + error.errors = error;
1168 + throw error;
1169 + }
1170 +
1171 + // Check contentType, so far we only support (raw) Data.
1172 + var contentType = asn1.derToOid(capture.contentType);
1173 + if(contentType !== forge.pki.oids.data) {
1174 + throw new Error('Unsupported PKCS#7 message. ' +
1175 + 'Only wrapped ContentType Data supported.');
1176 + }
1177 +
1178 + if(capture.encryptedContent) {
1179 + var content = '';
1180 + if(forge.util.isArray(capture.encryptedContent)) {
1181 + for(var i = 0; i < capture.encryptedContent.length; ++i) {
1182 + if(capture.encryptedContent[i].type !== asn1.Type.OCTETSTRING) {
1183 + throw new Error('Malformed PKCS#7 message, expecting encrypted ' +
1184 + 'content constructed of only OCTET STRING objects.');
1185 + }
1186 + content += capture.encryptedContent[i].value;
1187 + }
1188 + } else {
1189 + content = capture.encryptedContent;
1190 + }
1191 + msg.encryptedContent = {
1192 + algorithm: asn1.derToOid(capture.encAlgorithm),
1193 + parameter: forge.util.createBuffer(capture.encParameter.value),
1194 + content: forge.util.createBuffer(content)
1195 + };
1196 + }
1197 +
1198 + if(capture.content) {
1199 + var content = '';
1200 + if(forge.util.isArray(capture.content)) {
1201 + for(var i = 0; i < capture.content.length; ++i) {
1202 + if(capture.content[i].type !== asn1.Type.OCTETSTRING) {
1203 + throw new Error('Malformed PKCS#7 message, expecting ' +
1204 + 'content constructed of only OCTET STRING objects.');
1205 + }
1206 + content += capture.content[i].value;
1207 + }
1208 + } else {
1209 + content = capture.content;
1210 + }
1211 + msg.content = forge.util.createBuffer(content);
1212 + }
1213 +
1214 + msg.version = capture.version.charCodeAt(0);
1215 + msg.rawCapture = capture;
1216 +
1217 + return capture;
1218 +}
1219 +
1220 +/**
1221 + * Decrypt the symmetrically encrypted content block of the PKCS#7 message.
1222 + *
1223 + * Decryption is skipped in case the PKCS#7 message object already has a
1224 + * (decrypted) content attribute. The algorithm, key and cipher parameters
1225 + * (probably the iv) are taken from the encryptedContent attribute of the
1226 + * message object.
1227 + *
1228 + * @param The PKCS#7 message object.
1229 + */
1230 +function _decryptContent(msg) {
1231 + if(msg.encryptedContent.key === undefined) {
1232 + throw new Error('Symmetric key not available.');
1233 + }
1234 +
1235 + if(msg.content === undefined) {
1236 + var ciph;
1237 +
1238 + switch(msg.encryptedContent.algorithm) {
1239 + case forge.pki.oids['aes128-CBC']:
1240 + case forge.pki.oids['aes192-CBC']:
1241 + case forge.pki.oids['aes256-CBC']:
1242 + ciph = forge.aes.createDecryptionCipher(msg.encryptedContent.key);
1243 + break;
1244 +
1245 + case forge.pki.oids['desCBC']:
1246 + case forge.pki.oids['des-EDE3-CBC']:
1247 + ciph = forge.des.createDecryptionCipher(msg.encryptedContent.key);
1248 + break;
1249 +
1250 + default:
1251 + throw new Error('Unsupported symmetric cipher, OID ' +
1252 + msg.encryptedContent.algorithm);
1253 + }
1254 + ciph.start(msg.encryptedContent.parameter);
1255 + ciph.update(msg.encryptedContent.content);
1256 +
1257 + if(!ciph.finish()) {
1258 + throw new Error('Symmetric decryption failed.');
1259 + }
1260 +
1261 + msg.content = ciph.output;
1262 + }
1263 +}