[6a3a178] | 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 | }
|
---|