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('./forge');
|
---|
20 | require('./aes');
|
---|
21 | require('./asn1');
|
---|
22 | require('./des');
|
---|
23 | require('./oids');
|
---|
24 | require('./pem');
|
---|
25 | require('./pkcs7asn1');
|
---|
26 | require('./random');
|
---|
27 | require('./util');
|
---|
28 | require('./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.value,
|
---|
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 | // TODO: expose as common API call
|
---|
1095 | // create a RelativeDistinguishedName set
|
---|
1096 | // each value in the set is an AttributeTypeAndValue first
|
---|
1097 | // containing the type (an OID) and second the value
|
---|
1098 | return asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
---|
1099 | // AttributeType
|
---|
1100 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
---|
1101 | asn1.oidToDer(attr.type).getBytes()),
|
---|
1102 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SET, true, [
|
---|
1103 | // AttributeValue
|
---|
1104 | value
|
---|
1105 | ])
|
---|
1106 | ]);
|
---|
1107 | }
|
---|
1108 |
|
---|
1109 | /**
|
---|
1110 | * Map messages encrypted content to ASN.1 objects.
|
---|
1111 | *
|
---|
1112 | * @param ec The encryptedContent object of the message.
|
---|
1113 | *
|
---|
1114 | * @return ASN.1 representation of the encryptedContent object (SEQUENCE).
|
---|
1115 | */
|
---|
1116 | function _encryptedContentToAsn1(ec) {
|
---|
1117 | return [
|
---|
1118 | // ContentType, always Data for the moment
|
---|
1119 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
---|
1120 | asn1.oidToDer(forge.pki.oids.data).getBytes()),
|
---|
1121 | // ContentEncryptionAlgorithmIdentifier
|
---|
1122 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.SEQUENCE, true, [
|
---|
1123 | // Algorithm
|
---|
1124 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OID, false,
|
---|
1125 | asn1.oidToDer(ec.algorithm).getBytes()),
|
---|
1126 | // Parameters (IV)
|
---|
1127 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
|
---|
1128 | ec.parameter.getBytes())
|
---|
1129 | ]),
|
---|
1130 | // [0] EncryptedContent
|
---|
1131 | asn1.create(asn1.Class.CONTEXT_SPECIFIC, 0, true, [
|
---|
1132 | asn1.create(asn1.Class.UNIVERSAL, asn1.Type.OCTETSTRING, false,
|
---|
1133 | ec.content.getBytes())
|
---|
1134 | ])
|
---|
1135 | ];
|
---|
1136 | }
|
---|
1137 |
|
---|
1138 | /**
|
---|
1139 | * Reads the "common part" of an PKCS#7 content block (in ASN.1 format)
|
---|
1140 | *
|
---|
1141 | * This function reads the "common part" of the PKCS#7 content blocks
|
---|
1142 | * EncryptedData and EnvelopedData, i.e. version number and symmetrically
|
---|
1143 | * encrypted content block.
|
---|
1144 | *
|
---|
1145 | * The result of the ASN.1 validate and capture process is returned
|
---|
1146 | * to allow the caller to extract further data, e.g. the list of recipients
|
---|
1147 | * in case of a EnvelopedData object.
|
---|
1148 | *
|
---|
1149 | * @param msg the PKCS#7 object to read the data to.
|
---|
1150 | * @param obj the ASN.1 representation of the content block.
|
---|
1151 | * @param validator the ASN.1 structure validator object to use.
|
---|
1152 | *
|
---|
1153 | * @return the value map captured by validator object.
|
---|
1154 | */
|
---|
1155 | function _fromAsn1(msg, obj, validator) {
|
---|
1156 | var capture = {};
|
---|
1157 | var errors = [];
|
---|
1158 | if(!asn1.validate(obj, validator, capture, errors)) {
|
---|
1159 | var error = new Error('Cannot read PKCS#7 message. ' +
|
---|
1160 | 'ASN.1 object is not a supported PKCS#7 message.');
|
---|
1161 | error.errors = error;
|
---|
1162 | throw error;
|
---|
1163 | }
|
---|
1164 |
|
---|
1165 | // Check contentType, so far we only support (raw) Data.
|
---|
1166 | var contentType = asn1.derToOid(capture.contentType);
|
---|
1167 | if(contentType !== forge.pki.oids.data) {
|
---|
1168 | throw new Error('Unsupported PKCS#7 message. ' +
|
---|
1169 | 'Only wrapped ContentType Data supported.');
|
---|
1170 | }
|
---|
1171 |
|
---|
1172 | if(capture.encryptedContent) {
|
---|
1173 | var content = '';
|
---|
1174 | if(forge.util.isArray(capture.encryptedContent)) {
|
---|
1175 | for(var i = 0; i < capture.encryptedContent.length; ++i) {
|
---|
1176 | if(capture.encryptedContent[i].type !== asn1.Type.OCTETSTRING) {
|
---|
1177 | throw new Error('Malformed PKCS#7 message, expecting encrypted ' +
|
---|
1178 | 'content constructed of only OCTET STRING objects.');
|
---|
1179 | }
|
---|
1180 | content += capture.encryptedContent[i].value;
|
---|
1181 | }
|
---|
1182 | } else {
|
---|
1183 | content = capture.encryptedContent;
|
---|
1184 | }
|
---|
1185 | msg.encryptedContent = {
|
---|
1186 | algorithm: asn1.derToOid(capture.encAlgorithm),
|
---|
1187 | parameter: forge.util.createBuffer(capture.encParameter.value),
|
---|
1188 | content: forge.util.createBuffer(content)
|
---|
1189 | };
|
---|
1190 | }
|
---|
1191 |
|
---|
1192 | if(capture.content) {
|
---|
1193 | var content = '';
|
---|
1194 | if(forge.util.isArray(capture.content)) {
|
---|
1195 | for(var i = 0; i < capture.content.length; ++i) {
|
---|
1196 | if(capture.content[i].type !== asn1.Type.OCTETSTRING) {
|
---|
1197 | throw new Error('Malformed PKCS#7 message, expecting ' +
|
---|
1198 | 'content constructed of only OCTET STRING objects.');
|
---|
1199 | }
|
---|
1200 | content += capture.content[i].value;
|
---|
1201 | }
|
---|
1202 | } else {
|
---|
1203 | content = capture.content;
|
---|
1204 | }
|
---|
1205 | msg.content = forge.util.createBuffer(content);
|
---|
1206 | }
|
---|
1207 |
|
---|
1208 | msg.version = capture.version.charCodeAt(0);
|
---|
1209 | msg.rawCapture = capture;
|
---|
1210 |
|
---|
1211 | return capture;
|
---|
1212 | }
|
---|
1213 |
|
---|
1214 | /**
|
---|
1215 | * Decrypt the symmetrically encrypted content block of the PKCS#7 message.
|
---|
1216 | *
|
---|
1217 | * Decryption is skipped in case the PKCS#7 message object already has a
|
---|
1218 | * (decrypted) content attribute. The algorithm, key and cipher parameters
|
---|
1219 | * (probably the iv) are taken from the encryptedContent attribute of the
|
---|
1220 | * message object.
|
---|
1221 | *
|
---|
1222 | * @param The PKCS#7 message object.
|
---|
1223 | */
|
---|
1224 | function _decryptContent(msg) {
|
---|
1225 | if(msg.encryptedContent.key === undefined) {
|
---|
1226 | throw new Error('Symmetric key not available.');
|
---|
1227 | }
|
---|
1228 |
|
---|
1229 | if(msg.content === undefined) {
|
---|
1230 | var ciph;
|
---|
1231 |
|
---|
1232 | switch(msg.encryptedContent.algorithm) {
|
---|
1233 | case forge.pki.oids['aes128-CBC']:
|
---|
1234 | case forge.pki.oids['aes192-CBC']:
|
---|
1235 | case forge.pki.oids['aes256-CBC']:
|
---|
1236 | ciph = forge.aes.createDecryptionCipher(msg.encryptedContent.key);
|
---|
1237 | break;
|
---|
1238 |
|
---|
1239 | case forge.pki.oids['desCBC']:
|
---|
1240 | case forge.pki.oids['des-EDE3-CBC']:
|
---|
1241 | ciph = forge.des.createDecryptionCipher(msg.encryptedContent.key);
|
---|
1242 | break;
|
---|
1243 |
|
---|
1244 | default:
|
---|
1245 | throw new Error('Unsupported symmetric cipher, OID ' +
|
---|
1246 | msg.encryptedContent.algorithm);
|
---|
1247 | }
|
---|
1248 | ciph.start(msg.encryptedContent.parameter);
|
---|
1249 | ciph.update(msg.encryptedContent.content);
|
---|
1250 |
|
---|
1251 | if(!ciph.finish()) {
|
---|
1252 | throw new Error('Symmetric decryption failed.');
|
---|
1253 | }
|
---|
1254 |
|
---|
1255 | msg.content = ciph.output;
|
---|
1256 | }
|
---|
1257 | }
|
---|