Revision control

Copy as Markdown

Other Tools

/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, you can obtain one at https://mozilla.org/MPL/2.0/. */
/**
* Tests that multipart/signed parts are dispatched to the OpenPGP or to the
* S/MIME implementation based on the protocol parameter of the part, and that
* this works in a single MIME stream, no matter which of the two was
* displayed before.
*/
const { OpenPGPTestUtils } = ChromeUtils.importESModule(
);
const { EnigmailConstants } = ChromeUtils.importESModule(
);
const { EnigmailDecryption } = ChromeUtils.importESModule(
);
const { EnigmailSingletons } = ChromeUtils.importESModule(
);
const { MessageInjection } = ChromeUtils.importESModule(
);
const { SmimeUtils } = ChromeUtils.importESModule(
);
const smimeDataDir = "../../../data/smime/";
const openpgpDataDir = "../../../../mail/test/browser/openpgp/data/";
const gTextAliceBob = "This is a test message from Alice to Bob.";
const gTextBobAlice = "Sundays are nothing without callaloo.";
/**
* @implements {nsIMsgSMIMESink}
*/
const smimeSink = {
expectResults(expectedEvents) {
this._expectedEvents = expectedEvents;
this._deferred = Promise.withResolvers();
this.countReceived = 0;
this.signed = null;
this.encrypted = null;
return this._deferred.promise;
},
ignoreStatusFrom() {},
signatureProcessingStarted() {},
signedStatus(nestingLevel, signatureStatus) {
this.signed = { nestingLevel, signatureStatus };
this._received();
},
resetSignedStatus() {
this.signed = null;
this._received();
},
encryptionStatus(nestingLevel, encryptionStatus) {
this.encrypted = { nestingLevel, encryptionStatus };
this._received();
},
_received() {
if (++this.countReceived == this._expectedEvents) {
this._deferred.resolve();
}
},
};
/**
* @implements {nsIMsgOpenPGPSink}
*/
const openpgpSink = {
expectResults(expectedEvents) {
this._expectedEvents = expectedEvents;
this._deferred = Promise.withResolvers();
this.countReceived = 0;
this.statusFlags = 0;
EnigmailSingletons.messageReader = this;
return this._deferred.promise;
},
isCurrentMessage() {
return true;
},
isMultipartRelated() {
return false;
},
displaySubPart() {
return true;
},
hasUnauthenticatedParts() {
return false;
},
ignoreStatusFrom() {},
modifyMessageHeaders() {},
updateSecurityStatus(exitCode, statusFlags) {
this.statusFlags |= statusFlags;
if (++this.countReceived == this._expectedEvents) {
this._deferred.resolve();
}
},
};
/**
* @name Test
* @property {string} description - What the message exercises.
* @property {string} dir - Directory the eml file is found in.
* @property {string} filename - Name of the eml file.
* @property {string} text - Text the rendered body must contain.
* @property {integer} smimeEvents - Number of expected S/MIME sink calls.
* @property {integer} openpgpEvents - Number of expected OpenPGP sink calls.
* @property {boolean} [smimeSigned] - Expect a good S/MIME signature.
* @property {boolean} [smimeEncrypted] - Expect S/MIME encryption.
* @property {string[]} [openpgpFlags] - EnigmailConstants flag names that must
* be set in the reported OpenPGP status.
*/
/** @type {Test[]} */
const tests = [
{
description: "S/MIME clear-signed, top level",
dir: smimeDataDir,
filename: "alice.dsig.SHA256.multipart.eml",
text: gTextAliceBob,
smimeEvents: 1,
openpgpEvents: 0,
smimeSigned: true,
},
{
description: "OpenPGP signed, top level",
dir: `${openpgpDataDir}eml/`,
filename:
"signed-by-0xfbfcc82a015e7330-to-0xf231550c4f47e38e-unencrypted.eml",
text: gTextBobAlice,
smimeEvents: 0,
openpgpEvents: 1,
openpgpFlags: ["GOOD_SIGNATURE"],
},
{
description: "S/MIME clear-signed inside enveloped",
dir: smimeDataDir,
filename: "alice.dsig.SHA256.multipart.env.eml",
text: gTextAliceBob,
smimeEvents: 2,
openpgpEvents: 0,
smimeSigned: true,
smimeEncrypted: true,
},
{
description: "OpenPGP encrypted with inner signature",
dir: `${openpgpDataDir}eml/`,
filename:
"signed-by-0xfbfcc82a015e7330-encrypted-to-0xf231550c4f47e38e.eml",
text: gTextBobAlice,
smimeEvents: 0,
openpgpEvents: 2,
openpgpFlags: ["GOOD_SIGNATURE", "DECRYPTION_OKAY"],
},
];
const gInbox = new MessageInjection({ mode: "local" }).getInboxFolder();
add_setup(async function () {
SmimeUtils.ensureNSS();
SmimeUtils.loadPEMCertificate(
do_get_file(`${smimeDataDir}TestCA.pem`),
Ci.nsIX509Cert.CA_CERT
);
SmimeUtils.loadCertificateAndKey(
do_get_file(`${smimeDataDir}Alice.p12`),
"nss"
);
SmimeUtils.loadCertificateAndKey(
do_get_file(`${smimeDataDir}Bob.p12`),
"nss"
);
await OpenPGPTestUtils.initOpenPGP();
await OpenPGPTestUtils.importPrivateKey(
null,
do_get_file(
`${openpgpDataDir}keys/alice@openpgp.example-0xf231550c4f47e38e-secret.asc`
)
);
await OpenPGPTestUtils.importPublicKey(
null,
do_get_file(
`${openpgpDataDir}keys/bob@openpgp.example-0xfbfcc82a015e7330-pub.asc`
)
);
for (const test of tests) {
const promiseCopyListener = new PromiseTestUtils.PromiseCopyListener();
MailServices.copy.copyFileMessage(
do_get_file(`${test.dir}${test.filename}`),
gInbox,
null,
true,
0,
"",
promiseCopyListener,
null
);
await promiseCopyListener.promise;
}
});
/**
* The OpenPGP handler used to be registered for multipart/signed at runtime,
* and unregistered again to let S/MIME work. Nothing may register a handler
* for multipart/signed any more, the protocol parameter decides.
*/
add_task(function testNoMultipartSignedHandlerRegistered() {
Assert.ok(
!Components.manager.isContractIDRegistered(
"@mozilla.org/mimecth;1?type=multipart/signed"
),
"no content type handler is registered for multipart/signed"
);
Assert.ok(
Components.manager.isContractIDRegistered(
"@mozilla.org/mimecth;1?type=multipart/encrypted"
),
"the OpenPGP content type handler for multipart/encrypted is registered"
);
});
/**
* Display each message, in both orders, and check that the body renders and
* the correct sink is told about the crypto status. Each message is streamed
* exactly once, so a message that needed a reload to be displayed would fail
* here.
*/
add_task(async function testDispatchByProtocol() {
for (const order of ["forward", "backward"]) {
const indexes = tests.map((test, index) => index);
if (order == "backward") {
indexes.reverse();
}
for (const index of indexes) {
const test = tests[index];
info(`Running test: ${test.description} (${order})`);
const hdr = mailTestUtils.getMsgHdrN(gInbox, index);
const uri = hdr.folder.getUriForMsg(hdr);
const smimePromise = smimeSink.expectResults(test.smimeEvents);
const openpgpPromise = openpgpSink.expectResults(test.openpgpEvents);
// Stub this function so verifyDetached() can get the correct email.
EnigmailDecryption.getFromAddr = () => "bob@openpgp.example";
const conversion = apply_mime_conversion(uri, smimeSink, openpgpSink);
const body = await conversion.promise;
Assert.ok(
body.includes(test.text),
`${test.filename}: body of the signed part is rendered`
);
if (test.smimeEvents) {
await smimePromise;
}
if (test.openpgpEvents) {
await openpgpPromise;
}
Assert.equal(
smimeSink.countReceived,
test.smimeEvents,
`${test.filename}: number of S/MIME status reports`
);
Assert.equal(
openpgpSink.countReceived,
test.openpgpEvents,
`${test.filename}: number of OpenPGP status reports`
);
if (test.smimeSigned) {
Assert.equal(
smimeSink.signed?.signatureStatus,
0,
`${test.filename}: S/MIME signature is good`
);
}
if (test.smimeEncrypted) {
Assert.equal(
smimeSink.encrypted?.encryptionStatus,
0,
`${test.filename}: S/MIME decryption succeeded`
);
}
for (const flag of test.openpgpFlags ?? []) {
Assert.ok(
openpgpSink.statusFlags & EnigmailConstants[flag],
`${test.filename}: OpenPGP status flag "${flag}" is set`
);
}
}
}
});