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
+}