summaryrefslogtreecommitdiffstats
path: root/comm/mailnews/extensions/smime
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /comm/mailnews/extensions/smime
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'comm/mailnews/extensions/smime')
-rw-r--r--comm/mailnews/extensions/smime/certFetchingStatus.js255
-rw-r--r--comm/mailnews/extensions/smime/certFetchingStatus.xhtml40
-rw-r--r--comm/mailnews/extensions/smime/certpicker.js69
-rw-r--r--comm/mailnews/extensions/smime/certpicker.xhtml54
-rw-r--r--comm/mailnews/extensions/smime/components.conf63
-rw-r--r--comm/mailnews/extensions/smime/moz.build37
-rw-r--r--comm/mailnews/extensions/smime/msgCompSecurityInfo.js122
-rw-r--r--comm/mailnews/extensions/smime/msgCompSecurityInfo.xhtml69
-rw-r--r--comm/mailnews/extensions/smime/msgReadSMIMEOverlay.js251
-rw-r--r--comm/mailnews/extensions/smime/nsCMS.cpp1187
-rw-r--r--comm/mailnews/extensions/smime/nsCMS.h123
-rw-r--r--comm/mailnews/extensions/smime/nsCMSSecureMessage.cpp92
-rw-r--r--comm/mailnews/extensions/smime/nsCMSSecureMessage.h39
-rw-r--r--comm/mailnews/extensions/smime/nsCertPicker.cpp410
-rw-r--r--comm/mailnews/extensions/smime/nsCertPicker.h32
-rw-r--r--comm/mailnews/extensions/smime/nsEncryptedSMIMEURIsService.cpp32
-rw-r--r--comm/mailnews/extensions/smime/nsEncryptedSMIMEURIsService.h24
-rw-r--r--comm/mailnews/extensions/smime/nsICMSDecoder.idl29
-rw-r--r--comm/mailnews/extensions/smime/nsICMSDecoderJS.idl24
-rw-r--r--comm/mailnews/extensions/smime/nsICMSEncoder.idl29
-rw-r--r--comm/mailnews/extensions/smime/nsICMSMessage.idl96
-rw-r--r--comm/mailnews/extensions/smime/nsICMSMessageErrors.idl36
-rw-r--r--comm/mailnews/extensions/smime/nsICMSSecureMessage.idl30
-rw-r--r--comm/mailnews/extensions/smime/nsICertPickDialogs.idl27
-rw-r--r--comm/mailnews/extensions/smime/nsIEncryptedSMIMEURIsSrvc.idl24
-rw-r--r--comm/mailnews/extensions/smime/nsIMsgSMIMEHeaderSink.idl30
-rw-r--r--comm/mailnews/extensions/smime/nsIUserCertPicker.idl28
-rw-r--r--comm/mailnews/extensions/smime/nsMsgComposeSecure.cpp1277
-rw-r--r--comm/mailnews/extensions/smime/nsMsgComposeSecure.h103
29 files changed, 4632 insertions, 0 deletions
diff --git a/comm/mailnews/extensions/smime/certFetchingStatus.js b/comm/mailnews/extensions/smime/certFetchingStatus.js
new file mode 100644
index 0000000000..ea7cd63226
--- /dev/null
+++ b/comm/mailnews/extensions/smime/certFetchingStatus.js
@@ -0,0 +1,255 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+let USER_CERT_ATTRIBUTE = "usercertificate;binary";
+
+let gEmailAddresses;
+let gDirectoryPref;
+let gLdapServerURL;
+let gLdapConnection;
+let gCertDB;
+let gLdapOperation;
+let gLogin;
+
+window.addEventListener("DOMContentLoaded", onLoad);
+document.addEventListener("dialogcancel", stopFetching);
+
+/**
+ * Expects the following arguments:
+ * - pref name of LDAP directory to fetch from
+ * - array with email addresses
+ *
+ * Display modal dialog with message and stop button.
+ * In onload, kick off binding to LDAP.
+ * When bound, kick off the searches.
+ * On finding certificates, import into permanent cert database.
+ * When all searches are finished, close the dialog.
+ */
+function onLoad() {
+ gDirectoryPref = window.arguments[0];
+ gEmailAddresses = window.arguments[1];
+
+ if (!gEmailAddresses.length) {
+ window.close();
+ return;
+ }
+
+ setTimeout(search);
+}
+
+function search() {
+ // Get the login to authenticate as, if there is one. No big deal if we don't
+ // have one.
+ gLogin = Services.prefs.getStringPref(gDirectoryPref + ".auth.dn", undefined);
+
+ try {
+ let url = Services.prefs.getCharPref(gDirectoryPref + ".uri");
+
+ gLdapServerURL = Services.io.newURI(url).QueryInterface(Ci.nsILDAPURL);
+
+ gLdapConnection = Cc["@mozilla.org/network/ldap-connection;1"]
+ .createInstance()
+ .QueryInterface(Ci.nsILDAPConnection);
+
+ gLdapConnection.init(
+ gLdapServerURL,
+ gLogin,
+ new BindListener(),
+ null,
+ Ci.nsILDAPConnection.VERSION3
+ );
+ } catch (ex) {
+ console.error(ex);
+ window.close();
+ }
+}
+
+function stopFetching() {
+ if (gLdapOperation) {
+ try {
+ gLdapOperation.abandon();
+ } catch (e) {}
+ }
+}
+
+function importCert(ber_value) {
+ if (!gCertDB) {
+ gCertDB = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+ }
+
+ // ber_value has type nsILDAPBERValue
+ let cert_bytes = ber_value.get();
+ if (cert_bytes) {
+ gCertDB.importEmailCertificate(cert_bytes, cert_bytes.length, null);
+ }
+}
+
+function getLDAPOperation() {
+ gLdapOperation = Cc["@mozilla.org/network/ldap-operation;1"].createInstance(
+ Ci.nsILDAPOperation
+ );
+
+ gLdapOperation.init(gLdapConnection, new LDAPMessageListener(), null);
+}
+
+async function getPassword() {
+ // we only need a password if we are using credentials
+ if (!gLogin) {
+ return null;
+ }
+ let authPrompter = Services.ww.getNewAuthPrompter(window);
+ let strBundle = document.getElementById("bundle_ldap");
+ let password = { value: "" };
+
+ // nsLDAPAutocompleteSession uses asciiHost instead of host for the prompt
+ // text, I think we should be consistent.
+ if (
+ await authPrompter.asyncPromptPassword(
+ strBundle.getString("authPromptTitle"),
+ strBundle.getFormattedString("authPromptText", [
+ gLdapServerURL.asciiHost,
+ ]),
+ gLdapServerURL.spec,
+ authPrompter.SAVE_PASSWORD_PERMANENTLY,
+ password
+ )
+ ) {
+ return password.value;
+ }
+ return null;
+}
+
+/**
+ * Checks if the LDAP connection can be bound.
+ * @implements {nsILDAPMessageListener}
+ */
+class BindListener {
+ QueryInterface = ChromeUtils.generateQI(["nsILDAPMessageListener"]);
+
+ async onLDAPInit(conn, status) {
+ // Kick off bind.
+ getLDAPOperation();
+ gLdapOperation.simpleBind(await getPassword());
+ }
+
+ onLDAPMessage(message) {}
+
+ onLDAPError(status, secInfo, location) {
+ if (secInfo) {
+ console.warn(`LDAP bind connection security error for ${location}`);
+ } else {
+ console.warn(`LDAP bind error: ${status}`);
+ }
+ window.close();
+ }
+}
+
+/**
+ * LDAPMessageListener.
+ * @implements {nsILDAPMessageListener}
+ */
+class LDAPMessageListener {
+ QueryInterface = ChromeUtils.generateQI(["nsILDAPMessageListener"]);
+
+ onLDAPInit(conn, status) {}
+
+ onLDAPMessage(message) {
+ if (Ci.nsILDAPMessage.RES_SEARCH_RESULT == message.type) {
+ window.close();
+ return;
+ }
+
+ if (Ci.nsILDAPMessage.RES_BIND == message.type) {
+ if (Ci.nsILDAPErrors.SUCCESS != message.errorCode) {
+ window.close();
+ return;
+ }
+ // Kick off search.
+ let prefix1 = "";
+ let suffix1 = "";
+
+ let urlFilter = gLdapServerURL.filter;
+ if (
+ urlFilter != null &&
+ urlFilter.length > 0 &&
+ urlFilter != "(objectclass=*)"
+ ) {
+ if (urlFilter.startsWith("(")) {
+ prefix1 = "(&" + urlFilter;
+ } else {
+ prefix1 = "(&(" + urlFilter + ")";
+ }
+ suffix1 = ")";
+ }
+
+ let prefix2 = "";
+ let suffix2 = "";
+
+ if (gEmailAddresses.length > 1) {
+ prefix2 = "(|";
+ suffix2 = ")";
+ }
+
+ let mailFilter = "";
+
+ for (let email of gEmailAddresses) {
+ mailFilter += "(mail=" + email + ")";
+ }
+
+ let filter = prefix1 + prefix2 + mailFilter + suffix2 + suffix1;
+
+ // Max search results =>
+ // Double number of email addresses, because each person might have
+ // multiple certificates listed. We expect at most two certificates,
+ // one for signing, one for encrypting.
+ // Maybe that number should be larger, to allow for deployments,
+ // where even more certs can be stored per user???
+
+ let maxEntriesWanted = gEmailAddresses.length * 2;
+
+ getLDAPOperation();
+ gLdapOperation.searchExt(
+ gLdapServerURL.dn,
+ gLdapServerURL.scope,
+ filter,
+ USER_CERT_ATTRIBUTE,
+ 0,
+ maxEntriesWanted
+ );
+ return;
+ }
+
+ if (Ci.nsILDAPMessage.RES_SEARCH_ENTRY == message.type) {
+ let outBinValues = null;
+ try {
+ // This call may throw if the result message is empty or doesn't
+ // contain this attribute.
+ // It's an allowed condition that the attribute is missing on
+ // the server, so we silently ignore a failure to obtain it.
+ outBinValues = message.getBinaryValues(USER_CERT_ATTRIBUTE);
+ } catch (ex) {}
+ if (outBinValues) {
+ for (let i = 0; i < outBinValues.length; ++i) {
+ importCert(outBinValues[i]);
+ }
+ }
+ }
+ }
+
+ /**
+ * @param {nsresult} status
+ * @param {?nsITransportSecurityInfo} secInfo
+ * @param {?string} location
+ */
+ onLDAPError(status, secInfo, location) {
+ if (secInfo) {
+ console.warn(`LDAP connection security error for ${location}`);
+ } else {
+ console.warn(`LDAP error: ${status}`);
+ }
+ window.close();
+ }
+}
diff --git a/comm/mailnews/extensions/smime/certFetchingStatus.xhtml b/comm/mailnews/extensions/smime/certFetchingStatus.xhtml
new file mode 100644
index 0000000000..b82042edda
--- /dev/null
+++ b/comm/mailnews/extensions/smime/certFetchingStatus.xhtml
@@ -0,0 +1,40 @@
+<?xml version="1.0"?>
+<!-- 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 http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger-smime/locale/certFetchingStatus.dtd">
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ scrolling="false"
+>
+ <head>
+ <title>&title.label;</title>
+ <script
+ defer="defer"
+ src="chrome://messenger-smime/content/certFetchingStatus.js"
+ ></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <dialog
+ id="certFetchingStatus"
+ buttons="cancel"
+ buttonlabelcancel="&stop.label;"
+ >
+ <stringbundle
+ id="bundle_ldap"
+ src="chrome://mozldap/locale/ldap.properties"
+ />
+
+ <description>&info.message;</description>
+ </dialog>
+ </html:body>
+</html>
diff --git a/comm/mailnews/extensions/smime/certpicker.js b/comm/mailnews/extensions/smime/certpicker.js
new file mode 100644
index 0000000000..31ed9d3ed2
--- /dev/null
+++ b/comm/mailnews/extensions/smime/certpicker.js
@@ -0,0 +1,69 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+var dialogParams;
+var itemCount = 0;
+
+window.addEventListener("DOMContentLoaded", onLoad);
+
+document.addEventListener("dialogaccept", doOK);
+document.addEventListener("dialogcancel", doCancel);
+
+function onLoad() {
+ dialogParams = window.arguments[0].QueryInterface(Ci.nsIDialogParamBlock);
+
+ var selectElement = document.getElementById("nicknames");
+ itemCount = dialogParams.GetInt(0);
+
+ var selIndex = dialogParams.GetInt(1);
+ if (selIndex < 0) {
+ selIndex = 0;
+ }
+
+ for (let i = 0; i < itemCount; i++) {
+ let menuItemNode = document.createXULElement("menuitem");
+ let nick = dialogParams.GetString(i);
+ menuItemNode.setAttribute("value", i);
+ menuItemNode.setAttribute("label", nick); // This is displayed.
+ selectElement.menupopup.appendChild(menuItemNode);
+
+ if (selIndex == i) {
+ selectElement.selectedItem = menuItemNode;
+ }
+ }
+
+ dialogParams.SetInt(0, 0); // Set cancel return value.
+ setDetails();
+}
+
+function setDetails() {
+ let selItem = document.getElementById("nicknames").value;
+ if (selItem.length == 0) {
+ return;
+ }
+
+ let index = parseInt(selItem);
+ let details = dialogParams.GetString(index + itemCount);
+ document.getElementById("details").value = details;
+}
+
+function onCertSelected() {
+ setDetails();
+}
+
+function doOK() {
+ // Signal that the user accepted.
+ dialogParams.SetInt(0, 1);
+
+ // Signal the index of the selected cert in the list of cert nicknames
+ // provided.
+ let index = parseInt(document.getElementById("nicknames").value);
+ dialogParams.SetInt(1, index);
+}
+
+function doCancel() {
+ dialogParams.SetInt(0, 0); // Signal that the user cancelled.
+}
diff --git a/comm/mailnews/extensions/smime/certpicker.xhtml b/comm/mailnews/extensions/smime/certpicker.xhtml
new file mode 100644
index 0000000000..c0ff513cb6
--- /dev/null
+++ b/comm/mailnews/extensions/smime/certpicker.xhtml
@@ -0,0 +1,54 @@
+<?xml version="1.0"?>
+<!-- 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 http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+
+<!DOCTYPE html [ <!ENTITY % amE2EDTD SYSTEM "chrome://messenger/locale/am-smime.dtd">
+%amE2EDTD; ]>
+
+<html
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ scrolling="false"
+>
+ <head>
+ <title>&certPicker.title;</title>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/globalOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://global/content/editMenuOverlay.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger/content/certpicker.js"
+ ></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <dialog id="certPicker" buttons="accept,cancel">
+ <hbox align="center">
+ <label id="pickerInfo" value="&certPicker.info;" />
+ <!-- The items in this menulist must never be sorted,
+ but remain in the order filled by the application
+ -->
+ <menulist id="nicknames" oncommand="onCertSelected();">
+ <menupopup />
+ </menulist>
+ </hbox>
+ <separator class="thin" />
+ <label value="&certPicker.detailsLabel;" />
+ <html:textarea
+ id="details"
+ readonly="readonly"
+ style="height: 12em"
+ ></html:textarea>
+ </dialog>
+ </html:body>
+</html>
diff --git a/comm/mailnews/extensions/smime/components.conf b/comm/mailnews/extensions/smime/components.conf
new file mode 100644
index 0000000000..a5287d608a
--- /dev/null
+++ b/comm/mailnews/extensions/smime/components.conf
@@ -0,0 +1,63 @@
+# 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 http://mozilla.org/MPL/2.0/.
+
+Classes = [
+ {
+ "cid": "{54976882-7421-4286-8ecc-46373f15d7b5}",
+ "contract_ids": ["@mozilla.org/messengercompose/composesecure;1"],
+ "type": "nsMsgComposeSecure",
+ "headers": ["/comm/mailnews/extensions/smime/nsMsgComposeSecure.h"],
+ },
+ {
+ "cid": "{a0134d58-018f-4d40-a099-fa079e5024a6}",
+ "contract_ids": ["@mozilla.org/messenger-smime/smime-encrypted-uris-service;1"],
+ "type": "nsEncryptedSMIMEURIsService",
+ "headers": ["/comm/mailnews/extensions/smime/nsEncryptedSMIMEURIsService.h"],
+ },
+ {
+ "cid": "{5fb907e0-1dd2-11b2-a7c0-f14c416a62a1}",
+ "contract_ids": ["@mozilla.org/nsCMSSecureMessage;1"],
+ "type": "nsCMSSecureMessage",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/extensions/smime/nsCMSSecureMessage.h"],
+ },
+ {
+ "cid": "{9dcef3a4-a3bc-11d5-ba47-00108303b117}",
+ "contract_ids": ["@mozilla.org/nsCMSDecoder;1"],
+ "type": "nsCMSDecoder",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/extensions/smime/nsCMS.h"],
+ },
+ {
+ "cid": "{fb62c8ed-b875-488a-be35-ab9764bcad25}",
+ "contract_ids": ["@mozilla.org/nsCMSDecoderJS;1"],
+ "type": "nsCMSDecoderJS",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/extensions/smime/nsCMS.h"],
+ },
+ {
+ "cid": "{a15789aa-8903-462b-81e9-4aa2cff4d5cb}",
+ "contract_ids": ["@mozilla.org/nsCMSEncoder;1"],
+ "type": "nsCMSEncoder",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/extensions/smime/nsCMS.h"],
+ },
+ {
+ "cid": "{a4557478-ae16-11d5-ba4b-00108303b117}",
+ "contract_ids": ["@mozilla.org/nsCMSMessage;1"],
+ "type": "nsCMSMessage",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/extensions/smime/nsCMS.h"],
+ },
+ {
+ "cid": "{735959a1-af01-447e-b02d-56e968fa52b4}",
+ "contract_ids": [
+ "@mozilla.org/nsCertPickDialogs;1",
+ "@mozilla.org/user_cert_picker;1",
+ ],
+ "type": "nsCertPicker",
+ "init_method": "Init",
+ "headers": ["/comm/mailnews/extensions/smime/nsCertPicker.h"],
+ },
+]
diff --git a/comm/mailnews/extensions/smime/moz.build b/comm/mailnews/extensions/smime/moz.build
new file mode 100644
index 0000000000..f33678ecf2
--- /dev/null
+++ b/comm/mailnews/extensions/smime/moz.build
@@ -0,0 +1,37 @@
+# vim: set filetype=python:
+# 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 http://mozilla.org/MPL/2.0/.
+
+XPIDL_SOURCES += [
+ "nsICertPickDialogs.idl",
+ "nsICMSDecoder.idl",
+ "nsICMSDecoderJS.idl",
+ "nsICMSEncoder.idl",
+ "nsICMSMessage.idl",
+ "nsICMSMessageErrors.idl",
+ "nsICMSSecureMessage.idl",
+ "nsIEncryptedSMIMEURIsSrvc.idl",
+ "nsIMsgSMIMEHeaderSink.idl",
+ "nsIUserCertPicker.idl",
+]
+
+XPIDL_MODULE = "msgsmime"
+
+SOURCES += [
+ "nsCertPicker.cpp",
+ "nsCMS.cpp",
+ "nsCMSSecureMessage.cpp",
+ "nsEncryptedSMIMEURIsService.cpp",
+ "nsMsgComposeSecure.cpp",
+]
+
+FINAL_LIBRARY = "mail"
+
+LOCAL_INCLUDES += [
+ "/security/certverifier",
+ "/security/manager/pki",
+ "/security/manager/ssl",
+]
+
+XPCOM_MANIFESTS += ["components.conf"]
diff --git a/comm/mailnews/extensions/smime/msgCompSecurityInfo.js b/comm/mailnews/extensions/smime/msgCompSecurityInfo.js
new file mode 100644
index 0000000000..eb760a9c58
--- /dev/null
+++ b/comm/mailnews/extensions/smime/msgCompSecurityInfo.js
@@ -0,0 +1,122 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+var gListBox;
+var gViewButton;
+var gBundle;
+
+var gCerts = [];
+
+window.addEventListener("DOMContentLoaded", onLoad);
+window.addEventListener("resize", resizeColumns);
+
+function onLoad() {
+ let params = window.arguments[0];
+ if (!params) {
+ return;
+ }
+
+ gListBox = document.getElementById("infolist");
+ gViewButton = document.getElementById("viewCertButton");
+ gBundle = document.getElementById("bundle_smime_comp_info");
+
+ let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
+ Ci.nsIX509CertDB
+ );
+
+ let missing = [];
+ for (let i = 0; i < params.recipients.length; i++) {
+ let email = params.recipients[i];
+ let dbKey = params.compFields.composeSecure.getCertDBKeyForEmail(email);
+
+ if (dbKey) {
+ gCerts.push(certdb.findCertByDBKey(dbKey));
+ } else {
+ gCerts.push(null);
+ }
+
+ if (!gCerts[i]) {
+ missing.push(params.recipients[i]);
+ }
+ }
+
+ for (let i = 0; i < params.recipients.length; ++i) {
+ let email = document.createXULElement("label");
+ email.setAttribute("value", params.recipients[i]);
+ email.setAttribute("crop", "end");
+ email.setAttribute("style", "width: var(--recipientWidth)");
+
+ let listitem = document.createXULElement("richlistitem");
+ listitem.appendChild(email);
+
+ let cert = gCerts[i];
+ let statusItem = document.createXULElement("label");
+ statusItem.setAttribute(
+ "value",
+ gBundle.getString(cert ? "StatusValid" : "StatusNotFound")
+ );
+ statusItem.setAttribute("style", "width: var(--statusWidth)");
+ listitem.appendChild(statusItem);
+
+ gListBox.appendChild(listitem);
+ }
+ resizeColumns();
+}
+
+function resizeColumns() {
+ let list = document.getElementById("infolist");
+ let cols = list.getElementsByTagName("treecol");
+ list.style.setProperty(
+ "--recipientWidth",
+ cols[0].getBoundingClientRect().width + "px"
+ );
+ list.style.setProperty(
+ "--statusWidth",
+ cols[1].getBoundingClientRect().width + "px"
+ );
+ list.style.setProperty(
+ "--issuedWidth",
+ cols[2].getBoundingClientRect().width + "px"
+ );
+ list.style.setProperty(
+ "--expireWidth",
+ cols[3].getBoundingClientRect().width - 5 + "px"
+ );
+}
+
+// --- borrowed from pippki.js ---
+const PRErrorCodeSuccess = 0;
+
+const certificateUsageEmailSigner = 0x0010;
+const certificateUsageEmailRecipient = 0x0020;
+
+// A map from the name of a certificate usage to the value of the usage.
+const certificateUsages = {
+ certificateUsageEmailRecipient,
+};
+
+function onSelectionChange(event) {
+ gViewButton.disabled = !(
+ gListBox.selectedItems.length == 1 && certForRow(gListBox.selectedIndex)
+ );
+}
+
+function viewCertHelper(parent, cert) {
+ let url = `about:certificate?cert=${encodeURIComponent(
+ cert.getBase64DERString()
+ )}`;
+ let mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane");
+ mail3PaneWindow.switchToTabHavingURI(url, true, {});
+ parent.close();
+}
+
+function certForRow(aRowIndex) {
+ return gCerts[aRowIndex];
+}
+
+function viewSelectedCert() {
+ if (!gViewButton.disabled) {
+ viewCertHelper(window, certForRow(gListBox.selectedIndex));
+ }
+}
diff --git a/comm/mailnews/extensions/smime/msgCompSecurityInfo.xhtml b/comm/mailnews/extensions/smime/msgCompSecurityInfo.xhtml
new file mode 100644
index 0000000000..982d76818e
--- /dev/null
+++ b/comm/mailnews/extensions/smime/msgCompSecurityInfo.xhtml
@@ -0,0 +1,69 @@
+<?xml version="1.0"?>
+<!-- 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 http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/smime/msgCompSecurityInfo.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/colors.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/variables.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/themeableDialog.css" type="text/css"?>
+
+<!DOCTYPE html SYSTEM "chrome://messenger-smime/locale/msgCompSecurityInfo.dtd">
+<html
+ id="msgCompSecurityInfo"
+ xmlns="http://www.w3.org/1999/xhtml"
+ xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ lightweightthemes="true"
+ scrolling="false"
+>
+ <head>
+ <title>&title.label;</title>
+ <link rel="localization" href="branding/brand.ftl" />
+ <script
+ defer="defer"
+ src="chrome://messenger/content/dialogShadowDom.js"
+ ></script>
+ <script
+ defer="defer"
+ src="chrome://messenger-smime/content/msgCompSecurityInfo.js"
+ ></script>
+ </head>
+ <html:body
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ >
+ <dialog buttons="accept">
+ <stringbundle
+ id="bundle_smime_comp_info"
+ src="chrome://messenger-smime/locale/msgCompSecurityInfo.properties"
+ />
+
+ <separator class="thin" />
+ <label value="&status.certificates;" control="infolist" />
+
+ <richlistbox
+ id="infolist"
+ class="theme-listbox"
+ flex="1"
+ onselect="onSelectionChange(event);"
+ >
+ <treecols>
+ <treecol id="recipientHeader" label="&tree.recipient;" />
+ <treecol id="statusHeader" label="&tree.status;" />
+ <treecol id="issuedDateHeader" label="&tree.issuedDate;" />
+ <treecol id="expiresDateHeader" label="&tree.expiresDate;" />
+ </treecols>
+ </richlistbox>
+ <hbox pack="start">
+ <button
+ id="viewCertButton"
+ disabled="true"
+ label="&view.label;"
+ accesskey="&view.accesskey;"
+ oncommand="viewSelectedCert();"
+ />
+ </hbox>
+ </dialog>
+ </html:body>
+</html>
diff --git a/comm/mailnews/extensions/smime/msgReadSMIMEOverlay.js b/comm/mailnews/extensions/smime/msgReadSMIMEOverlay.js
new file mode 100644
index 0000000000..bd7672bbe8
--- /dev/null
+++ b/comm/mailnews/extensions/smime/msgReadSMIMEOverlay.js
@@ -0,0 +1,251 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+/* import-globals-from ../../../mail/base/content/aboutMessage.js */
+
+var gEncryptionStatus = -1;
+var gSignatureStatus = -1;
+var gSignerCert = null;
+var gEncryptionCert = null;
+
+function showImapSignatureUnknown() {
+ let readSmimeBundle = Services.strings.createBundle(
+ "chrome://messenger-smime/locale/msgReadSMIMEOverlay.properties"
+ );
+ let brandBundle = document.getElementById("bundle_brand");
+ if (!readSmimeBundle || !brandBundle) {
+ return;
+ }
+
+ if (
+ Services.prompt.confirm(
+ window,
+ brandBundle.getString("brandShortName"),
+ readSmimeBundle.GetStringFromName("ImapOnDemand")
+ )
+ ) {
+ gDBView.reloadMessageWithAllParts();
+ }
+}
+
+/**
+ * Populate the message security popup panel with S/MIME data.
+ */
+function loadSmimeMessageSecurityInfo() {
+ let sBundle = Services.strings.createBundle(
+ "chrome://messenger-smime/locale/msgSecurityInfo.properties"
+ );
+
+ let sigInfoLabel = null;
+ let sigInfoHeader = null;
+ let sigInfo = null;
+ let sigInfo_clueless = false;
+ let sigClass = null;
+
+ switch (gSignatureStatus) {
+ case -1:
+ case Ci.nsICMSMessageErrors.VERIFY_NOT_SIGNED:
+ sigInfoLabel = "SINoneLabel";
+ sigInfo = "SINone";
+ sigClass = "none";
+ break;
+
+ case Ci.nsICMSMessageErrors.SUCCESS:
+ sigInfoLabel = "SIValidLabel";
+ sigInfo = "SIValid";
+ sigClass = "ok";
+ break;
+
+ case Ci.nsICMSMessageErrors.VERIFY_BAD_SIGNATURE:
+ case Ci.nsICMSMessageErrors.VERIFY_DIGEST_MISMATCH:
+ sigInfoLabel = "SIInvalidLabel";
+ sigInfoHeader = "SIInvalidHeader";
+ sigInfo = "SIContentAltered";
+ sigClass = "mismatch";
+ break;
+
+ case Ci.nsICMSMessageErrors.VERIFY_UNKNOWN_ALGO:
+ case Ci.nsICMSMessageErrors.VERIFY_UNSUPPORTED_ALGO:
+ sigInfoLabel = "SIInvalidLabel";
+ sigInfoHeader = "SIInvalidHeader";
+ sigInfo = "SIInvalidCipher";
+ sigClass = "unknown";
+ break;
+
+ case Ci.nsICMSMessageErrors.VERIFY_HEADER_MISMATCH:
+ sigInfoLabel = "SIPartiallyValidLabel";
+ sigInfoHeader = "SIPartiallyValidHeader";
+ sigInfo = "SIHeaderMismatch";
+ sigClass = "mismatch";
+ break;
+
+ case Ci.nsICMSMessageErrors.VERIFY_CERT_WITHOUT_ADDRESS:
+ sigInfoLabel = "SIPartiallyValidLabel";
+ sigInfoHeader = "SIPartiallyValidHeader";
+ sigInfo = "SICertWithoutAddress";
+ sigClass = "unknown";
+ break;
+
+ case Ci.nsICMSMessageErrors.VERIFY_UNTRUSTED:
+ sigInfoLabel = "SIInvalidLabel";
+ sigInfoHeader = "SIInvalidHeader";
+ sigInfo = "SIUntrustedCA";
+ sigClass = "notok";
+ // XXX Need to extend to communicate better errors
+ // might also be:
+ // SIExpired SIRevoked SINotYetValid SIUnknownCA SIExpiredCA SIRevokedCA SINotYetValidCA
+ break;
+
+ case Ci.nsICMSMessageErrors.VERIFY_NOT_YET_ATTEMPTED:
+ case Ci.nsICMSMessageErrors.GENERAL_ERROR:
+ case Ci.nsICMSMessageErrors.VERIFY_NO_CONTENT_INFO:
+ case Ci.nsICMSMessageErrors.VERIFY_BAD_DIGEST:
+ case Ci.nsICMSMessageErrors.VERIFY_NOCERT:
+ case Ci.nsICMSMessageErrors.VERIFY_ERROR_UNVERIFIED:
+ case Ci.nsICMSMessageErrors.VERIFY_ERROR_PROCESSING:
+ case Ci.nsICMSMessageErrors.VERIFY_MALFORMED_SIGNATURE:
+ case Ci.nsICMSMessageErrors.VERIFY_TIME_MISMATCH:
+ sigInfoLabel = "SIInvalidLabel";
+ sigInfoHeader = "SIInvalidHeader";
+ sigInfo_clueless = true;
+ sigClass = "unverified";
+ break;
+ default:
+ console.error("Unexpected gSignatureStatus: " + gSignatureStatus);
+ }
+
+ document.getElementById("techLabel").textContent = "- S/MIME";
+
+ let signatureLabel = document.getElementById("signatureLabel");
+ signatureLabel.textContent = sBundle.GetStringFromName(sigInfoLabel);
+
+ // Remove the second class to properly update the signature icon.
+ signatureLabel.classList.remove(signatureLabel.classList.item(1));
+ signatureLabel.classList.add(sigClass);
+
+ if (sigInfoHeader) {
+ let label = document.getElementById("signatureHeader");
+ label.collapsed = false;
+ label.textContent = sBundle.GetStringFromName(sigInfoHeader);
+ }
+
+ let str;
+ if (sigInfo) {
+ str = sBundle.GetStringFromName(sigInfo);
+ } else if (sigInfo_clueless) {
+ str =
+ sBundle.GetStringFromName("SIClueless") + " (" + gSignatureStatus + ")";
+ }
+ document.getElementById("signatureExplanation").textContent = str;
+
+ let encInfoLabel = null;
+ let encInfoHeader = null;
+ let encInfo = null;
+ let encInfo_clueless = false;
+ let encClass = null;
+
+ switch (gEncryptionStatus) {
+ case -1:
+ encInfoLabel = "EINoneLabel2";
+ encInfo = "EINone";
+ encClass = "none";
+ break;
+
+ case Ci.nsICMSMessageErrors.SUCCESS:
+ encInfoLabel = "EIValidLabel";
+ encInfo = "EIValid";
+ encClass = "ok";
+ break;
+
+ case Ci.nsICMSMessageErrors.ENCRYPT_INCOMPLETE:
+ encInfoLabel = "EIInvalidLabel";
+ encInfo = "EIContentAltered";
+ encClass = "notok";
+ break;
+
+ case Ci.nsICMSMessageErrors.GENERAL_ERROR:
+ encInfoLabel = "EIInvalidLabel";
+ encInfoHeader = "EIInvalidHeader";
+ encInfo_clueless = 1;
+ encClass = "notok";
+ break;
+ default:
+ console.error("Unexpected gEncryptionStatus: " + gEncryptionStatus);
+ }
+
+ let encryptionLabel = document.getElementById("encryptionLabel");
+ encryptionLabel.textContent = sBundle.GetStringFromName(encInfoLabel);
+
+ // Remove the second class to properly update the encryption icon.
+ encryptionLabel.classList.remove(encryptionLabel.classList.item(1));
+ encryptionLabel.classList.add(encClass);
+
+ if (encInfoHeader) {
+ let label = document.getElementById("encryptionHeader");
+ label.collapsed = false;
+ label.textContent = sBundle.GetStringFromName(encInfoHeader);
+ }
+
+ if (encInfo) {
+ str = sBundle.GetStringFromName(encInfo);
+ } else if (encInfo_clueless) {
+ str = sBundle.GetStringFromName("EIClueless");
+ }
+ document.getElementById("encryptionExplanation").textContent = str;
+
+ if (gSignerCert) {
+ document.getElementById("signatureCert").collapsed = false;
+ if (gSignerCert.subjectName) {
+ document.getElementById("signedBy").textContent = gSignerCert.commonName;
+ }
+ if (gSignerCert.emailAddress) {
+ document.getElementById("signerEmail").textContent =
+ gSignerCert.emailAddress;
+ }
+ if (gSignerCert.issuerName) {
+ document.getElementById("sigCertIssuedBy").textContent =
+ gSignerCert.issuerCommonName;
+ }
+ }
+
+ if (gEncryptionCert) {
+ document.getElementById("encryptionCert").collapsed = false;
+ if (gEncryptionCert.subjectName) {
+ document.getElementById("encryptedFor").textContent =
+ gEncryptionCert.commonName;
+ }
+ if (gEncryptionCert.emailAddress) {
+ document.getElementById("recipientEmail").textContent =
+ gEncryptionCert.emailAddress;
+ }
+ if (gEncryptionCert.issuerName) {
+ document.getElementById("encCertIssuedBy").textContent =
+ gEncryptionCert.issuerCommonName;
+ }
+ }
+}
+
+function viewSignatureCert() {
+ if (!gSignerCert) {
+ return;
+ }
+
+ let url = `about:certificate?cert=${encodeURIComponent(
+ gSignerCert.getBase64DERString()
+ )}`;
+ let mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane");
+ mail3PaneWindow.switchToTabHavingURI(url, true, {});
+}
+
+function viewEncryptionCert() {
+ if (!gEncryptionCert) {
+ return;
+ }
+
+ let url = `about:certificate?cert=${encodeURIComponent(
+ gEncryptionCert.getBase64DERString()
+ )}`;
+ let mail3PaneWindow = Services.wm.getMostRecentWindow("mail:3pane");
+ mail3PaneWindow.switchToTabHavingURI(url, true, {});
+}
diff --git a/comm/mailnews/extensions/smime/nsCMS.cpp b/comm/mailnews/extensions/smime/nsCMS.cpp
new file mode 100644
index 0000000000..be743b9663
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsCMS.cpp
@@ -0,0 +1,1187 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsCMS.h"
+
+#include "CertVerifier.h"
+#include "CryptoTask.h"
+#include "ScopedNSSTypes.h"
+#include "cms.h"
+#include "mozilla/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "nsDependentSubstring.h"
+#include "nsICryptoHash.h"
+#include "nsISupports.h"
+#include "nsIX509CertDB.h"
+#include "nsNSSCertificate.h"
+#include "nsNSSComponent.h"
+#include "nsNSSHelper.h"
+#include "nsServiceManagerUtils.h"
+#include "mozpkix/Result.h"
+#include "mozpkix/pkixtypes.h"
+#include "sechash.h"
+#include "secerr.h"
+#include "smime.h"
+#include "mozilla/StaticMutex.h"
+#include "nsIPrefBranch.h"
+
+using namespace mozilla;
+using namespace mozilla::psm;
+using namespace mozilla::pkix;
+
+static mozilla::LazyLogModule gCMSLog("CMS");
+
+NS_IMPL_ISUPPORTS(nsCMSMessage, nsICMSMessage)
+
+nsCMSMessage::nsCMSMessage() { m_cmsMsg = nullptr; }
+nsCMSMessage::nsCMSMessage(NSSCMSMessage* aCMSMsg) { m_cmsMsg = aCMSMsg; }
+
+nsCMSMessage::~nsCMSMessage() {
+ if (m_cmsMsg) {
+ NSS_CMSMessage_Destroy(m_cmsMsg);
+ }
+}
+
+nsresult nsCMSMessage::Init() {
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssInitialized =
+ do_GetService("@mozilla.org/psm;1", &rv);
+ return rv;
+}
+
+NS_IMETHODIMP nsCMSMessage::VerifySignature(int32_t verifyFlags) {
+ return CommonVerifySignature(verifyFlags, {}, 0);
+}
+
+NSSCMSSignerInfo* nsCMSMessage::GetTopLevelSignerInfo() {
+ if (!m_cmsMsg) return nullptr;
+
+ if (!NSS_CMSMessage_IsSigned(m_cmsMsg)) return nullptr;
+
+ NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(m_cmsMsg, 0);
+ if (!cinfo) return nullptr;
+
+ NSSCMSSignedData* sigd =
+ (NSSCMSSignedData*)NSS_CMSContentInfo_GetContent(cinfo);
+ if (!sigd) return nullptr;
+
+ PR_ASSERT(NSS_CMSSignedData_SignerInfoCount(sigd) > 0);
+ return NSS_CMSSignedData_GetSignerInfo(sigd, 0);
+}
+
+NS_IMETHODIMP nsCMSMessage::GetSignerEmailAddress(char** aEmail) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerEmailAddress"));
+ NS_ENSURE_ARG(aEmail);
+
+ NSSCMSSignerInfo* si = GetTopLevelSignerInfo();
+ if (!si) return NS_ERROR_FAILURE;
+
+ *aEmail = NSS_CMSSignerInfo_GetSignerEmailAddress(si);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCMSMessage::GetSignerCommonName(char** aName) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::GetSignerCommonName"));
+ NS_ENSURE_ARG(aName);
+
+ NSSCMSSignerInfo* si = GetTopLevelSignerInfo();
+ if (!si) return NS_ERROR_FAILURE;
+
+ *aName = NSS_CMSSignerInfo_GetSignerCommonName(si);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCMSMessage::ContentIsEncrypted(bool* isEncrypted) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::ContentIsEncrypted"));
+ NS_ENSURE_ARG(isEncrypted);
+
+ if (!m_cmsMsg) return NS_ERROR_FAILURE;
+
+ *isEncrypted = NSS_CMSMessage_IsEncrypted(m_cmsMsg);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCMSMessage::ContentIsSigned(bool* isSigned) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::ContentIsSigned"));
+ NS_ENSURE_ARG(isSigned);
+
+ if (!m_cmsMsg) return NS_ERROR_FAILURE;
+
+ *isSigned = NSS_CMSMessage_IsSigned(m_cmsMsg);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCMSMessage::GetSignerCert(nsIX509Cert** scert) {
+ NSSCMSSignerInfo* si = GetTopLevelSignerInfo();
+ if (!si) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIX509Cert> cert;
+ if (si->cert) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::GetSignerCert got signer cert"));
+
+ nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID);
+ nsTArray<uint8_t> certBytes;
+ certBytes.AppendElements(si->cert->derCert.data, si->cert->derCert.len);
+ nsresult rv = certdb->ConstructX509(certBytes, getter_AddRefs(cert));
+ NS_ENSURE_SUCCESS(rv, rv);
+ } else {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::GetSignerCert no signer cert, do we have a cert "
+ "list? %s",
+ (si->certList ? "yes" : "no")));
+
+ *scert = nullptr;
+ }
+
+ cert.forget(scert);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCMSMessage::GetEncryptionCert(nsIX509Cert**) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP nsCMSMessage::GetSigningTime(PRTime* aTime) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::GetSigningTime"));
+ NS_ENSURE_ARG(aTime);
+
+ NSSCMSSignerInfo* si = GetTopLevelSignerInfo();
+ if (!si) {
+ return NS_ERROR_FAILURE;
+ }
+
+ SECStatus getSigningTimeResult = NSS_CMSSignerInfo_GetSigningTime(si, aTime);
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::GetSigningTime result: %s",
+ (getSigningTimeResult ? "ok" : "fail")));
+
+ return getSigningTimeResult == SECSuccess ? NS_OK : NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsCMSMessage::VerifyDetachedSignature(int32_t verifyFlags,
+ const nsTArray<uint8_t>& aDigestData,
+ int16_t aDigestType) {
+ if (aDigestData.IsEmpty()) return NS_ERROR_FAILURE;
+
+ return CommonVerifySignature(verifyFlags, aDigestData, aDigestType);
+}
+
+// This is an exact copy of NSS_CMSArray_Count from NSS' cmsarray.c,
+// temporarily necessary, see below for for justification.
+static int myNSS_CMSArray_Count(void** array) {
+ int n = 0;
+
+ if (array == NULL) return 0;
+
+ while (*array++ != NULL) n++;
+
+ return n;
+}
+
+// This is an exact copy of NSS_CMSArray_Add from NSS' cmsarray.c,
+// temporarily necessary, see below for for justification.
+static SECStatus myNSS_CMSArray_Add(PLArenaPool* poolp, void*** array,
+ void* obj) {
+ void** p;
+ int n;
+ void** dest;
+
+ PORT_Assert(array != NULL);
+ if (array == NULL) return SECFailure;
+
+ if (*array == NULL) {
+ dest = (void**)PORT_ArenaAlloc(poolp, 2 * sizeof(void*));
+ n = 0;
+ } else {
+ n = 0;
+ p = *array;
+ while (*p++) n++;
+ dest = (void**)PORT_ArenaGrow(poolp, *array, (n + 1) * sizeof(void*),
+ (n + 2) * sizeof(void*));
+ }
+
+ if (dest == NULL) return SECFailure;
+
+ dest[n] = obj;
+ dest[n + 1] = NULL;
+ *array = dest;
+ return SECSuccess;
+}
+
+// This is an exact copy of NSS_CMSArray_Add from NSS' cmsarray.c,
+// temporarily necessary, see below for for justification.
+static SECStatus myNSS_CMSSignedData_AddTempCertificate(NSSCMSSignedData* sigd,
+ CERTCertificate* cert) {
+ CERTCertificate* c;
+ SECStatus rv;
+
+ if (!sigd || !cert) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ c = CERT_DupCertificate(cert);
+ rv = myNSS_CMSArray_Add(sigd->cmsg->poolp, (void***)&(sigd->tempCerts),
+ (void*)c);
+ return rv;
+}
+
+typedef SECStatus (*extraVerificationOnCertFn)(CERTCertificate* cert,
+ SECCertUsage certusage);
+
+static SECStatus myExtraVerificationOnCert(CERTCertificate* cert,
+ SECCertUsage certusage) {
+ RefPtr<SharedCertVerifier> certVerifier;
+ certVerifier = GetDefaultCertVerifier();
+ if (!certVerifier) {
+ return SECFailure;
+ }
+
+ SECCertificateUsage usageForPkix;
+
+ switch (certusage) {
+ case certUsageEmailSigner:
+ usageForPkix = certificateUsageEmailSigner;
+ break;
+ case certUsageEmailRecipient:
+ usageForPkix = certificateUsageEmailRecipient;
+ break;
+ default:
+ return SECFailure;
+ }
+
+ nsTArray<uint8_t> certBytes(cert->derCert.data, cert->derCert.len);
+ nsTArray<nsTArray<uint8_t>> builtChain;
+ // This code is used when verifying incoming certificates, including
+ // a signature certificate. Performing OCSP is necessary.
+ // Allowing OCSP in blocking mode should be fine, because all our
+ // callers run this code on a separate thread, using
+ // SMimeVerificationTask/CryptoTask.
+ mozilla::pkix::Result result = certVerifier->VerifyCert(
+ certBytes, usageForPkix, Now(), nullptr /*XXX pinarg*/,
+ nullptr /*hostname*/, builtChain);
+ if (result != mozilla::pkix::Success) {
+ return SECFailure;
+ }
+
+ return SECSuccess;
+}
+
+// This is a temporary copy of NSS_CMSSignedData_ImportCerts, which
+// performs additional verifications prior to import.
+// The copy is almost identical to the original.
+//
+// The ONLY DIFFERENCE is the addition of parameter extraVerifyFn,
+// and the call to it - plus a non-null check.
+//
+// NSS should add this or a similar API in the future,
+// and then these temporary functions should be removed, including
+// the ones above. Request is tracked in bugzilla 1738592.
+static SECStatus myNSS_CMSSignedData_ImportCerts(
+ NSSCMSSignedData* sigd, CERTCertDBHandle* certdb, SECCertUsage certusage,
+ PRBool keepcerts, extraVerificationOnCertFn extraVerifyFn) {
+ int certcount;
+ CERTCertificate** certArray = NULL;
+ CERTCertList* certList = NULL;
+ CERTCertListNode* node;
+ SECStatus rv;
+ SECItem** rawArray;
+ int i;
+ PRTime now;
+
+ if (!sigd) {
+ PORT_SetError(SEC_ERROR_INVALID_ARGS);
+ return SECFailure;
+ }
+
+ certcount = myNSS_CMSArray_Count((void**)sigd->rawCerts);
+
+ /* get the certs in the temp DB */
+ rv = CERT_ImportCerts(certdb, certusage, certcount, sigd->rawCerts,
+ &certArray, PR_FALSE, PR_FALSE, NULL);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* save the certs so they don't get destroyed */
+ for (i = 0; i < certcount; i++) {
+ CERTCertificate* cert = certArray[i];
+ if (cert) myNSS_CMSSignedData_AddTempCertificate(sigd, cert);
+ }
+
+ if (!keepcerts) {
+ goto done;
+ }
+
+ /* build a CertList for filtering */
+ certList = CERT_NewCertList();
+ if (certList == NULL) {
+ rv = SECFailure;
+ goto loser;
+ }
+ for (i = 0; i < certcount; i++) {
+ CERTCertificate* cert = certArray[i];
+ if (cert) cert = CERT_DupCertificate(cert);
+ if (cert) CERT_AddCertToListTail(certList, cert);
+ }
+
+ /* filter out the certs we don't want */
+ rv = CERT_FilterCertListByUsage(certList, certusage, PR_FALSE);
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ /* go down the remaining list of certs and verify that they have
+ * valid chains, then import them.
+ */
+ now = PR_Now();
+ for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList);
+ node = CERT_LIST_NEXT(node)) {
+ CERTCertificateList* certChain;
+
+ if (!node->cert) {
+ continue;
+ }
+
+ if (extraVerifyFn) {
+ if ((*extraVerifyFn)(node->cert, certusage) != SECSuccess) {
+ continue;
+ }
+ }
+
+ if (CERT_VerifyCert(certdb, node->cert, PR_TRUE, certusage, now, NULL,
+ NULL) != SECSuccess) {
+ continue;
+ }
+
+ certChain = CERT_CertChainFromCert(node->cert, certusage, PR_FALSE);
+ if (!certChain) {
+ continue;
+ }
+
+ /*
+ * CertChain returns an array of SECItems, import expects an array of
+ * SECItem pointers. Create the SECItem Pointers from the array of
+ * SECItems.
+ */
+ rawArray = (SECItem**)PORT_Alloc(certChain->len * sizeof(SECItem*));
+ if (!rawArray) {
+ CERT_DestroyCertificateList(certChain);
+ continue;
+ }
+ for (i = 0; i < certChain->len; i++) {
+ rawArray[i] = &certChain->certs[i];
+ }
+ (void)CERT_ImportCerts(certdb, certusage, certChain->len, rawArray, NULL,
+ keepcerts, PR_FALSE, NULL);
+ PORT_Free(rawArray);
+ CERT_DestroyCertificateList(certChain);
+ }
+
+ rv = SECSuccess;
+
+ /* XXX CRL handling */
+
+done:
+ if (sigd->signerInfos != NULL) {
+ /* fill in all signerinfo's certs */
+ for (i = 0; sigd->signerInfos[i] != NULL; i++)
+ (void)NSS_CMSSignerInfo_GetSigningCertificate(sigd->signerInfos[i],
+ certdb);
+ }
+
+loser:
+ /* now free everything */
+ if (certArray) {
+ CERT_DestroyCertArray(certArray, certcount);
+ }
+ if (certList) {
+ CERT_DestroyCertList(certList);
+ }
+
+ return rv;
+}
+
+nsresult nsCMSMessage::CommonVerifySignature(
+ int32_t verifyFlags, const nsTArray<uint8_t>& aDigestData,
+ int16_t aDigestType) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature, content level count %d",
+ NSS_CMSMessage_ContentLevelCount(m_cmsMsg)));
+ NSSCMSContentInfo* cinfo = nullptr;
+ NSSCMSSignedData* sigd = nullptr;
+ NSSCMSSignerInfo* si;
+ int32_t nsigners;
+ nsresult rv = NS_ERROR_FAILURE;
+ SECOidTag sigAlgTag;
+
+ if (!NSS_CMSMessage_IsSigned(m_cmsMsg)) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - not signed"));
+ return NS_ERROR_CMS_VERIFY_NOT_SIGNED;
+ }
+
+ cinfo = NSS_CMSMessage_ContentLevel(m_cmsMsg, 0);
+ if (cinfo) {
+ switch (NSS_CMSContentInfo_GetContentTypeTag(cinfo)) {
+ case SEC_OID_PKCS7_SIGNED_DATA:
+ sigd = reinterpret_cast<NSSCMSSignedData*>(
+ NSS_CMSContentInfo_GetContent(cinfo));
+ break;
+
+ case SEC_OID_PKCS7_ENVELOPED_DATA:
+ case SEC_OID_PKCS7_ENCRYPTED_DATA:
+ case SEC_OID_PKCS7_DIGESTED_DATA:
+ default: {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - unexpected "
+ "ContentTypeTag"));
+ rv = NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
+ goto loser;
+ }
+ }
+ }
+
+ if (!sigd) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - no content info"));
+ rv = NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO;
+ goto loser;
+ }
+
+ if (!aDigestData.IsEmpty()) {
+ SECOidTag oidTag;
+ SECItem digest;
+ // NSS_CMSSignedData_SetDigestValue() takes a copy and won't mutate our
+ // data, so we're OK to cast away the const here.
+ digest.data = const_cast<uint8_t*>(aDigestData.Elements());
+ digest.len = aDigestData.Length();
+
+ if (NSS_CMSSignedData_HasDigests(sigd)) {
+ SECAlgorithmID** existingAlgs = NSS_CMSSignedData_GetDigestAlgs(sigd);
+ if (existingAlgs) {
+ while (*existingAlgs) {
+ SECAlgorithmID* alg = *existingAlgs;
+ SECOidTag algOIDTag = SECOID_FindOIDTag(&alg->algorithm);
+ NSS_CMSSignedData_SetDigestValue(sigd, algOIDTag, NULL);
+ ++existingAlgs;
+ }
+ }
+ }
+
+ oidTag =
+ HASH_GetHashOidTagByHashType(static_cast<HASH_HashType>(aDigestType));
+ if (oidTag == SEC_OID_UNKNOWN) {
+ rv = NS_ERROR_CMS_VERIFY_BAD_DIGEST;
+ goto loser;
+ }
+
+ if (NSS_CMSSignedData_SetDigestValue(sigd, oidTag, &digest)) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - bad digest"));
+ rv = NS_ERROR_CMS_VERIFY_BAD_DIGEST;
+ goto loser;
+ }
+ }
+
+ // Import certs. Note that import failure is not a signature verification
+ // failure. //
+ if (myNSS_CMSSignedData_ImportCerts(
+ sigd, CERT_GetDefaultCertDB(), certUsageEmailRecipient, true,
+ myExtraVerificationOnCert) != SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - can not import certs"));
+ }
+
+ nsigners = NSS_CMSSignedData_SignerInfoCount(sigd);
+ PR_ASSERT(nsigners > 0);
+ NS_ENSURE_TRUE(nsigners > 0, NS_ERROR_UNEXPECTED);
+ si = NSS_CMSSignedData_GetSignerInfo(sigd, 0);
+
+ NS_ENSURE_TRUE(si, NS_ERROR_UNEXPECTED);
+ NS_ENSURE_TRUE(si->cert, NS_ERROR_UNEXPECTED);
+
+ // See bug 324474. We want to make sure the signing cert is
+ // still valid at the current time.
+
+ if (myExtraVerificationOnCert(si->cert, certUsageEmailSigner) != SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - signing cert not trusted "
+ "now"));
+ rv = NS_ERROR_CMS_VERIFY_UNTRUSTED;
+ goto loser;
+ }
+
+ sigAlgTag = NSS_CMSSignerInfo_GetDigestAlgTag(si);
+ switch (sigAlgTag) {
+ case SEC_OID_SHA256:
+ case SEC_OID_SHA384:
+ case SEC_OID_SHA512:
+ break;
+
+ case SEC_OID_SHA1:
+ if (verifyFlags & nsICMSVerifyFlags::VERIFY_ALLOW_WEAK_SHA1) {
+ break;
+ }
+ // else fall through to failure
+#if defined(__clang__)
+ [[clang::fallthrough]];
+#endif
+
+ default:
+ MOZ_LOG(
+ gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - unsupported digest algo"));
+ rv = NS_ERROR_CMS_VERIFY_UNSUPPORTED_ALGO;
+ goto loser;
+ };
+
+ // We verify the first signer info, only //
+ // XXX: NSS_CMSSignedData_VerifySignerInfo calls CERT_VerifyCert, which
+ // requires NSS's certificate verification configuration to be done in
+ // order to work well (e.g. honoring OCSP preferences and proxy settings
+ // for OCSP requests), but Gecko stopped doing that configuration. Something
+ // similar to what was done for Gecko bug 1028643 needs to be done here too.
+ if (NSS_CMSSignedData_VerifySignerInfo(sigd, 0, CERT_GetDefaultCertDB(),
+ certUsageEmailSigner) != SECSuccess) {
+ MOZ_LOG(
+ gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - unable to verify signature"));
+
+ if (NSSCMSVS_SigningCertNotFound == si->verificationStatus) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - signing cert not found"));
+ rv = NS_ERROR_CMS_VERIFY_NOCERT;
+ } else if (NSSCMSVS_SigningCertNotTrusted == si->verificationStatus) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - signing cert not trusted "
+ "at signing time"));
+ rv = NS_ERROR_CMS_VERIFY_UNTRUSTED;
+ } else if (NSSCMSVS_Unverified == si->verificationStatus) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - can not verify"));
+ rv = NS_ERROR_CMS_VERIFY_ERROR_UNVERIFIED;
+ } else if (NSSCMSVS_ProcessingError == si->verificationStatus) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - processing error"));
+ rv = NS_ERROR_CMS_VERIFY_ERROR_PROCESSING;
+ } else if (NSSCMSVS_BadSignature == si->verificationStatus) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - bad signature"));
+ rv = NS_ERROR_CMS_VERIFY_BAD_SIGNATURE;
+ } else if (NSSCMSVS_DigestMismatch == si->verificationStatus) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - digest mismatch"));
+ rv = NS_ERROR_CMS_VERIFY_DIGEST_MISMATCH;
+ } else if (NSSCMSVS_SignatureAlgorithmUnknown == si->verificationStatus) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - algo unknown"));
+ rv = NS_ERROR_CMS_VERIFY_UNKNOWN_ALGO;
+ } else if (NSSCMSVS_SignatureAlgorithmUnsupported ==
+ si->verificationStatus) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - algo not supported"));
+ rv = NS_ERROR_CMS_VERIFY_UNSUPPORTED_ALGO;
+ } else if (NSSCMSVS_MalformedSignature == si->verificationStatus) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - malformed signature"));
+ rv = NS_ERROR_CMS_VERIFY_MALFORMED_SIGNATURE;
+ }
+
+ goto loser;
+ }
+
+ // Save the profile. Note that save import failure is not a signature
+ // verification failure. //
+ if (NSS_SMIMESignerInfo_SaveSMIMEProfile(si) != SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CommonVerifySignature - unable to save smime "
+ "profile"));
+ }
+
+ rv = NS_OK;
+loser:
+ return rv;
+}
+
+NS_IMETHODIMP nsCMSMessage::AsyncVerifySignature(
+ int32_t verifyFlags, nsISMimeVerificationListener* aListener) {
+ return CommonAsyncVerifySignature(verifyFlags, aListener, {}, 0);
+}
+
+NS_IMETHODIMP nsCMSMessage::AsyncVerifyDetachedSignature(
+ int32_t verifyFlags, nsISMimeVerificationListener* aListener,
+ const nsTArray<uint8_t>& aDigestData, int16_t aDigestType) {
+ if (aDigestData.IsEmpty()) return NS_ERROR_FAILURE;
+
+ return CommonAsyncVerifySignature(verifyFlags, aListener, aDigestData,
+ aDigestType);
+}
+
+class SMimeVerificationTask final : public CryptoTask {
+ public:
+ SMimeVerificationTask(nsICMSMessage* aMessage, int32_t verifyFlags,
+ nsISMimeVerificationListener* aListener,
+ const nsTArray<uint8_t>& aDigestData,
+ int16_t aDigestType)
+ : mMessage(aMessage),
+ mListener(aListener),
+ mDigestData(aDigestData.Clone()),
+ mDigestType(aDigestType),
+ mVerifyFlags(verifyFlags) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+
+ private:
+ virtual nsresult CalculateResult() override {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ // Because the S/MIME code and related certificate processing isn't
+ // sufficiently threadsafe (see bug 1529003), we want this code to
+ // never run in parallel (see bug 1386601).
+ mozilla::StaticMutexAutoLock lock(sMutex);
+ nsresult rv;
+ if (mDigestData.IsEmpty()) {
+ rv = mMessage->VerifySignature(mVerifyFlags);
+ } else {
+ rv = mMessage->VerifyDetachedSignature(mVerifyFlags, mDigestData,
+ mDigestType);
+ }
+
+ return rv;
+ }
+ virtual void CallCallback(nsresult rv) override {
+ MOZ_ASSERT(NS_IsMainThread());
+ mListener->Notify(mMessage, rv);
+ }
+
+ nsCOMPtr<nsICMSMessage> mMessage;
+ nsCOMPtr<nsISMimeVerificationListener> mListener;
+ nsTArray<uint8_t> mDigestData;
+ int16_t mDigestType;
+ int32_t mVerifyFlags;
+
+ static mozilla::StaticMutex sMutex;
+};
+
+mozilla::StaticMutex SMimeVerificationTask::sMutex;
+
+nsresult nsCMSMessage::CommonAsyncVerifySignature(
+ int32_t verifyFlags, nsISMimeVerificationListener* aListener,
+ const nsTArray<uint8_t>& aDigestData, int16_t aDigestType) {
+ RefPtr<CryptoTask> task = new SMimeVerificationTask(
+ this, verifyFlags, aListener, aDigestData, aDigestType);
+ return task->Dispatch();
+}
+
+class nsZeroTerminatedCertArray {
+ public:
+ nsZeroTerminatedCertArray() : mCerts(nullptr), mPoolp(nullptr), mSize(0) {}
+
+ ~nsZeroTerminatedCertArray() {
+ if (mCerts) {
+ for (uint32_t i = 0; i < mSize; i++) {
+ if (mCerts[i]) {
+ CERT_DestroyCertificate(mCerts[i]);
+ }
+ }
+ }
+
+ if (mPoolp) PORT_FreeArena(mPoolp, false);
+ }
+
+ bool allocate(uint32_t count) {
+ // only allow allocation once
+ if (mPoolp) return false;
+
+ mSize = count;
+
+ if (!mSize) return false;
+
+ mPoolp = PORT_NewArena(1024);
+ if (!mPoolp) return false;
+
+ mCerts = (CERTCertificate**)PORT_ArenaZAlloc(
+ mPoolp, (count + 1) * sizeof(CERTCertificate*));
+
+ if (!mCerts) return false;
+
+ // null array, including zero termination
+ for (uint32_t i = 0; i < count + 1; i++) {
+ mCerts[i] = nullptr;
+ }
+
+ return true;
+ }
+
+ void set(uint32_t i, CERTCertificate* c) {
+ if (i >= mSize) return;
+
+ if (mCerts[i]) {
+ CERT_DestroyCertificate(mCerts[i]);
+ }
+
+ mCerts[i] = CERT_DupCertificate(c);
+ }
+
+ CERTCertificate* get(uint32_t i) {
+ if (i >= mSize) return nullptr;
+
+ return CERT_DupCertificate(mCerts[i]);
+ }
+
+ CERTCertificate** getRawArray() { return mCerts; }
+
+ private:
+ CERTCertificate** mCerts;
+ PLArenaPool* mPoolp;
+ uint32_t mSize;
+};
+
+NS_IMETHODIMP nsCMSMessage::CreateEncrypted(
+ const nsTArray<RefPtr<nsIX509Cert>>& aRecipientCerts) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::CreateEncrypted"));
+ NSSCMSContentInfo* cinfo;
+ NSSCMSEnvelopedData* envd;
+ NSSCMSRecipientInfo* recipientInfo;
+ nsZeroTerminatedCertArray recipientCerts;
+ SECOidTag bulkAlgTag;
+ int keySize;
+ uint32_t i;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ // Check the recipient certificates //
+ uint32_t recipientCertCount = aRecipientCerts.Length();
+ PR_ASSERT(recipientCertCount > 0);
+
+ if (!recipientCerts.allocate(recipientCertCount)) {
+ goto loser;
+ }
+
+ for (i = 0; i < recipientCertCount; i++) {
+ nsIX509Cert* x509cert = aRecipientCerts[i];
+
+ if (!x509cert) return NS_ERROR_FAILURE;
+
+ UniqueCERTCertificate c(x509cert->GetCert());
+ recipientCerts.set(i, c.get());
+ }
+
+ // Find a bulk key algorithm //
+ if (NSS_SMIMEUtil_FindBulkAlgForRecipients(
+ recipientCerts.getRawArray(), &bulkAlgTag, &keySize) != SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateEncrypted - can't find bulk alg for "
+ "recipients"));
+ rv = NS_ERROR_CMS_ENCRYPT_NO_BULK_ALG;
+ goto loser;
+ }
+
+ m_cmsMsg = NSS_CMSMessage_Create(nullptr);
+ if (!m_cmsMsg) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateEncrypted - can't create new cms message"));
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto loser;
+ }
+
+ if ((envd = NSS_CMSEnvelopedData_Create(m_cmsMsg, bulkAlgTag, keySize)) ==
+ nullptr) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateEncrypted - can't create enveloped data"));
+ goto loser;
+ }
+
+ cinfo = NSS_CMSMessage_GetContentInfo(m_cmsMsg);
+ if (NSS_CMSContentInfo_SetContent_EnvelopedData(m_cmsMsg, cinfo, envd) !=
+ SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateEncrypted - can't create content enveloped "
+ "data"));
+ goto loser;
+ }
+
+ cinfo = NSS_CMSEnvelopedData_GetContentInfo(envd);
+ if (NSS_CMSContentInfo_SetContent_Data(m_cmsMsg, cinfo, nullptr, false) !=
+ SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateEncrypted - can't set content data"));
+ goto loser;
+ }
+
+ // Create and attach recipient information //
+ for (i = 0; i < recipientCertCount; i++) {
+ UniqueCERTCertificate rc(recipientCerts.get(i));
+ if ((recipientInfo = NSS_CMSRecipientInfo_Create(m_cmsMsg, rc.get())) ==
+ nullptr) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateEncrypted - can't create recipient info"));
+ goto loser;
+ }
+ if (NSS_CMSEnvelopedData_AddRecipient(envd, recipientInfo) != SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateEncrypted - can't add recipient info"));
+ goto loser;
+ }
+ }
+
+ return NS_OK;
+loser:
+ if (m_cmsMsg) {
+ NSS_CMSMessage_Destroy(m_cmsMsg);
+ m_cmsMsg = nullptr;
+ }
+
+ return rv;
+}
+
+bool nsCMSMessage::IsAllowedHash(const int16_t aCryptoHashInt) {
+ switch (aCryptoHashInt) {
+ case nsICryptoHash::SHA1:
+ case nsICryptoHash::SHA256:
+ case nsICryptoHash::SHA384:
+ case nsICryptoHash::SHA512:
+ return true;
+ default:
+ return false;
+ }
+}
+
+NS_IMETHODIMP
+nsCMSMessage::CreateSigned(nsIX509Cert* aSigningCert, nsIX509Cert* aEncryptCert,
+ const nsTArray<uint8_t>& aDigestData,
+ int16_t aDigestType) {
+ NS_ENSURE_ARG(aSigningCert);
+ MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSMessage::CreateSigned"));
+ NSSCMSContentInfo* cinfo;
+ NSSCMSSignedData* sigd;
+ NSSCMSSignerInfo* signerinfo;
+ UniqueCERTCertificate scert(aSigningCert->GetCert());
+ UniqueCERTCertificate ecert;
+ nsresult rv = NS_ERROR_FAILURE;
+
+ if (!scert) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aEncryptCert) {
+ ecert = UniqueCERTCertificate(aEncryptCert->GetCert());
+ }
+
+ if (!IsAllowedHash(aDigestType)) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ SECOidTag digestType =
+ HASH_GetHashOidTagByHashType(static_cast<HASH_HashType>(aDigestType));
+ if (digestType == SEC_OID_UNKNOWN) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ /*
+ * create the message object
+ */
+ m_cmsMsg =
+ NSS_CMSMessage_Create(nullptr); /* create a message on its own pool */
+ if (!m_cmsMsg) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateSigned - can't create new message"));
+ rv = NS_ERROR_OUT_OF_MEMORY;
+ goto loser;
+ }
+
+ /*
+ * build chain of objects: message->signedData->data
+ */
+ if ((sigd = NSS_CMSSignedData_Create(m_cmsMsg)) == nullptr) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateSigned - can't create signed data"));
+ goto loser;
+ }
+ cinfo = NSS_CMSMessage_GetContentInfo(m_cmsMsg);
+ if (NSS_CMSContentInfo_SetContent_SignedData(m_cmsMsg, cinfo, sigd) !=
+ SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateSigned - can't set content signed data"));
+ goto loser;
+ }
+
+ cinfo = NSS_CMSSignedData_GetContentInfo(sigd);
+
+ /* we're always passing data in and detaching optionally */
+ if (NSS_CMSContentInfo_SetContent_Data(m_cmsMsg, cinfo, nullptr, true) !=
+ SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateSigned - can't set content data"));
+ goto loser;
+ }
+
+ /*
+ * create & attach signer information
+ */
+ signerinfo = NSS_CMSSignerInfo_Create(m_cmsMsg, scert.get(), digestType);
+ if (!signerinfo) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateSigned - can't create signer info"));
+ goto loser;
+ }
+
+ /* we want the cert chain included for this one */
+ if (NSS_CMSSignerInfo_IncludeCerts(signerinfo, NSSCMSCM_CertChain,
+ certUsageEmailSigner) != SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateSigned - can't include signer cert chain"));
+ goto loser;
+ }
+
+ if (NSS_CMSSignerInfo_AddSigningTime(signerinfo, PR_Now()) != SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateSigned - can't add signing time"));
+ goto loser;
+ }
+
+ if (NSS_CMSSignerInfo_AddSMIMECaps(signerinfo) != SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateSigned - can't add smime caps"));
+ goto loser;
+ }
+
+ if (ecert) {
+ if (NSS_CMSSignerInfo_AddSMIMEEncKeyPrefs(
+ signerinfo, ecert.get(), CERT_GetDefaultCertDB()) != SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateSigned - can't add smime enc key prefs"));
+ goto loser;
+ }
+
+ if (NSS_CMSSignerInfo_AddMSSMIMEEncKeyPrefs(
+ signerinfo, ecert.get(), CERT_GetDefaultCertDB()) != SECSuccess) {
+ MOZ_LOG(
+ gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateSigned - can't add MS smime enc key prefs"));
+ goto loser;
+ }
+
+ // If signing and encryption cert are identical, don't add it twice.
+ bool addEncryptionCert =
+ (ecert && (!scert || !CERT_CompareCerts(ecert.get(), scert.get())));
+
+ if (addEncryptionCert &&
+ NSS_CMSSignedData_AddCertificate(sigd, ecert.get()) != SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateSigned - can't add own encryption "
+ "certificate"));
+ goto loser;
+ }
+ }
+
+ if (NSS_CMSSignedData_AddSignerInfo(sigd, signerinfo) != SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateSigned - can't add signer info"));
+ goto loser;
+ }
+
+ // Finally, add the pre-computed digest if passed in
+ if (!aDigestData.IsEmpty()) {
+ SECItem digest;
+
+ // NSS_CMSSignedData_SetDigestValue() takes a copy and won't mutate our
+ // data, so we're OK to cast away the const here.
+ digest.data = const_cast<uint8_t*>(aDigestData.Elements());
+ digest.len = aDigestData.Length();
+ if (NSS_CMSSignedData_SetDigestValue(sigd, digestType, &digest) !=
+ SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSMessage::CreateSigned - can't set digest value"));
+ goto loser;
+ }
+ }
+
+ return NS_OK;
+loser:
+ if (m_cmsMsg) {
+ NSS_CMSMessage_Destroy(m_cmsMsg);
+ m_cmsMsg = nullptr;
+ }
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsCMSDecoder, nsICMSDecoder)
+NS_IMPL_ISUPPORTS(nsCMSDecoderJS, nsICMSDecoderJS)
+
+nsCMSDecoder::nsCMSDecoder() : m_dcx(nullptr) {}
+nsCMSDecoderJS::nsCMSDecoderJS() : m_dcx(nullptr) {}
+
+nsCMSDecoder::~nsCMSDecoder() {
+ if (m_dcx) {
+ NSS_CMSDecoder_Cancel(m_dcx);
+ m_dcx = nullptr;
+ }
+}
+
+nsCMSDecoderJS::~nsCMSDecoderJS() {
+ if (m_dcx) {
+ NSS_CMSDecoder_Cancel(m_dcx);
+ m_dcx = nullptr;
+ }
+}
+
+nsresult nsCMSDecoder::Init() {
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssInitialized =
+ do_GetService("@mozilla.org/psm;1", &rv);
+ return rv;
+}
+
+nsresult nsCMSDecoderJS::Init() {
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssInitialized =
+ do_GetService("@mozilla.org/psm;1", &rv);
+ return rv;
+}
+
+/* void start (in NSSCMSContentCallback cb, in voidPtr arg); */
+NS_IMETHODIMP nsCMSDecoder::Start(NSSCMSContentCallback cb, void* arg) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSDecoder::Start"));
+ m_ctx = new PipUIContext();
+
+ m_dcx = NSS_CMSDecoder_Start(0, cb, arg, 0, m_ctx, 0, 0);
+ if (!m_dcx) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSDecoder::Start - can't start decoder"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+/* void update (in string bug, in long len); */
+NS_IMETHODIMP nsCMSDecoder::Update(const char* buf, int32_t len) {
+ NSS_CMSDecoder_Update(m_dcx, (char*)buf, len);
+ return NS_OK;
+}
+
+/* void finish (); */
+NS_IMETHODIMP nsCMSDecoder::Finish(nsICMSMessage** aCMSMsg) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSDecoder::Finish"));
+ NSSCMSMessage* cmsMsg;
+ cmsMsg = NSS_CMSDecoder_Finish(m_dcx);
+ m_dcx = nullptr;
+ if (cmsMsg) {
+ nsCMSMessage* obj = new nsCMSMessage(cmsMsg);
+ // The NSS object cmsMsg still carries a reference to the context
+ // we gave it on construction.
+ // Make sure the context will live long enough.
+ obj->referenceContext(m_ctx);
+ NS_ADDREF(*aCMSMsg = obj);
+ }
+ return NS_OK;
+}
+
+void nsCMSDecoderJS::content_callback(void* arg, const char* input,
+ unsigned long length) {
+ nsCMSDecoderJS* self = reinterpret_cast<nsCMSDecoderJS*>(arg);
+ self->mDecryptedData.AppendElements(input, length);
+}
+
+NS_IMETHODIMP nsCMSDecoderJS::Decrypt(const nsTArray<uint8_t>& aInput,
+ nsTArray<uint8_t>& _retval) {
+ if (aInput.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ m_ctx = new PipUIContext();
+
+ m_dcx = NSS_CMSDecoder_Start(0, nsCMSDecoderJS::content_callback, this, 0,
+ m_ctx, 0, 0);
+ if (!m_dcx) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSDecoderJS::Start - can't start decoder"));
+ return NS_ERROR_FAILURE;
+ }
+
+ NSS_CMSDecoder_Update(m_dcx, (char*)aInput.Elements(), aInput.Length());
+
+ NSSCMSMessage* cmsMsg;
+ cmsMsg = NSS_CMSDecoder_Finish(m_dcx);
+ m_dcx = nullptr;
+ if (cmsMsg) {
+ nsCMSMessage* obj = new nsCMSMessage(cmsMsg);
+ // The NSS object cmsMsg still carries a reference to the context
+ // we gave it on construction.
+ // Make sure the context will live long enough.
+ obj->referenceContext(m_ctx);
+ mCMSMessage = obj;
+ }
+
+ _retval = mDecryptedData.Clone();
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(nsCMSEncoder, nsICMSEncoder)
+
+nsCMSEncoder::nsCMSEncoder() : m_ecx(nullptr) {}
+
+nsCMSEncoder::~nsCMSEncoder() {
+ if (m_ecx) NSS_CMSEncoder_Cancel(m_ecx);
+}
+
+nsresult nsCMSEncoder::Init() {
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssInitialized =
+ do_GetService("@mozilla.org/psm;1", &rv);
+ return rv;
+}
+
+/* void start (); */
+NS_IMETHODIMP nsCMSEncoder::Start(nsICMSMessage* aMsg, NSSCMSContentCallback cb,
+ void* arg) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSEncoder::Start"));
+ nsCMSMessage* cmsMsg = static_cast<nsCMSMessage*>(aMsg);
+ m_ctx = new PipUIContext();
+
+ m_ecx = NSS_CMSEncoder_Start(cmsMsg->getCMS(), cb, arg, 0, 0, 0, m_ctx, 0, 0,
+ 0, 0);
+ if (!m_ecx) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSEncoder::Start - can't start encoder"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+/* void update (in string aBuf, in long aLen); */
+NS_IMETHODIMP nsCMSEncoder::Update(const char* aBuf, int32_t aLen) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSEncoder::Update"));
+ if (!m_ecx || NSS_CMSEncoder_Update(m_ecx, aBuf, aLen) != SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSEncoder::Update - can't update encoder"));
+ return NS_ERROR_FAILURE;
+ }
+ return NS_OK;
+}
+
+/* void finish (); */
+NS_IMETHODIMP nsCMSEncoder::Finish() {
+ nsresult rv = NS_OK;
+ MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSEncoder::Finish"));
+ if (!m_ecx || NSS_CMSEncoder_Finish(m_ecx) != SECSuccess) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug,
+ ("nsCMSEncoder::Finish - can't finish encoder"));
+ rv = NS_ERROR_FAILURE;
+ }
+ m_ecx = nullptr;
+ return rv;
+}
+
+/* void encode (in nsICMSMessage aMsg); */
+NS_IMETHODIMP nsCMSEncoder::Encode(nsICMSMessage* aMsg) {
+ MOZ_LOG(gCMSLog, LogLevel::Debug, ("nsCMSEncoder::Encode"));
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/comm/mailnews/extensions/smime/nsCMS.h b/comm/mailnews/extensions/smime/nsCMS.h
new file mode 100644
index 0000000000..14ed2cd07f
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsCMS.h
@@ -0,0 +1,123 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef __NS_CMS_H__
+#define __NS_CMS_H__
+
+#include "nsISupports.h"
+#include "nsCOMPtr.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsICMSMessage.h"
+#include "nsICMSEncoder.h"
+#include "nsICMSDecoder.h"
+#include "nsICMSDecoderJS.h"
+#include "sechash.h"
+#include "cms.h"
+
+#define NS_CMSMESSAGE_CID \
+ { \
+ 0xa4557478, 0xae16, 0x11d5, { \
+ 0xba, 0x4b, 0x00, 0x10, 0x83, 0x03, 0xb1, 0x17 \
+ } \
+ }
+
+class nsCMSMessage : public nsICMSMessage {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICMSMESSAGE
+
+ nsCMSMessage();
+ explicit nsCMSMessage(NSSCMSMessage* aCMSMsg);
+ nsresult Init();
+
+ void referenceContext(nsIInterfaceRequestor* aContext) { m_ctx = aContext; }
+ NSSCMSMessage* getCMS() { return m_cmsMsg; }
+
+ private:
+ virtual ~nsCMSMessage();
+ nsCOMPtr<nsIInterfaceRequestor> m_ctx;
+ NSSCMSMessage* m_cmsMsg;
+ NSSCMSSignerInfo* GetTopLevelSignerInfo();
+ nsresult CommonVerifySignature(int32_t verifyFlags,
+ const nsTArray<uint8_t>& aDigestData,
+ int16_t aDigestType);
+
+ nsresult CommonAsyncVerifySignature(int32_t verifyFlags,
+ nsISMimeVerificationListener* aListener,
+ const nsTArray<uint8_t>& aDigestData,
+ int16_t aDigestType);
+ bool IsAllowedHash(const int16_t aCryptoHashInt);
+};
+
+// ===============================================
+// nsCMSDecoder - implementation of nsICMSDecoder
+// ===============================================
+
+#define NS_CMSDECODER_CID \
+ { \
+ 0x9dcef3a4, 0xa3bc, 0x11d5, { \
+ 0xba, 0x47, 0x00, 0x10, 0x83, 0x03, 0xb1, 0x17 \
+ } \
+ }
+
+class nsCMSDecoder : public nsICMSDecoder {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICMSDECODER
+
+ nsCMSDecoder();
+ nsresult Init();
+
+ private:
+ virtual ~nsCMSDecoder();
+ nsCOMPtr<nsIInterfaceRequestor> m_ctx;
+ NSSCMSDecoderContext* m_dcx;
+};
+
+class nsCMSDecoderJS : public nsICMSDecoderJS {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICMSDECODERJS
+
+ nsCMSDecoderJS();
+ nsresult Init();
+
+ private:
+ virtual ~nsCMSDecoderJS();
+ nsCOMPtr<nsIInterfaceRequestor> m_ctx;
+ NSSCMSDecoderContext* m_dcx;
+
+ nsTArray<uint8_t> mDecryptedData;
+ nsCOMPtr<nsICMSMessage> mCMSMessage;
+
+ static void content_callback(void* arg, const char* input,
+ unsigned long length);
+};
+
+// ===============================================
+// nsCMSEncoder - implementation of nsICMSEncoder
+// ===============================================
+
+#define NS_CMSENCODER_CID \
+ { \
+ 0xa15789aa, 0x8903, 0x462b, { \
+ 0x81, 0xe9, 0x4a, 0xa2, 0xcf, 0xf4, 0xd5, 0xcb \
+ } \
+ }
+class nsCMSEncoder : public nsICMSEncoder {
+ public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSICMSENCODER
+
+ nsCMSEncoder();
+ nsresult Init();
+
+ private:
+ virtual ~nsCMSEncoder();
+ nsCOMPtr<nsIInterfaceRequestor> m_ctx;
+ NSSCMSEncoderContext* m_ecx;
+};
+
+#endif
diff --git a/comm/mailnews/extensions/smime/nsCMSSecureMessage.cpp b/comm/mailnews/extensions/smime/nsCMSSecureMessage.cpp
new file mode 100644
index 0000000000..c2c220a2f4
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsCMSSecureMessage.cpp
@@ -0,0 +1,92 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsCMSSecureMessage.h"
+
+#include <string.h>
+
+#include "ScopedNSSTypes.h"
+#include "SharedCertVerifier.h"
+#include "cms.h"
+#include "mozilla/Logging.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsDependentSubstring.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsServiceManagerUtils.h"
+#include "nsISupports.h"
+#include "nsIX509Cert.h"
+#include "nsIX509CertDB.h"
+#include "nsNSSComponent.h"
+#include "nsNSSHelper.h"
+#include "plbase64.h"
+
+using namespace mozilla;
+using namespace mozilla::psm;
+
+// Standard ISupports implementation
+// NOTE: Should these be the thread-safe versions?
+
+/*****
+ * nsCMSSecureMessage
+ *****/
+
+// Standard ISupports implementation
+NS_IMPL_ISUPPORTS(nsCMSSecureMessage, nsICMSSecureMessage)
+
+// nsCMSSecureMessage constructor
+nsCMSSecureMessage::nsCMSSecureMessage() {
+ // initialize superclass
+}
+
+// nsCMSMessage destructor
+nsCMSSecureMessage::~nsCMSSecureMessage() {}
+
+nsresult nsCMSSecureMessage::Init() {
+ nsresult rv;
+ nsCOMPtr<nsISupports> nssInitialized =
+ do_GetService("@mozilla.org/psm;1", &rv);
+ return rv;
+}
+
+nsresult nsCMSSecureMessage::CheckUsageOk(nsIX509Cert* aCert,
+ SECCertificateUsage aUsage,
+ bool* aCanBeUsed) {
+ NS_ENSURE_ARG_POINTER(aCert);
+ *aCanBeUsed = false;
+
+ nsresult rv;
+ nsCOMPtr<nsIX509CertDB> certdb =
+ do_GetService("@mozilla.org/security/x509certdb;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsTArray<uint8_t> certBytes;
+ rv = aCert->GetRawDER(certBytes);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+ NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
+
+ nsTArray<nsTArray<uint8_t>> unusedBuiltChain;
+ // It's fine to skip OCSP, because this is called only from code
+ // for selecting the user's own configured cert.
+ if (certVerifier->VerifyCert(certBytes, aUsage, mozilla::pkix::Now(), nullptr,
+ nullptr, unusedBuiltChain,
+ CertVerifier::FLAG_LOCAL_ONLY) ==
+ mozilla::pkix::Success) {
+ *aCanBeUsed = true;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsCMSSecureMessage::CanBeUsedForEmailEncryption(
+ nsIX509Cert* aCert, bool* aCanBeUsed) {
+ return CheckUsageOk(aCert, certificateUsageEmailRecipient, aCanBeUsed);
+}
+
+NS_IMETHODIMP nsCMSSecureMessage::CanBeUsedForEmailSigning(nsIX509Cert* aCert,
+ bool* aCanBeUsed) {
+ return CheckUsageOk(aCert, certificateUsageEmailSigner, aCanBeUsed);
+}
diff --git a/comm/mailnews/extensions/smime/nsCMSSecureMessage.h b/comm/mailnews/extensions/smime/nsCMSSecureMessage.h
new file mode 100644
index 0000000000..186dba525f
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsCMSSecureMessage.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCMSSecureMessage_h
+#define nsCMSSecureMessage_h
+
+#include "nsICMSSecureMessage.h"
+#include "cert.h"
+
+// ===============================================
+// nsCMSManager - implementation of nsICMSManager
+// ===============================================
+
+#define NS_CMSSECUREMESSAGE_CID \
+ { \
+ 0x5fb907e0, 0x1dd2, 0x11b2, { \
+ 0xa7, 0xc0, 0xf1, 0x4c, 0x41, 0x6a, 0x62, 0xa1 \
+ } \
+ }
+
+class nsCMSSecureMessage : public nsICMSSecureMessage {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICMSSECUREMESSAGE
+
+ nsCMSSecureMessage();
+ nsresult Init();
+
+ private:
+ virtual ~nsCMSSecureMessage();
+ nsresult encode(const unsigned char* data, int32_t dataLen, char** _retval);
+ nsresult decode(const char* data, unsigned char** result, int32_t* _retval);
+ nsresult CheckUsageOk(nsIX509Cert* cert, SECCertificateUsage usage,
+ bool* _retval);
+};
+
+#endif // nsCMSSecureMessage_h
diff --git a/comm/mailnews/extensions/smime/nsCertPicker.cpp b/comm/mailnews/extensions/smime/nsCertPicker.cpp
new file mode 100644
index 0000000000..7224762eef
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsCertPicker.cpp
@@ -0,0 +1,410 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsCertPicker.h"
+
+#include "MainThreadUtils.h"
+#include "ScopedNSSTypes.h"
+#include "cert.h"
+#include "mozilla/RefPtr.h"
+#include "nsCOMPtr.h"
+#include "nsICertPickDialogs.h"
+#include "nsIDialogParamBlock.h"
+#include "nsIInterfaceRequestor.h"
+#include "nsIX509CertValidity.h"
+#include "nsMemory.h"
+#include "nsMsgComposeSecure.h"
+#include "nsNSSCertificate.h"
+#include "nsNSSComponent.h"
+#include "nsNSSDialogHelper.h"
+#include "nsNSSHelper.h"
+#include "nsNSSCertHelper.h"
+#include "nsReadableUtils.h"
+#include "nsComponentManagerUtils.h" // for do_CreateInstance
+#include "nsString.h"
+#include "mozpkix/pkixtypes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Unused.h"
+#include "mozilla/intl/AppDateTimeFormat.h"
+
+using namespace mozilla;
+
+// Copied from security/manager/ssl/nsCertTree.cpp
+static void PRTimeToLocalDateString(PRTime time, nsAString& result) {
+ PRExplodedTime explodedTime;
+ PR_ExplodeTime(time, PR_LocalTimeParameters, &explodedTime);
+ mozilla::intl::DateTimeFormat::StyleBag style;
+ style.date = mozilla::Some(mozilla::intl::DateTimeFormat::Style::Long);
+ style.time = mozilla::Nothing();
+ mozilla::Unused << intl::AppDateTimeFormat::Format(style, &explodedTime,
+ result);
+}
+
+MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueCERTCertNicknames,
+ CERTCertNicknames, CERT_FreeNicknames)
+
+CERTCertNicknames* getNSSCertNicknamesFromCertList(
+ const UniqueCERTCertList& certList) {
+ nsAutoString expiredString, notYetValidString;
+ nsAutoString expiredStringLeadingSpace, notYetValidStringLeadingSpace;
+
+ GetPIPNSSBundleString("NicknameExpired", expiredString);
+ GetPIPNSSBundleString("NicknameNotYetValid", notYetValidString);
+
+ expiredStringLeadingSpace.Append(' ');
+ expiredStringLeadingSpace.Append(expiredString);
+
+ notYetValidStringLeadingSpace.Append(' ');
+ notYetValidStringLeadingSpace.Append(notYetValidString);
+
+ NS_ConvertUTF16toUTF8 aUtf8ExpiredString(expiredStringLeadingSpace);
+ NS_ConvertUTF16toUTF8 aUtf8NotYetValidString(notYetValidStringLeadingSpace);
+
+ return CERT_NicknameStringsFromCertList(
+ certList.get(), const_cast<char*>(aUtf8ExpiredString.get()),
+ const_cast<char*>(aUtf8NotYetValidString.get()));
+}
+
+nsresult FormatUIStrings(nsIX509Cert* cert, const nsAutoString& nickname,
+ nsAutoString& nickWithSerial, nsAutoString& details) {
+ if (!NS_IsMainThread()) {
+ NS_ERROR("nsNSSCertificate::FormatUIStrings called off the main thread");
+ return NS_ERROR_NOT_SAME_THREAD;
+ }
+
+ RefPtr<nsMsgComposeSecure> mcs = new nsMsgComposeSecure;
+ if (!mcs) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString info;
+ nsAutoString temp1;
+
+ nickWithSerial.Append(nickname);
+
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoIssuedFor", info))) {
+ details.Append(info);
+ details.Append(char16_t(' '));
+ if (NS_SUCCEEDED(cert->GetSubjectName(temp1)) && !temp1.IsEmpty()) {
+ details.Append(temp1);
+ }
+ details.Append(char16_t('\n'));
+ }
+
+ if (NS_SUCCEEDED(cert->GetSerialNumber(temp1)) && !temp1.IsEmpty()) {
+ details.AppendLiteral(" ");
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertDumpSerialNo", info))) {
+ details.Append(info);
+ details.AppendLiteral(": ");
+ }
+ details.Append(temp1);
+
+ nickWithSerial.AppendLiteral(" [");
+ nickWithSerial.Append(temp1);
+ nickWithSerial.Append(char16_t(']'));
+
+ details.Append(char16_t('\n'));
+ }
+
+ nsCOMPtr<nsIX509CertValidity> validity;
+ nsresult rv = cert->GetValidity(getter_AddRefs(validity));
+ if (NS_SUCCEEDED(rv) && validity) {
+ details.AppendLiteral(" ");
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoValid", info))) {
+ details.Append(info);
+ }
+
+ PRTime notBefore;
+ rv = validity->GetNotBefore(&notBefore);
+ if (NS_SUCCEEDED(rv)) {
+ details.Append(char16_t(' '));
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoFrom", info))) {
+ details.Append(info);
+ details.Append(char16_t(' '));
+ }
+ PRTimeToLocalDateString(notBefore, temp1);
+ details.Append(temp1);
+ }
+
+ PRTime notAfter;
+ rv = validity->GetNotAfter(&notAfter);
+ if (NS_SUCCEEDED(rv)) {
+ details.Append(char16_t(' '));
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoTo", info))) {
+ details.Append(info);
+ details.Append(char16_t(' '));
+ }
+ PRTimeToLocalDateString(notAfter, temp1);
+ details.Append(temp1);
+ }
+ details.Append(char16_t('\n'));
+ }
+
+ UniqueCERTCertificate nssCert(cert->GetCert());
+ if (!nssCert) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString firstEmail;
+ const char* aWalkAddr;
+ for (aWalkAddr = CERT_GetFirstEmailAddress(nssCert.get()); aWalkAddr;
+ aWalkAddr = CERT_GetNextEmailAddress(nssCert.get(), aWalkAddr)) {
+ NS_ConvertUTF8toUTF16 email(aWalkAddr);
+ if (email.IsEmpty()) continue;
+
+ if (firstEmail.IsEmpty()) {
+ // If the first email address from the subject DN is also present
+ // in the subjectAltName extension, GetEmailAddresses() will return
+ // it twice (as received from NSS). Remember the first address so that
+ // we can filter out duplicates later on.
+ firstEmail = email;
+
+ details.AppendLiteral(" ");
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoEmail", info))) {
+ details.Append(info);
+ details.AppendLiteral(": ");
+ }
+ details.Append(email);
+ } else {
+ // Append current address if it's different from the first one.
+ if (!firstEmail.Equals(email)) {
+ details.AppendLiteral(", ");
+ details.Append(email);
+ }
+ }
+ }
+
+ if (!firstEmail.IsEmpty()) {
+ // We got at least one email address, so we want a newline
+ details.Append(char16_t('\n'));
+ }
+
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoIssuedBy", info))) {
+ details.Append(info);
+ details.Append(char16_t(' '));
+
+ if (NS_SUCCEEDED(cert->GetIssuerName(temp1)) && !temp1.IsEmpty()) {
+ details.Append(temp1);
+ }
+
+ details.Append(char16_t('\n'));
+ }
+
+ if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoStoredIn", info))) {
+ details.Append(info);
+ details.Append(char16_t(' '));
+
+ if (NS_SUCCEEDED(cert->GetTokenName(temp1)) && !temp1.IsEmpty()) {
+ details.Append(temp1);
+ }
+ }
+
+ // the above produces the following output:
+ //
+ // Issued to: $subjectName
+ // Serial number: $serialNumber
+ // Valid from: $starting_date to $expiration_date
+ // Certificate Key usage: $usages
+ // Email: $address(es)
+ // Issued by: $issuerName
+ // Stored in: $token
+
+ return rv;
+}
+
+NS_IMPL_ISUPPORTS(nsCertPicker, nsICertPickDialogs, nsIUserCertPicker)
+
+nsCertPicker::nsCertPicker() {}
+
+nsCertPicker::~nsCertPicker() {}
+
+nsresult nsCertPicker::Init() {
+ nsresult rv;
+ nsCOMPtr<nsISupports> psm = do_GetService("@mozilla.org/psm;1", &rv);
+ return rv;
+}
+
+NS_IMETHODIMP
+nsCertPicker::PickCertificate(nsIInterfaceRequestor* ctx,
+ const nsTArray<nsString>& certNickList,
+ const nsTArray<nsString>& certDetailsList,
+ int32_t* selectedIndex, bool* canceled) {
+ nsresult rv;
+ uint32_t i;
+ MOZ_ASSERT(certNickList.Length() == certDetailsList.Length());
+ const uint32_t count = certNickList.Length();
+
+ *canceled = false;
+
+ nsCOMPtr<nsIDialogParamBlock> block =
+ do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID);
+ if (!block) return NS_ERROR_FAILURE;
+
+ block->SetNumberStrings(1 + count * 2);
+
+ for (i = 0; i < count; i++) {
+ rv = block->SetString(i, ToNewUnicode(certNickList[i]));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ for (i = 0; i < count; i++) {
+ rv = block->SetString(i + count, ToNewUnicode(certDetailsList[i]));
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ rv = block->SetInt(0, count);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = block->SetInt(1, *selectedIndex);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = nsNSSDialogHelper::openDialog(
+ nullptr, "chrome://messenger/content/certpicker.xhtml", block);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ int32_t status;
+
+ rv = block->GetInt(0, &status);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ *canceled = (status == 0) ? true : false;
+ if (!*canceled) {
+ rv = block->GetInt(1, selectedIndex);
+ }
+ return rv;
+}
+
+NS_IMETHODIMP nsCertPicker::PickByUsage(nsIInterfaceRequestor* ctx,
+ const char16_t* selectedNickname,
+ int32_t certUsage, bool allowInvalid,
+ bool allowDuplicateNicknames,
+ const nsAString& emailAddress,
+ bool* canceled, nsIX509Cert** _retval) {
+ int32_t selectedIndex = -1;
+ bool selectionFound = false;
+ CERTCertListNode* node = nullptr;
+ nsresult rv = NS_OK;
+
+ {
+ // Iterate over all certs. This assures that user is logged in to all
+ // hardware tokens.
+ nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
+ UniqueCERTCertList allcerts(PK11_ListCerts(PK11CertListUnique, ctx));
+ }
+
+ /* find all user certs that are valid for the specified usage */
+ /* note that we are allowing expired certs in this list */
+ UniqueCERTCertList certList(CERT_FindUserCertsByUsage(
+ CERT_GetDefaultCertDB(), (SECCertUsage)certUsage,
+ !allowDuplicateNicknames, !allowInvalid, ctx));
+ if (!certList) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ /* if a (non-empty) emailAddress argument is supplied to PickByUsage, */
+ /* remove non-matching certificates from the candidate list */
+
+ if (!emailAddress.IsEmpty()) {
+ node = CERT_LIST_HEAD(certList);
+ while (!CERT_LIST_END(node, certList)) {
+ /* if the cert has at least one e-mail address, check if suitable */
+ if (CERT_GetFirstEmailAddress(node->cert)) {
+ RefPtr<nsNSSCertificate> tempCert(new nsNSSCertificate(node->cert));
+ bool match = false;
+ rv = tempCert->ContainsEmailAddress(emailAddress, &match);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ if (!match) {
+ /* doesn't contain the specified address, so remove from the list */
+ CERTCertListNode* freenode = node;
+ node = CERT_LIST_NEXT(node);
+ CERT_RemoveCertListNode(freenode);
+ continue;
+ }
+ }
+ node = CERT_LIST_NEXT(node);
+ }
+ }
+
+ UniqueCERTCertNicknames nicknames(getNSSCertNicknamesFromCertList(certList));
+ if (!nicknames) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsTArray<nsString> certNicknameList(nicknames->numnicknames);
+ nsTArray<nsString> certDetailsList(nicknames->numnicknames);
+
+ int32_t CertsToUse;
+
+ for (CertsToUse = 0, node = CERT_LIST_HEAD(certList.get());
+ !CERT_LIST_END(node, certList.get()) &&
+ CertsToUse < nicknames->numnicknames;
+ node = CERT_LIST_NEXT(node)) {
+ RefPtr<nsNSSCertificate> tempCert(new nsNSSCertificate(node->cert));
+
+ if (tempCert) {
+ nsAutoString i_nickname(
+ NS_ConvertUTF8toUTF16(nicknames->nicknames[CertsToUse]));
+ nsAutoString nickWithSerial;
+ nsAutoString details;
+
+ if (!selectionFound) {
+ /* for the case when selectedNickname refers to a bare nickname */
+ if (i_nickname == nsDependentString(selectedNickname)) {
+ selectedIndex = CertsToUse;
+ selectionFound = true;
+ }
+ }
+
+ if (NS_SUCCEEDED(
+ FormatUIStrings(tempCert, i_nickname, nickWithSerial, details))) {
+ certNicknameList.AppendElement(nickWithSerial);
+ certDetailsList.AppendElement(details);
+ if (!selectionFound) {
+ /* for the case when selectedNickname refers to nickname + serial */
+ if (nickWithSerial == nsDependentString(selectedNickname)) {
+ selectedIndex = CertsToUse;
+ selectionFound = true;
+ }
+ }
+ } else {
+ // Placeholder, to keep the indexes valid.
+ certNicknameList.AppendElement(u""_ns);
+ certDetailsList.AppendElement(u""_ns);
+ }
+
+ ++CertsToUse;
+ }
+ }
+
+ if (certNicknameList.IsEmpty()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ nsCOMPtr<nsICertPickDialogs> dialogs;
+ rv = getNSSDialogs(getter_AddRefs(dialogs), NS_GET_IID(nsICertPickDialogs),
+ NS_CERTPICKDIALOGS_CONTRACTID);
+
+ if (NS_SUCCEEDED(rv)) {
+ // Show the cert picker dialog and get the index of the selected cert.
+ rv = dialogs->PickCertificate(ctx, certNicknameList, certDetailsList,
+ &selectedIndex, canceled);
+ }
+
+ if (NS_SUCCEEDED(rv) && !*canceled) {
+ int32_t i;
+ for (i = 0, node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList);
+ ++i, node = CERT_LIST_NEXT(node)) {
+ if (i == selectedIndex) {
+ RefPtr<nsNSSCertificate> cert = new nsNSSCertificate(node->cert);
+ cert.forget(_retval);
+ break;
+ }
+ }
+ }
+
+ return rv;
+}
diff --git a/comm/mailnews/extensions/smime/nsCertPicker.h b/comm/mailnews/extensions/smime/nsCertPicker.h
new file mode 100644
index 0000000000..04b5270eda
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsCertPicker.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsCertPicker_h
+#define nsCertPicker_h
+
+#include "nsICertPickDialogs.h"
+#include "nsIUserCertPicker.h"
+
+#define NS_CERT_PICKER_CID \
+ { \
+ 0x735959a1, 0xaf01, 0x447e, { \
+ 0xb0, 0x2d, 0x56, 0xe9, 0x68, 0xfa, 0x52, 0xb4 \
+ } \
+ }
+
+class nsCertPicker : public nsICertPickDialogs, public nsIUserCertPicker {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICERTPICKDIALOGS
+ NS_DECL_NSIUSERCERTPICKER
+
+ nsCertPicker();
+ nsresult Init();
+
+ protected:
+ virtual ~nsCertPicker();
+};
+
+#endif // nsCertPicker_h
diff --git a/comm/mailnews/extensions/smime/nsEncryptedSMIMEURIsService.cpp b/comm/mailnews/extensions/smime/nsEncryptedSMIMEURIsService.cpp
new file mode 100644
index 0000000000..ecd428f29b
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsEncryptedSMIMEURIsService.cpp
@@ -0,0 +1,32 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsEncryptedSMIMEURIsService.h"
+
+NS_IMPL_ISUPPORTS(nsEncryptedSMIMEURIsService, nsIEncryptedSMIMEURIsService)
+
+nsEncryptedSMIMEURIsService::nsEncryptedSMIMEURIsService() {}
+
+nsEncryptedSMIMEURIsService::~nsEncryptedSMIMEURIsService() {}
+
+NS_IMETHODIMP nsEncryptedSMIMEURIsService::RememberEncrypted(
+ const nsACString& uri) {
+ // Assuming duplicates are allowed.
+ mEncryptedURIs.AppendElement(uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsEncryptedSMIMEURIsService::ForgetEncrypted(
+ const nsACString& uri) {
+ // Assuming, this will only remove one copy of the string, if the array
+ // contains multiple copies of the same string.
+ mEncryptedURIs.RemoveElement(uri);
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsEncryptedSMIMEURIsService::IsEncrypted(const nsACString& uri,
+ bool* _retval) {
+ *_retval = mEncryptedURIs.Contains(uri);
+ return NS_OK;
+}
diff --git a/comm/mailnews/extensions/smime/nsEncryptedSMIMEURIsService.h b/comm/mailnews/extensions/smime/nsEncryptedSMIMEURIsService.h
new file mode 100644
index 0000000000..dfce321f52
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsEncryptedSMIMEURIsService.h
@@ -0,0 +1,24 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#ifndef _nsEncryptedSMIMEURIsService_H_
+#define _nsEncryptedSMIMEURIsService_H_
+
+#include "nsIEncryptedSMIMEURIsSrvc.h"
+#include "nsTArray.h"
+#include "nsString.h"
+
+class nsEncryptedSMIMEURIsService : public nsIEncryptedSMIMEURIsService {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIENCRYPTEDSMIMEURISSERVICE
+
+ nsEncryptedSMIMEURIsService();
+
+ protected:
+ virtual ~nsEncryptedSMIMEURIsService();
+ nsTArray<nsCString> mEncryptedURIs;
+};
+
+#endif
diff --git a/comm/mailnews/extensions/smime/nsICMSDecoder.idl b/comm/mailnews/extensions/smime/nsICMSDecoder.idl
new file mode 100644
index 0000000000..5764707c59
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsICMSDecoder.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+typedef void (*NSSCMSContentCallback)(void *arg, const char *buf, unsigned long len);
+
+#define NS_CMSDECODER_CONTRACTID "@mozilla.org/nsCMSDecoder;1"
+%}
+
+native NSSCMSContentCallback(NSSCMSContentCallback);
+
+interface nsICMSMessage;
+
+/**
+ * nsICMSDecoder
+ * Streaming interface to decode an CMS message, the input data may be
+ * passed in chunks. Cannot be called from JS.
+ */
+[uuid(c7c7033b-f341-4065-aadd-7eef55ce0dda)]
+interface nsICMSDecoder : nsISupports
+{
+ void start(in NSSCMSContentCallback cb, in voidPtr arg);
+ void update(in string aBuf, in long aLen);
+ void finish(out nsICMSMessage msg);
+};
diff --git a/comm/mailnews/extensions/smime/nsICMSDecoderJS.idl b/comm/mailnews/extensions/smime/nsICMSDecoderJS.idl
new file mode 100644
index 0000000000..83ff66e12d
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsICMSDecoderJS.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsICMSMessage;
+
+/**
+ * nsICMSDecoderJS
+ * Interface to decode a CMS message, can be called from JavaScript.
+ */
+[scriptable,uuid(7e0a6708-17f4-4573-8115-1ca14f1442e0)]
+interface nsICMSDecoderJS : nsISupports
+{
+ /**
+ * Return the result of decoding/decrypting the given CMS message.
+ *
+ * @param aInput The bytes of a CMS message.
+ * @return The decoded data
+ */
+ Array<uint8_t> decrypt(in Array<uint8_t> input);
+};
diff --git a/comm/mailnews/extensions/smime/nsICMSEncoder.idl b/comm/mailnews/extensions/smime/nsICMSEncoder.idl
new file mode 100644
index 0000000000..23fe086acb
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsICMSEncoder.idl
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+%{ C++
+typedef void (*NSSCMSContentCallback)(void *arg, const char *buf, unsigned long len);
+
+#define NS_CMSENCODER_CONTRACTID "@mozilla.org/nsCMSEncoder;1"
+%}
+
+native NSSCMSContentCallback(NSSCMSContentCallback);
+
+interface nsICMSMessage;
+
+/**
+ * nsICMSEncoder
+ * Interface to Encode an CMS message
+ */
+[uuid(17dc4fb4-e379-4e56-a4a4-57cdcc74816f)]
+interface nsICMSEncoder : nsISupports
+{
+ void start(in nsICMSMessage aMsg, in NSSCMSContentCallback cb, in voidPtr arg);
+ void update(in string aBuf, in long aLen);
+ void finish();
+ void encode(in nsICMSMessage aMsg);
+};
diff --git a/comm/mailnews/extensions/smime/nsICMSMessage.idl b/comm/mailnews/extensions/smime/nsICMSMessage.idl
new file mode 100644
index 0000000000..ed863e5418
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsICMSMessage.idl
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsISMimeVerificationListener;
+
+%{ C++
+#define NS_CMSMESSAGE_CONTRACTID "@mozilla.org/nsCMSMessage;1"
+%}
+
+/*
+ * At the time the ptr type is eliminated in both interfaces, both should be
+ * made scriptable.
+ */
+[ptr] native UnsignedCharPtr(unsigned char);
+
+interface nsIX509Cert;
+
+[uuid(cd76ec81-02f0-41a3-8852-c0acce0bab53)]
+interface nsICMSVerifyFlags : nsISupports
+{
+ const long NONE = 0;
+ const long VERIFY_ALLOW_WEAK_SHA1 = 1 << 0;
+};
+
+/**
+ * nsICMSMessage
+ * Interface to a CMS Message
+ */
+[uuid(c6d51c22-73e9-4dad-86b9-bde584e33c63)]
+interface nsICMSMessage : nsISupports
+{
+ void contentIsSigned(out boolean aSigned);
+ void contentIsEncrypted(out boolean aEncrypted);
+ void getSignerCommonName(out string aName);
+ void getSignerEmailAddress(out string aEmail);
+ void getSignerCert(out nsIX509Cert scert);
+ void getEncryptionCert(out nsIX509Cert ecert);
+ void getSigningTime(out PRTime aTime);
+
+ /**
+ * @param verifyFlags - Optional flags from nsICMSVerifyFlags.
+ */
+ void verifySignature(in long verifyFlags);
+
+ /**
+ * @param verifyFlags - Optional flags from nsICMSVerifyFlags.
+ */
+ void verifyDetachedSignature(in long verifyFlags,
+ in Array<octet> aDigestData,
+ in int16_t aDigestType);
+ void CreateEncrypted(in Array<nsIX509Cert> aRecipientCerts);
+
+ /* The parameter aDigestType must be one of the values in nsICryptoHash */
+ void CreateSigned(in nsIX509Cert scert, in nsIX509Cert ecert,
+ in Array<octet> aDigestData, in int16_t aDigestType);
+
+ /**
+ * Async version of nsICMSMessage::VerifySignature.
+ * Code will be executed on a background thread and
+ * availability of results will be notified using a
+ * call to nsISMimeVerificationListener.
+ */
+ void asyncVerifySignature(in long verifyFlags,
+ in nsISMimeVerificationListener listener);
+
+ /**
+ * Async version of nsICMSMessage::VerifyDetachedSignature.
+ * Code will be executed on a background thread and
+ * availability of results will be notified using a
+ * call to nsISMimeVerificationListener.
+ *
+ * Set aDigestType to one of the values from nsICryptoHash.
+ */
+ void asyncVerifyDetachedSignature(in long verifyFlags,
+ in nsISMimeVerificationListener listener,
+ in Array<octet> aDigestData,
+ in int16_t aDigestType);
+};
+
+[uuid(5226d698-0773-4f25-b94c-7944b3fc01d3)]
+interface nsISMimeVerificationListener : nsISupports {
+
+ /**
+ * Notify that results are ready, that have been requested
+ * using nsICMSMessage::asyncVerify[Detached]Signature()
+ *
+ * verificationResultCode matches synchronous result code from
+ * nsICMSMessage::verify[Detached]Signature
+ */
+ void notify(in nsICMSMessage verifiedMessage,
+ in nsresult verificationResultCode);
+};
diff --git a/comm/mailnews/extensions/smime/nsICMSMessageErrors.idl b/comm/mailnews/extensions/smime/nsICMSMessageErrors.idl
new file mode 100644
index 0000000000..7378185f50
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsICMSMessageErrors.idl
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+/**
+ * nsICMSMessageErrors
+ * Scriptable error constants for nsICMSMessage
+ */
+[scriptable,uuid(267f1a5b-88f7-413b-bc49-487e745282f1)]
+interface nsICMSMessageErrors : nsISupports
+{
+ const long SUCCESS = 0;
+ const long GENERAL_ERROR = 1;
+ const long VERIFY_NOT_SIGNED = 1024;
+ const long VERIFY_NO_CONTENT_INFO = 1025;
+ const long VERIFY_BAD_DIGEST = 1026;
+ const long VERIFY_NOCERT = 1028;
+ const long VERIFY_UNTRUSTED = 1029;
+ const long VERIFY_ERROR_UNVERIFIED = 1031;
+ const long VERIFY_ERROR_PROCESSING = 1032;
+ const long VERIFY_BAD_SIGNATURE = 1033;
+ const long VERIFY_DIGEST_MISMATCH = 1034;
+ const long VERIFY_UNKNOWN_ALGO = 1035;
+ const long VERIFY_UNSUPPORTED_ALGO = 1036;
+ const long VERIFY_MALFORMED_SIGNATURE = 1037;
+ const long VERIFY_HEADER_MISMATCH = 1038;
+ const long VERIFY_NOT_YET_ATTEMPTED = 1039;
+ const long VERIFY_CERT_WITHOUT_ADDRESS = 1040;
+ const long VERIFY_TIME_MISMATCH = 1041;
+
+ const long ENCRYPT_NO_BULK_ALG = 1056;
+ const long ENCRYPT_INCOMPLETE = 1057;
+};
diff --git a/comm/mailnews/extensions/smime/nsICMSSecureMessage.idl b/comm/mailnews/extensions/smime/nsICMSSecureMessage.idl
new file mode 100644
index 0000000000..d41fc04743
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsICMSSecureMessage.idl
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIX509Cert;
+
+/**
+ * nsICMSManager (service)
+ * Interface to access users certificate store
+ */
+[scriptable, uuid(17103436-0111-4819-a751-0fc4aa6e3d79)]
+interface nsICMSSecureMessage : nsISupports
+{
+ /**
+ * Return true if the certificate can be used for encrypting emails.
+ */
+ bool canBeUsedForEmailEncryption(in nsIX509Cert cert);
+
+ /**
+ * Return true if the certificate can be used for signing emails.
+ */
+ bool canBeUsedForEmailSigning(in nsIX509Cert cert);
+};
+
+%{C++
+#define NS_CMSSECUREMESSAGE_CONTRACTID "@mozilla.org/nsCMSSecureMessage;1"
+%}
diff --git a/comm/mailnews/extensions/smime/nsICertPickDialogs.idl b/comm/mailnews/extensions/smime/nsICertPickDialogs.idl
new file mode 100644
index 0000000000..534d47fa77
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsICertPickDialogs.idl
@@ -0,0 +1,27 @@
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIInterfaceRequestor;
+
+/**
+ * nsICertPickDialogs provides a generic UI for choosing a certificate.
+ */
+[scriptable, uuid(51d59b08-1dd2-11b2-ad4a-a51b92f8a184)]
+interface nsICertPickDialogs : nsISupports
+{
+ /**
+ * General purpose certificate prompter.
+ */
+ void pickCertificate(in nsIInterfaceRequestor ctx,
+ in Array<AString> certNickList,
+ in Array<AString> certDetailsList,
+ inout long selectedIndex,
+ out boolean canceled);
+};
+
+%{C++
+#define NS_CERTPICKDIALOGS_CONTRACTID "@mozilla.org/nsCertPickDialogs;1"
+%}
diff --git a/comm/mailnews/extensions/smime/nsIEncryptedSMIMEURIsSrvc.idl b/comm/mailnews/extensions/smime/nsIEncryptedSMIMEURIsSrvc.idl
new file mode 100644
index 0000000000..53e305c977
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsIEncryptedSMIMEURIsSrvc.idl
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+/* This is a private interface used exclusively by SMIME.
+ It provides functionality to the JS UI code,
+ that is only accessible from C++.
+*/
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(f86e55c9-530b-483f-91a7-10fb5b852488)]
+interface nsIEncryptedSMIMEURIsService : nsISupports
+{
+ /// Remember that this URI is encrypted.
+ void rememberEncrypted(in AUTF8String uri);
+
+ /// Forget that this URI is encrypted.
+ void forgetEncrypted(in AUTF8String uri);
+
+ /// Check if this URI is encrypted.
+ boolean isEncrypted(in AUTF8String uri);
+};
diff --git a/comm/mailnews/extensions/smime/nsIMsgSMIMEHeaderSink.idl b/comm/mailnews/extensions/smime/nsIMsgSMIMEHeaderSink.idl
new file mode 100644
index 0000000000..5a0a9b838b
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsIMsgSMIMEHeaderSink.idl
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+
+/* This is a private interface used exclusively by SMIME. NO ONE outside of extensions/smime
+ or the hard coded smime decryption files in mime/src should have any knowledge nor should
+ be referring to this interface.
+*/
+
+#include "nsISupports.idl"
+
+interface nsIX509Cert;
+
+[scriptable, uuid(25380FA1-E70C-4e82-B0BC-F31C2F41C470)]
+interface nsIMsgSMIMEHeaderSink : nsISupports
+{
+ void signedStatus(in long aNestingLevel,
+ in long aSignatureStatus,
+ in nsIX509Cert aSignerCert,
+ in AUTF8String aMsgNeckoURL,
+ in ACString originMimePartNumber);
+ void encryptionStatus(in long aNestingLevel,
+ in long aEncryptionStatus,
+ in nsIX509Cert aReceipientCert,
+ in AUTF8String aMsgNeckoURL,
+ in ACString originMimePartNumber);
+ void ignoreStatusFrom(in ACString originMimePartNumber);
+};
diff --git a/comm/mailnews/extensions/smime/nsIUserCertPicker.idl b/comm/mailnews/extensions/smime/nsIUserCertPicker.idl
new file mode 100644
index 0000000000..666941c297
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsIUserCertPicker.idl
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIX509Cert;
+interface nsIInterfaceRequestor;
+
+[scriptable, uuid(92396323-23f2-49e0-bf98-a25a725231ab)]
+interface nsIUserCertPicker : nsISupports {
+ nsIX509Cert pickByUsage(in nsIInterfaceRequestor ctx,
+ in wstring selectedNickname,
+ in long certUsage, // as defined by NSS enum SECCertUsage
+ in boolean allowInvalid,
+ in boolean allowDuplicateNicknames,
+ in AString emailAddress, // optional - if non-empty,
+ // skip certificates which
+ // have at least one e-mail
+ // address but do not
+ // include this specific one
+ out boolean canceled);
+};
+
+%{C++
+#define NS_CERT_PICKER_CONTRACTID "@mozilla.org/user_cert_picker;1"
+%}
diff --git a/comm/mailnews/extensions/smime/nsMsgComposeSecure.cpp b/comm/mailnews/extensions/smime/nsMsgComposeSecure.cpp
new file mode 100644
index 0000000000..4ecf73783d
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsMsgComposeSecure.cpp
@@ -0,0 +1,1277 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * 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 http://mozilla.org/MPL/2.0/. */
+
+#include "nsMsgComposeSecure.h"
+
+#include "ScopedNSSTypes.h"
+#include "cert.h"
+#include "keyhi.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Components.h"
+#include "mozilla/mailnews/MimeEncoder.h"
+#include "mozilla/mailnews/MimeHeaderParser.h"
+#include "msgCore.h"
+#include "nsComponentManagerUtils.h"
+#include "nsICryptoHash.h"
+#include "nsIMimeConverter.h"
+#include "nsIMsgCompFields.h"
+#include "nsIMsgIdentity.h"
+#include "nsIX509CertDB.h"
+#include "nsMemory.h"
+#include "nsMimeTypes.h"
+#include "nsNSSComponent.h"
+#include "nsServiceManagerUtils.h"
+#include "nspr.h"
+#include "mozpkix/Result.h"
+#include "nsNSSCertificate.h"
+#include "nsNSSHelper.h"
+#include "CryptoTask.h"
+
+using namespace mozilla::mailnews;
+using namespace mozilla;
+using namespace mozilla::psm;
+
+#define MK_MIME_ERROR_WRITING_FILE -1
+
+#define SMIME_STRBUNDLE_URL "chrome://messenger/locale/am-smime.properties"
+
+// It doesn't make sense to encode the message because the message will be
+// displayed only if the MUA doesn't support MIME.
+// We need to consider what to do in case the server doesn't support 8BITMIME.
+// In short, we can't use non-ASCII characters here.
+static const char crypto_multipart_blurb[] =
+ "This is a cryptographically signed message in MIME format.";
+
+static void mime_crypto_write_base64(void* closure, const char* buf,
+ unsigned long size);
+static nsresult mime_encoder_output_fn(const char* buf, int32_t size,
+ void* closure);
+static nsresult mime_nested_encoder_output_fn(const char* buf, int32_t size,
+ void* closure);
+static nsresult make_multipart_signed_header_string(bool outer_p,
+ char** header_return,
+ char** boundary_return,
+ int16_t hash_type);
+static char* mime_make_separator(const char* prefix);
+
+static void GenerateGlobalRandomBytes(unsigned char* buf, int32_t len) {
+ static bool firstTime = true;
+
+ if (firstTime) {
+ // Seed the random-number generator with current time so that
+ // the numbers will be different every time we run.
+ srand((unsigned)PR_Now());
+ firstTime = false;
+ }
+
+ for (int32_t i = 0; i < len; i++) buf[i] = rand() % 10;
+}
+
+char* mime_make_separator(const char* prefix) {
+ unsigned char rand_buf[13];
+ GenerateGlobalRandomBytes(rand_buf, 12);
+
+ return PR_smprintf(
+ "------------%s"
+ "%02X%02X%02X%02X"
+ "%02X%02X%02X%02X"
+ "%02X%02X%02X%02X",
+ prefix, rand_buf[0], rand_buf[1], rand_buf[2], rand_buf[3], rand_buf[4],
+ rand_buf[5], rand_buf[6], rand_buf[7], rand_buf[8], rand_buf[9],
+ rand_buf[10], rand_buf[11]);
+}
+
+// end of copied code which needs fixed....
+
+/////////////////////////////////////////////////////////////////////////////////////////
+// Implementation of nsMsgComposeSecure
+/////////////////////////////////////////////////////////////////////////////////////////
+
+NS_IMPL_ISUPPORTS(nsMsgComposeSecure, nsIMsgComposeSecure)
+
+nsMsgComposeSecure::nsMsgComposeSecure()
+ : mSignMessage(false),
+ mAlwaysEncryptMessage(false),
+ mCryptoState(mime_crypto_none),
+ mHashType(0),
+ mMultipartSignedBoundary(nullptr),
+ mIsDraft(false),
+ mBuffer(nullptr),
+ mBufferedBytes(0),
+ mErrorAlreadyReported(false) {}
+
+nsMsgComposeSecure::~nsMsgComposeSecure() {
+ /* destructor code */
+ if (mEncryptionContext) {
+ if (mBufferedBytes) {
+ mEncryptionContext->Update(mBuffer, mBufferedBytes);
+ mBufferedBytes = 0;
+ }
+ mEncryptionContext->Finish();
+ mEncryptionContext = nullptr;
+ }
+
+ delete[] mBuffer;
+ mBuffer = nullptr;
+
+ PR_FREEIF(mMultipartSignedBoundary);
+}
+
+NS_IMETHODIMP nsMsgComposeSecure::SetSignMessage(bool value) {
+ mSignMessage = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeSecure::GetSignMessage(bool* _retval) {
+ *_retval = mSignMessage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeSecure::SetRequireEncryptMessage(bool value) {
+ mAlwaysEncryptMessage = value;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeSecure::GetRequireEncryptMessage(bool* _retval) {
+ *_retval = mAlwaysEncryptMessage;
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsMsgComposeSecure::RequiresCryptoEncapsulation(
+ nsIMsgIdentity* aIdentity, nsIMsgCompFields* aCompFields,
+ bool* aRequiresEncryptionWork) {
+ NS_ENSURE_ARG_POINTER(aRequiresEncryptionWork);
+
+ *aRequiresEncryptionWork = false;
+
+ bool alwaysEncryptMessages = false;
+ bool signMessage = false;
+ nsresult rv = ExtractEncryptionState(aIdentity, aCompFields, &signMessage,
+ &alwaysEncryptMessages);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ if (alwaysEncryptMessages || signMessage) *aRequiresEncryptionWork = true;
+
+ return NS_OK;
+}
+
+nsresult nsMsgComposeSecure::GetSMIMEBundleString(const char16_t* name,
+ nsString& outString) {
+ outString.Truncate();
+
+ NS_ENSURE_ARG_POINTER(name);
+
+ NS_ENSURE_TRUE(InitializeSMIMEBundle(), NS_ERROR_FAILURE);
+
+ return mSMIMEBundle->GetStringFromName(NS_ConvertUTF16toUTF8(name).get(),
+ outString);
+}
+
+nsresult nsMsgComposeSecure::SMIMEBundleFormatStringFromName(
+ const char* name, nsTArray<nsString>& params, nsAString& outString) {
+ NS_ENSURE_ARG_POINTER(name);
+
+ if (!InitializeSMIMEBundle()) return NS_ERROR_FAILURE;
+
+ return mSMIMEBundle->FormatStringFromName(name, params, outString);
+}
+
+bool nsMsgComposeSecure::InitializeSMIMEBundle() {
+ if (mSMIMEBundle) return true;
+
+ nsCOMPtr<nsIStringBundleService> bundleService =
+ mozilla::components::StringBundle::Service();
+ nsresult rv = bundleService->CreateBundle(SMIME_STRBUNDLE_URL,
+ getter_AddRefs(mSMIMEBundle));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ return true;
+}
+
+void nsMsgComposeSecure::SetError(nsIMsgSendReport* sendReport,
+ const char16_t* bundle_string) {
+ if (!sendReport || !bundle_string) return;
+
+ if (mErrorAlreadyReported) return;
+
+ mErrorAlreadyReported = true;
+
+ nsString errorString;
+ nsresult res = GetSMIMEBundleString(bundle_string, errorString);
+ if (NS_SUCCEEDED(res) && !errorString.IsEmpty()) {
+ sendReport->SetMessage(nsIMsgSendReport::process_Current, errorString.get(),
+ true);
+ }
+}
+
+void nsMsgComposeSecure::SetErrorWithParam(nsIMsgSendReport* sendReport,
+ const char* bundle_string,
+ const char* param) {
+ if (!sendReport || !bundle_string || !param) return;
+
+ if (mErrorAlreadyReported) return;
+
+ mErrorAlreadyReported = true;
+
+ nsString errorString;
+ nsresult res;
+ AutoTArray<nsString, 1> params;
+ CopyASCIItoUTF16(MakeStringSpan(param), *params.AppendElement());
+ res = SMIMEBundleFormatStringFromName(bundle_string, params, errorString);
+
+ if (NS_SUCCEEDED(res) && !errorString.IsEmpty()) {
+ sendReport->SetMessage(nsIMsgSendReport::process_Current, errorString.get(),
+ true);
+ }
+}
+
+nsresult nsMsgComposeSecure::ExtractEncryptionState(
+ nsIMsgIdentity* aIdentity, nsIMsgCompFields* aComposeFields,
+ bool* aSignMessage, bool* aEncrypt) {
+ if (!aComposeFields && !aIdentity)
+ return NS_ERROR_FAILURE; // kick out...invalid args....
+
+ NS_ENSURE_ARG_POINTER(aSignMessage);
+ NS_ENSURE_ARG_POINTER(aEncrypt);
+
+ this->GetSignMessage(aSignMessage);
+ this->GetRequireEncryptMessage(aEncrypt);
+
+ return NS_OK;
+}
+
+// Select a hash algorithm to sign message
+// based on subject public key type and size.
+static nsresult GetSigningHashFunction(nsIX509Cert* aSigningCert,
+ int16_t* hashType) {
+ // Get the signing certificate
+ CERTCertificate* scert = nullptr;
+ if (aSigningCert) {
+ scert = aSigningCert->GetCert();
+ }
+ if (!scert) {
+ return NS_ERROR_FAILURE;
+ }
+
+ UniqueSECKEYPublicKey scertPublicKey(CERT_ExtractPublicKey(scert));
+ if (!scertPublicKey) {
+ return mozilla::MapSECStatus(SECFailure);
+ }
+ KeyType subjectPublicKeyType = SECKEY_GetPublicKeyType(scertPublicKey.get());
+
+ // Get the length of the signature in bits.
+ unsigned siglen = SECKEY_SignatureLen(scertPublicKey.get()) * 8;
+ if (!siglen) {
+ return mozilla::MapSECStatus(SECFailure);
+ }
+
+ // Select a hash function for signature generation whose security strength
+ // meets or exceeds the security strength of the public key, using NIST
+ // Special Publication 800-57, Recommendation for Key Management - Part 1:
+ // General (Revision 3), where Table 2 specifies the security strength of
+ // the public key and Table 3 lists acceptable hash functions. (The security
+ // strength of the hash (for digital signatures) is half the length of the
+ // output.)
+ // [SP 800-57 is available at http://csrc.nist.gov/publications/PubsSPs.html.]
+ if (subjectPublicKeyType == rsaKey) {
+ // For RSA, siglen is the same as the length of the modulus.
+
+ // SHA-1 provides equivalent security strength for up to 1024 bits
+ // SHA-256 provides equivalent security strength for up to 3072 bits
+
+ if (siglen > 3072) {
+ *hashType = nsICryptoHash::SHA512;
+ } else if (siglen > 1024) {
+ *hashType = nsICryptoHash::SHA256;
+ } else {
+ *hashType = nsICryptoHash::SHA1;
+ }
+ } else if (subjectPublicKeyType == dsaKey) {
+ // For DSA, siglen is twice the length of the q parameter of the key.
+ // The security strength of the key is half the length (in bits) of
+ // the q parameter of the key.
+
+ // NSS only supports SHA-1, SHA-224, and SHA-256 for DSA signatures.
+ // The S/MIME code does not support SHA-224.
+
+ if (siglen >= 512) { // 512-bit signature = 256-bit q parameter
+ *hashType = nsICryptoHash::SHA256;
+ } else {
+ *hashType = nsICryptoHash::SHA1;
+ }
+ } else if (subjectPublicKeyType == ecKey) {
+ // For ECDSA, siglen is twice the length of the field size. The security
+ // strength of the key is half the length (in bits) of the field size.
+
+ if (siglen >= 1024) { // 1024-bit signature = 512-bit field size
+ *hashType = nsICryptoHash::SHA512;
+ } else if (siglen >= 768) { // 768-bit signature = 384-bit field size
+ *hashType = nsICryptoHash::SHA384;
+ } else if (siglen >= 512) { // 512-bit signature = 256-bit field size
+ *hashType = nsICryptoHash::SHA256;
+ } else {
+ *hashType = nsICryptoHash::SHA1;
+ }
+ } else {
+ // Unknown key type
+ *hashType = nsICryptoHash::SHA256;
+ NS_WARNING("GetSigningHashFunction: Subject public key type unknown.");
+ }
+ return NS_OK;
+}
+
+/* void beginCryptoEncapsulation (in nsOutputFileStream aStream, in boolean
+ * aEncrypt, in boolean aSign, in string aRecipeints, in boolean aIsDraft); */
+NS_IMETHODIMP nsMsgComposeSecure::BeginCryptoEncapsulation(
+ nsIOutputStream* aStream, const char* aRecipients,
+ nsIMsgCompFields* aCompFields, nsIMsgIdentity* aIdentity,
+ nsIMsgSendReport* sendReport, bool aIsDraft) {
+ mErrorAlreadyReported = false;
+ nsresult rv = NS_OK;
+
+ // CryptoEncapsulation should be synchronous, therefore it must
+ // avoid cert verification or looking up certs, which often involves
+ // async OCSP. The message composer should already have looked up
+ // and verified certificates whenever the user modified the recipient
+ // list, and should have used CacheValidCertForEmail to make those
+ // certificates known to us.
+ // (That code may use the AsyncFindCertByEmailAddr API which allows
+ // lookup and validation to be performed on a background thread,
+ // which is required when using OCSP.)
+
+ bool encryptMessages = false;
+ bool signMessage = false;
+ ExtractEncryptionState(aIdentity, aCompFields, &signMessage,
+ &encryptMessages);
+
+ if (!signMessage && !encryptMessages) return NS_ERROR_FAILURE;
+
+ mStream = aStream;
+ mIsDraft = aIsDraft;
+
+ if (encryptMessages && signMessage)
+ mCryptoState = mime_crypto_signed_encrypted;
+ else if (encryptMessages)
+ mCryptoState = mime_crypto_encrypted;
+ else if (signMessage)
+ mCryptoState = mime_crypto_clear_signed;
+ else
+ PR_ASSERT(0);
+
+ aIdentity->GetUnicharAttribute("signing_cert_name", mSigningCertName);
+ aIdentity->GetCharAttribute("signing_cert_dbkey", mSigningCertDBKey);
+ aIdentity->GetUnicharAttribute("encryption_cert_name", mEncryptionCertName);
+ aIdentity->GetCharAttribute("encryption_cert_dbkey", mEncryptionCertDBKey);
+
+ rv = MimeCryptoHackCerts(aRecipients, sendReport, encryptMessages,
+ signMessage, aIdentity);
+ if (NS_FAILED(rv)) {
+ goto FAIL;
+ }
+
+ if (signMessage && mSelfSigningCert) {
+ rv = GetSigningHashFunction(mSelfSigningCert, &mHashType);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ switch (mCryptoState) {
+ case mime_crypto_clear_signed:
+ rv = MimeInitMultipartSigned(true, sendReport);
+ break;
+ case mime_crypto_opaque_signed:
+ PR_ASSERT(0); /* #### no api for this yet */
+ rv = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case mime_crypto_signed_encrypted:
+ rv = MimeInitEncryption(true, sendReport);
+ break;
+ case mime_crypto_encrypted:
+ rv = MimeInitEncryption(false, sendReport);
+ break;
+ case mime_crypto_none:
+ /* This can happen if mime_crypto_hack_certs() decided to turn off
+ encryption (by asking the user.) */
+ // XXX 1 is not a valid nsresult
+ rv = static_cast<nsresult>(1);
+ break;
+ default:
+ PR_ASSERT(0);
+ break;
+ }
+
+FAIL:
+ return rv;
+}
+
+/* void finishCryptoEncapsulation (in boolean aAbort); */
+NS_IMETHODIMP nsMsgComposeSecure::FinishCryptoEncapsulation(
+ bool aAbort, nsIMsgSendReport* sendReport) {
+ nsresult rv = NS_OK;
+
+ if (!aAbort) {
+ switch (mCryptoState) {
+ case mime_crypto_clear_signed:
+ rv = MimeFinishMultipartSigned(true, sendReport);
+ break;
+ case mime_crypto_opaque_signed:
+ PR_ASSERT(0); /* #### no api for this yet */
+ rv = NS_ERROR_FAILURE;
+ break;
+ case mime_crypto_signed_encrypted:
+ rv = MimeFinishEncryption(true, sendReport);
+ break;
+ case mime_crypto_encrypted:
+ rv = MimeFinishEncryption(false, sendReport);
+ break;
+ default:
+ PR_ASSERT(0);
+ rv = NS_ERROR_FAILURE;
+ break;
+ }
+ }
+ return rv;
+}
+
+nsresult nsMsgComposeSecure::MimeInitMultipartSigned(
+ bool aOuter, nsIMsgSendReport* sendReport) {
+ /* First, construct and write out the multipart/signed MIME header data.
+ */
+ nsresult rv = NS_OK;
+ char* header = 0;
+ uint32_t L;
+
+ rv = make_multipart_signed_header_string(
+ aOuter, &header, &mMultipartSignedBoundary, mHashType);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ L = strlen(header);
+
+ if (aOuter) {
+ /* If this is the outer block, write it to the file. */
+ uint32_t n;
+ rv = mStream->Write(header, L, &n);
+ if (NS_FAILED(rv) || n < L) {
+ // XXX This is -1, not an nsresult
+ rv = static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE);
+ }
+ } else {
+ /* If this is an inner block, feed it through the crypto stream. */
+ rv = MimeCryptoWriteBlock(header, L);
+ }
+
+ PR_Free(header);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* Now initialize the crypto library, so that we can compute a hash
+ on the object which we are signing.
+ */
+
+ PR_SetError(0, 0);
+ mDataHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mDataHash->Init(mHashType);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ PR_SetError(0, 0);
+ return rv;
+}
+
+nsresult nsMsgComposeSecure::MimeInitEncryption(bool aSign,
+ nsIMsgSendReport* sendReport) {
+ nsresult rv;
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> sMIMEBundle;
+ nsString mime_smime_enc_content_desc;
+
+ bundleSvc->CreateBundle(SMIME_STRBUNDLE_URL, getter_AddRefs(sMIMEBundle));
+
+ if (!sMIMEBundle) return NS_ERROR_FAILURE;
+
+ sMIMEBundle->GetStringFromName("mime_smimeEncryptedContentDesc",
+ mime_smime_enc_content_desc);
+ NS_ConvertUTF16toUTF8 enc_content_desc_utf8(mime_smime_enc_content_desc);
+
+ nsCOMPtr<nsIMimeConverter> mimeConverter =
+ do_GetService("@mozilla.org/messenger/mimeconverter;1", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+ nsCString encodedContentDescription;
+ mimeConverter->EncodeMimePartIIStr_UTF8(
+ enc_content_desc_utf8, false, sizeof("Content-Description: "),
+ nsIMimeConverter::MIME_ENCODED_WORD_SIZE, encodedContentDescription);
+
+ /* First, construct and write out the opaque-crypto-blob MIME header data.
+ */
+
+ char* s = PR_smprintf("Content-Type: " APPLICATION_PKCS7_MIME
+ "; name=\"smime.p7m\"; smime-type=enveloped-data" CRLF
+ "Content-Transfer-Encoding: " ENCODING_BASE64 CRLF
+ "Content-Disposition: attachment"
+ "; filename=\"smime.p7m\"" CRLF
+ "Content-Description: %s" CRLF CRLF,
+ encodedContentDescription.get());
+
+ uint32_t L;
+ if (!s) return NS_ERROR_OUT_OF_MEMORY;
+ L = strlen(s);
+ uint32_t n;
+ rv = mStream->Write(s, L, &n);
+ if (NS_FAILED(rv) || n < L) {
+ return NS_ERROR_FAILURE;
+ }
+ PR_Free(s);
+ s = 0;
+
+ /* Now initialize the crypto library, so that we can filter the object
+ to be encrypted through it.
+ */
+
+ if (!mIsDraft) {
+ PR_ASSERT(!mCerts.IsEmpty());
+ if (mCerts.IsEmpty()) return NS_ERROR_FAILURE;
+ }
+
+ // If a previous call to MimeInitEncryption (this function) failed,
+ // the mEncryptionContext already exists and references our
+ // mCryptoEncoder. Destroy mEncryptionContext to release the
+ // reference prior to resetting mCryptoEncoder.
+ if (mEncryptionContext) {
+ mEncryptionContext->Finish();
+ mEncryptionContext = nullptr;
+ }
+
+ // Initialize the base64 encoder
+ mCryptoEncoder.reset(
+ MimeEncoder::GetBase64Encoder(mime_encoder_output_fn, this));
+
+ /* Initialize the encrypter (and add the sender's cert.) */
+ PR_ASSERT(mSelfEncryptionCert);
+ PR_SetError(0, 0);
+ mEncryptionCinfo = do_CreateInstance(NS_CMSMESSAGE_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+ rv = mEncryptionCinfo->CreateEncrypted(mCerts);
+ if (NS_FAILED(rv)) {
+ SetError(sendReport, u"ErrorEncryptMail");
+ goto FAIL;
+ }
+
+ mEncryptionContext = do_CreateInstance(NS_CMSENCODER_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) return rv;
+
+ if (!mBuffer) {
+ mBuffer = new char[eBufferSize];
+ if (!mBuffer) return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ mBufferedBytes = 0;
+
+ rv = mEncryptionContext->Start(mEncryptionCinfo, mime_crypto_write_base64,
+ mCryptoEncoder.get());
+ if (NS_FAILED(rv)) {
+ SetError(sendReport, u"ErrorEncryptMail");
+ goto FAIL;
+ }
+
+ /* If we're signing, tack a multipart/signed header onto the front of
+ the data to be encrypted, and initialize the sign-hashing code too.
+ */
+ if (aSign) {
+ rv = MimeInitMultipartSigned(false, sendReport);
+ if (NS_FAILED(rv)) goto FAIL;
+ }
+
+FAIL:
+ return rv;
+}
+
+nsresult nsMsgComposeSecure::MimeFinishMultipartSigned(
+ bool aOuter, nsIMsgSendReport* sendReport) {
+ nsresult rv;
+ nsCOMPtr<nsICMSMessage> cinfo =
+ do_CreateInstance(NS_CMSMESSAGE_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsICMSEncoder> encoder =
+ do_CreateInstance(NS_CMSENCODER_CONTRACTID, &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ char* header = nullptr;
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ mozilla::components::StringBundle::Service();
+ NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED);
+
+ nsCOMPtr<nsIStringBundle> sMIMEBundle;
+ nsString mime_smime_sig_content_desc;
+
+ bundleSvc->CreateBundle(SMIME_STRBUNDLE_URL, getter_AddRefs(sMIMEBundle));
+
+ if (!sMIMEBundle) return NS_ERROR_FAILURE;
+
+ sMIMEBundle->GetStringFromName("mime_smimeSignatureContentDesc",
+ mime_smime_sig_content_desc);
+
+ NS_ConvertUTF16toUTF8 sig_content_desc_utf8(mime_smime_sig_content_desc);
+
+ /* Compute the hash...
+ */
+
+ NS_ENSURE_STATE(mDataHash);
+ nsAutoCString hashString;
+ rv = mDataHash->Finish(false, hashString);
+ mDataHash = nullptr;
+ NS_ENSURE_SUCCESS(rv, rv);
+ if (PR_GetError() < 0) return NS_ERROR_FAILURE;
+
+ /* Write out the headers for the signature.
+ */
+ uint32_t L;
+ header = PR_smprintf(
+ CRLF "--%s" CRLF "Content-Type: " APPLICATION_PKCS7_SIGNATURE
+ "; name=\"smime.p7s\"" CRLF
+ "Content-Transfer-Encoding: " ENCODING_BASE64 CRLF
+ "Content-Disposition: attachment; "
+ "filename=\"smime.p7s\"" CRLF "Content-Description: %s" CRLF CRLF,
+ mMultipartSignedBoundary, sig_content_desc_utf8.get());
+
+ if (!header) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ L = strlen(header);
+ if (aOuter) {
+ /* If this is the outer block, write it to the file. */
+ uint32_t n;
+ rv = mStream->Write(header, L, &n);
+ if (NS_FAILED(rv) || n < L) {
+ // XXX This is -1, not an nsresult
+ rv = static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE);
+ }
+ } else {
+ /* If this is an inner block, feed it through the crypto stream. */
+ rv = MimeCryptoWriteBlock(header, L);
+ }
+
+ PR_Free(header);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* Create the signature...
+ */
+
+ NS_ASSERTION(mHashType, "Hash function for signature has not been set.");
+
+ PR_ASSERT(mSelfSigningCert);
+ PR_SetError(0, 0);
+
+ nsTArray<uint8_t> digest;
+ digest.AppendElements(hashString.get(), hashString.Length());
+
+ rv = cinfo->CreateSigned(mSelfSigningCert, mSelfEncryptionCert, digest,
+ mHashType);
+ if (NS_FAILED(rv)) {
+ SetError(sendReport, u"ErrorCanNotSignMail");
+ return rv;
+ }
+
+ // Initialize the base64 encoder for the signature data.
+ MOZ_ASSERT(!mSigEncoder, "Shouldn't already have a mSigEncoder");
+ mSigEncoder.reset(MimeEncoder::GetBase64Encoder(
+ (aOuter ? mime_encoder_output_fn : mime_nested_encoder_output_fn), this));
+
+ /* Write out the signature.
+ */
+ PR_SetError(0, 0);
+ rv = encoder->Start(cinfo, mime_crypto_write_base64, mSigEncoder.get());
+ if (NS_FAILED(rv)) {
+ SetError(sendReport, u"ErrorCanNotSignMail");
+ return rv;
+ }
+
+ // We're not passing in any data, so no update needed.
+ rv = encoder->Finish();
+ if (NS_FAILED(rv)) {
+ SetError(sendReport, u"ErrorCanNotSignMail");
+ return rv;
+ }
+
+ // Shut down the sig's base64 encoder.
+ rv = mSigEncoder->Flush();
+ mSigEncoder.reset();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ /* Now write out the terminating boundary.
+ */
+ {
+ uint32_t L;
+ char* header = PR_smprintf(CRLF "--%s--" CRLF, mMultipartSignedBoundary);
+ PR_Free(mMultipartSignedBoundary);
+ mMultipartSignedBoundary = 0;
+
+ if (!header) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+ L = strlen(header);
+ if (aOuter) {
+ /* If this is the outer block, write it to the file. */
+ uint32_t n;
+ rv = mStream->Write(header, L, &n);
+ if (NS_FAILED(rv) || n < L)
+ // XXX This is -1, not an nsresult
+ rv = static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE);
+ } else {
+ /* If this is an inner block, feed it through the crypto stream. */
+ rv = MimeCryptoWriteBlock(header, L);
+ }
+ }
+
+ return rv;
+}
+
+/* Helper function for mime_finish_crypto_encapsulation() to close off
+ an opaque crypto object (for encrypted or signed-and-encrypted messages.)
+ */
+nsresult nsMsgComposeSecure::MimeFinishEncryption(
+ bool aSign, nsIMsgSendReport* sendReport) {
+ nsresult rv;
+
+ /* If this object is both encrypted and signed, close off the
+ signature first (since it's inside.) */
+ if (aSign) {
+ rv = MimeFinishMultipartSigned(false, sendReport);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ /* Close off the opaque encrypted blob.
+ */
+ PR_ASSERT(mEncryptionContext);
+
+ if (mBufferedBytes) {
+ rv = mEncryptionContext->Update(mBuffer, mBufferedBytes);
+ mBufferedBytes = 0;
+ if (NS_FAILED(rv)) {
+ PR_ASSERT(PR_GetError() < 0);
+ return rv;
+ }
+ }
+
+ rv = mEncryptionContext->Finish();
+ mEncryptionContext = nullptr;
+
+ if (NS_FAILED(rv)) {
+ SetError(sendReport, u"ErrorEncryptMail");
+ return rv;
+ }
+
+ NS_ENSURE_TRUE(mEncryptionCinfo, NS_ERROR_UNEXPECTED);
+
+ mEncryptionCinfo = nullptr;
+
+ // Shut down the base64 encoder.
+ mCryptoEncoder->Flush();
+ mCryptoEncoder.reset();
+
+ uint32_t n;
+ rv = mStream->Write(CRLF, 2, &n);
+ if (NS_FAILED(rv) || n < 2) rv = NS_ERROR_FAILURE;
+
+ return rv;
+}
+
+/* Used to figure out what certs should be used when encrypting this message.
+ */
+nsresult nsMsgComposeSecure::MimeCryptoHackCerts(const char* aRecipients,
+ nsIMsgSendReport* sendReport,
+ bool aEncrypt, bool aSign,
+ nsIMsgIdentity* aIdentity) {
+ nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID);
+ nsresult res = NS_OK;
+
+ PR_ASSERT(aEncrypt || aSign);
+
+ /*
+ Signing and encryption certs use the following (per-identity) preferences:
+ - "signing_cert_name"/"encryption_cert_name": a string specifying the
+ nickname of the certificate
+ - "signing_cert_dbkey"/"encryption_cert_dbkey": a Base64 encoded blob
+ specifying an nsIX509Cert dbKey (represents serial number
+ and issuer DN, which is considered to be unique for X.509 certificates)
+ */
+
+ RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+ NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
+
+ // Calling CERT_GetCertNicknames has the desired side effect of
+ // traversing all tokens, and bringing up prompts to unlock them.
+ nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext();
+ CERTCertNicknames* result_unused = CERT_GetCertNicknames(
+ CERT_GetDefaultCertDB(), SEC_CERT_NICKNAMES_USER, ctx);
+ CERT_FreeNicknames(result_unused);
+
+ nsTArray<nsTArray<uint8_t>> builtChain;
+ if (!mEncryptionCertDBKey.IsEmpty()) {
+ res = certdb->FindCertByDBKey(mEncryptionCertDBKey,
+ getter_AddRefs(mSelfEncryptionCert));
+
+ if (NS_SUCCEEDED(res) && mSelfEncryptionCert) {
+ nsTArray<uint8_t> certBytes;
+ res = mSelfEncryptionCert->GetRawDER(certBytes);
+ NS_ENSURE_SUCCESS(res, res);
+
+ if (certVerifier->VerifyCert(
+ certBytes, certificateUsageEmailRecipient, mozilla::pkix::Now(),
+ nullptr, nullptr, builtChain,
+ // Only local checks can run on the main thread.
+ // Skipping OCSP for the user's own cert seems accaptable.
+ CertVerifier::FLAG_LOCAL_ONLY) != mozilla::pkix::Success) {
+ // not suitable for encryption, so unset cert and clear pref
+ mSelfEncryptionCert = nullptr;
+ mEncryptionCertDBKey.Truncate();
+ aIdentity->SetCharAttribute("encryption_cert_dbkey",
+ mEncryptionCertDBKey);
+ }
+ }
+ }
+
+ // same procedure for the signing cert
+ if (!mSigningCertDBKey.IsEmpty()) {
+ res = certdb->FindCertByDBKey(mSigningCertDBKey,
+ getter_AddRefs(mSelfSigningCert));
+ if (NS_SUCCEEDED(res) && mSelfSigningCert) {
+ nsTArray<uint8_t> certBytes;
+ res = mSelfSigningCert->GetRawDER(certBytes);
+ NS_ENSURE_SUCCESS(res, res);
+
+ if (certVerifier->VerifyCert(
+ certBytes, certificateUsageEmailSigner, mozilla::pkix::Now(),
+ nullptr, nullptr, builtChain,
+ // Only local checks can run on the main thread.
+ // Skipping OCSP for the user's own cert seems accaptable.
+ CertVerifier::FLAG_LOCAL_ONLY) != mozilla::pkix::Success) {
+ // not suitable for signing, so unset cert and clear pref
+ mSelfSigningCert = nullptr;
+ mSigningCertDBKey.Truncate();
+ aIdentity->SetCharAttribute("signing_cert_dbkey", mSigningCertDBKey);
+ }
+ }
+ }
+
+ // must have both the signing and encryption certs to sign
+ if (!mSelfSigningCert && aSign) {
+ SetError(sendReport, u"NoSenderSigningCert");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!mSelfEncryptionCert && aEncrypt) {
+ SetError(sendReport, u"NoSenderEncryptionCert");
+ return NS_ERROR_FAILURE;
+ }
+
+ if (aEncrypt && mSelfEncryptionCert) {
+ // Make sure self's configured cert is prepared for being used
+ // as an email recipient cert.
+ UniqueCERTCertificate nsscert(mSelfEncryptionCert->GetCert());
+ if (!nsscert) {
+ return NS_ERROR_FAILURE;
+ }
+ // XXX: This does not respect the nsNSSShutDownObject protocol.
+ if (CERT_SaveSMimeProfile(nsscert.get(), nullptr, nullptr) != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ /* If the message is to be encrypted, then get the recipient certs */
+ if (aEncrypt) {
+ nsTArray<nsCString> mailboxes;
+ ExtractEmails(EncodedHeader(nsDependentCString(aRecipients)),
+ UTF16ArrayAdapter<>(mailboxes));
+ uint32_t count = mailboxes.Length();
+
+ bool already_added_self_cert = false;
+
+ for (uint32_t i = 0; i < count; i++) {
+ nsCString mailbox_lowercase;
+ ToLowerCase(mailboxes[i], mailbox_lowercase);
+ nsCOMPtr<nsIX509Cert> cert;
+
+ nsCString dbKey;
+ res = GetCertDBKeyForEmail(mailbox_lowercase, dbKey);
+ if (NS_SUCCEEDED(res)) {
+ res = certdb->FindCertByDBKey(dbKey, getter_AddRefs(cert));
+ }
+
+ if (NS_FAILED(res) || !cert) {
+ // Failure to find a valid encryption cert is fatal.
+ // Here I assume that mailbox is ascii rather than utf8.
+ SetErrorWithParam(sendReport, "MissingRecipientEncryptionCert",
+ mailboxes[i].get());
+ return res;
+ }
+
+ /* #### see if recipient requests `signedData'.
+ if (...) no_clearsigning_p = true;
+ (This is the only reason we even bother looking up the certs
+ of the recipients if we're sending a signed-but-not-encrypted
+ message.)
+ */
+
+ if (cert.get() == mSelfEncryptionCert.get()) {
+ already_added_self_cert = true;
+ }
+
+ mCerts.AppendElement(cert);
+ }
+
+ if (!already_added_self_cert) {
+ mCerts.AppendElement(mSelfEncryptionCert);
+ }
+ }
+ return res;
+}
+
+NS_IMETHODIMP nsMsgComposeSecure::MimeCryptoWriteBlock(const char* buf,
+ int32_t size) {
+ int status = 0;
+ nsresult rv;
+
+ /* If this is a From line, mangle it before signing it. You just know
+ that something somewhere is going to mangle it later, and that's
+ going to cause the signature check to fail.
+
+ (This assumes that, in the cases where From-mangling must happen,
+ this function is called a line at a time. That happens to be the
+ case.)
+ */
+ if (size >= 5 && buf[0] == 'F' && !strncmp(buf, "From ", 5)) {
+ char mangle[] = ">";
+ nsresult res = MimeCryptoWriteBlock(mangle, 1);
+ if (NS_FAILED(res)) return res;
+ // This value will actually be cast back to an nsresult before use, so this
+ // cast is reasonable under the circumstances.
+ status = static_cast<int>(res);
+ }
+
+ /* If we're signing, or signing-and-encrypting, feed this data into
+ the computation of the hash. */
+ if (mDataHash) {
+ PR_SetError(0, 0);
+ mDataHash->Update((const uint8_t*)buf, size);
+ status = PR_GetError();
+ if (status < 0) goto FAIL;
+ }
+
+ PR_SetError(0, 0);
+ if (mEncryptionContext) {
+ /* If we're encrypting, or signing-and-encrypting, write this data
+ by filtering it through the crypto library. */
+
+ /* We want to create equally sized encryption strings */
+ const char* inputBytesIterator = buf;
+ uint32_t inputBytesLeft = size;
+
+ while (inputBytesLeft) {
+ const uint32_t spaceLeftInBuffer = eBufferSize - mBufferedBytes;
+ const uint32_t bytesToAppend =
+ std::min(inputBytesLeft, spaceLeftInBuffer);
+
+ memcpy(mBuffer + mBufferedBytes, inputBytesIterator, bytesToAppend);
+ mBufferedBytes += bytesToAppend;
+
+ inputBytesIterator += bytesToAppend;
+ inputBytesLeft -= bytesToAppend;
+
+ if (eBufferSize == mBufferedBytes) {
+ rv = mEncryptionContext->Update(mBuffer, mBufferedBytes);
+ mBufferedBytes = 0;
+ if (NS_FAILED(rv)) {
+ status = PR_GetError();
+ PR_ASSERT(status < 0);
+ if (status >= 0) status = -1;
+ goto FAIL;
+ }
+ }
+ }
+ } else {
+ /* If we're not encrypting (presumably just signing) then write this
+ data directly to the file. */
+
+ uint32_t n;
+ rv = mStream->Write(buf, size, &n);
+ if (NS_FAILED(rv) || n < (uint32_t)size) {
+ // XXX MK_MIME_ERROR_WRITING_FILE is -1, which is not a valid nsresult
+ return static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE);
+ }
+ }
+FAIL:
+ // XXX status sometimes has invalid nsresults like -1 or PR_GetError()
+ // assigned to it
+ return static_cast<nsresult>(status);
+}
+
+/* Returns a string consisting of a Content-Type header, and a boundary
+ string, suitable for moving from the header block, down into the body
+ of a multipart object. The boundary itself is also returned (so that
+ the caller knows what to write to close it off.)
+ */
+static nsresult make_multipart_signed_header_string(bool outer_p,
+ char** header_return,
+ char** boundary_return,
+ int16_t hash_type) {
+ const char* hashStr;
+ *header_return = 0;
+ *boundary_return = mime_make_separator("ms");
+
+ if (!*boundary_return) return NS_ERROR_OUT_OF_MEMORY;
+
+ switch (hash_type) {
+ case nsICryptoHash::SHA1:
+ hashStr = PARAM_MICALG_SHA1;
+ break;
+ case nsICryptoHash::SHA256:
+ hashStr = PARAM_MICALG_SHA256;
+ break;
+ case nsICryptoHash::SHA384:
+ hashStr = PARAM_MICALG_SHA384;
+ break;
+ case nsICryptoHash::SHA512:
+ hashStr = PARAM_MICALG_SHA512;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *header_return = PR_smprintf("Content-Type: " MULTIPART_SIGNED
+ "; "
+ "protocol=\"" APPLICATION_PKCS7_SIGNATURE
+ "\"; "
+ "micalg=%s; "
+ "boundary=\"%s\"" CRLF CRLF
+ "%s%s"
+ "--%s" CRLF,
+ hashStr, *boundary_return,
+ (outer_p ? crypto_multipart_blurb : ""),
+ (outer_p ? CRLF CRLF : ""), *boundary_return);
+
+ if (!*header_return) {
+ PR_Free(*boundary_return);
+ *boundary_return = 0;
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ return NS_OK;
+}
+
+/* Used as the output function of a SEC_PKCS7EncoderContext -- we feed
+ plaintext into the crypto engine, and it calls this function with encrypted
+ data; then this function writes a base64-encoded representation of that
+ data to the file (by filtering it through the given MimeEncoder object.)
+
+ Also used as the output function of SEC_PKCS7Encode() -- but in that case,
+ it's used to write the encoded representation of the signature. The only
+ difference is which MimeEncoder object is used.
+ */
+static void mime_crypto_write_base64(void* closure, const char* buf,
+ unsigned long size) {
+ MimeEncoder* encoder = (MimeEncoder*)closure;
+ nsresult rv = encoder->Write(buf, size);
+ PR_SetError(NS_FAILED(rv) ? static_cast<uint32_t>(rv) : 0, 0);
+}
+
+/* Used as the output function of MimeEncoder -- when we have generated
+ the signature for a multipart/signed object, this is used to write the
+ base64-encoded representation of the signature to the file.
+ */
+// TODO: size should probably be converted to uint32_t
+nsresult mime_encoder_output_fn(const char* buf, int32_t size, void* closure) {
+ nsMsgComposeSecure* state = (nsMsgComposeSecure*)closure;
+ nsCOMPtr<nsIOutputStream> stream;
+ state->GetOutputStream(getter_AddRefs(stream));
+ uint32_t n;
+ nsresult rv = stream->Write((char*)buf, size, &n);
+ if (NS_FAILED(rv) || n < (uint32_t)size)
+ return NS_ERROR_FAILURE;
+ else
+ return NS_OK;
+}
+
+/* Like mime_encoder_output_fn, except this is used for the case where we
+ are both signing and encrypting -- the base64-encoded output of the
+ signature should be fed into the crypto engine, rather than being written
+ directly to the file.
+ */
+static nsresult mime_nested_encoder_output_fn(const char* buf, int32_t size,
+ void* closure) {
+ nsMsgComposeSecure* state = (nsMsgComposeSecure*)closure;
+
+ // Copy to new null-terminated string so JS glue doesn't crash when
+ // MimeCryptoWriteBlock() is implemented in JS.
+ nsCString bufWithNull;
+ bufWithNull.Assign(buf, size);
+ return state->MimeCryptoWriteBlock(bufWithNull.get(), size);
+}
+
+class FindSMimeCertTask final : public CryptoTask {
+ public:
+ FindSMimeCertTask(const nsACString& email,
+ nsIDoneFindCertForEmailCallback* listener)
+ : mEmail(email), mListener(listener) {
+ MOZ_ASSERT(NS_IsMainThread());
+ }
+ ~FindSMimeCertTask();
+
+ private:
+ virtual nsresult CalculateResult() override;
+ virtual void CallCallback(nsresult rv) override;
+
+ const nsCString mEmail;
+ nsCOMPtr<nsIX509Cert> mCert;
+ nsCOMPtr<nsIDoneFindCertForEmailCallback> mListener;
+
+ static mozilla::StaticMutex sMutex;
+};
+
+mozilla::StaticMutex FindSMimeCertTask::sMutex;
+
+void FindSMimeCertTask::CallCallback(nsresult rv) {
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCOMPtr<nsIX509Cert> cert;
+ nsCOMPtr<nsIDoneFindCertForEmailCallback> listener;
+ {
+ mozilla::StaticMutexAutoLock lock(sMutex);
+ if (!mListener) {
+ return;
+ }
+ // We won't need these objects after leaving this function, so let's
+ // destroy them early. Also has the benefit that we're already
+ // on the main thread. By destroying the listener here, we avoid
+ // dispatching in the destructor.
+ mCert.swap(cert);
+ mListener.swap(listener);
+ }
+ listener->FindCertDone(mEmail, cert);
+}
+
+/*
+called by:
+ GetValidCertInfo
+ GetRecipientCertsInfo
+ GetNoCertAddresses
+*/
+nsresult FindSMimeCertTask::CalculateResult() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsresult rv = BlockUntilLoadableCertsLoaded();
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+ NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED);
+
+ const nsCString& flatEmailAddress = PromiseFlatCString(mEmail);
+ UniqueCERTCertList certlist(
+ PK11_FindCertsFromEmailAddress(flatEmailAddress.get(), nullptr));
+ if (!certlist) return NS_ERROR_FAILURE;
+
+ // certlist now contains certificates with the right email address,
+ // but they might not have the correct usage or might even be invalid
+
+ if (CERT_LIST_END(CERT_LIST_HEAD(certlist), certlist))
+ return NS_ERROR_FAILURE; // no certs found
+
+ CERTCertListNode* node;
+ // search for a valid certificate
+ for (node = CERT_LIST_HEAD(certlist); !CERT_LIST_END(node, certlist);
+ node = CERT_LIST_NEXT(node)) {
+ nsTArray<uint8_t> certBytes(node->cert->derCert.data,
+ node->cert->derCert.len);
+ nsTArray<nsTArray<uint8_t>> unusedCertChain;
+
+ mozilla::pkix::Result result = certVerifier->VerifyCert(
+ certBytes, certificateUsageEmailRecipient, mozilla::pkix::Now(),
+ nullptr /*XXX pinarg*/, nullptr /*hostname*/, unusedCertChain);
+ if (result == mozilla::pkix::Success) {
+ mozilla::StaticMutexAutoLock lock(sMutex);
+ mCert = new nsNSSCertificate(node->cert);
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+/*
+ * We need to ensure that the callback is destroyed on the main thread.
+ */
+class ProxyListenerDestructor final : public mozilla::Runnable {
+ public:
+ explicit ProxyListenerDestructor(
+ nsCOMPtr<nsIDoneFindCertForEmailCallback>&& aListener)
+ : mozilla::Runnable("ProxyListenerDestructor"),
+ mListener(std::move(aListener)) {}
+
+ NS_IMETHODIMP
+ Run() override {
+ MOZ_ASSERT(NS_IsMainThread());
+ // Release the object referenced by mListener.
+ mListener = nullptr;
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsIDoneFindCertForEmailCallback> mListener;
+};
+
+FindSMimeCertTask::~FindSMimeCertTask() {
+ // Unless we already cleaned up inside CallCallback, we must release
+ // the listener on the main thread.
+ if (mListener && !NS_IsMainThread()) {
+ RefPtr<ProxyListenerDestructor> runnable =
+ new ProxyListenerDestructor(std::move(mListener));
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(runnable));
+ }
+}
+
+NS_IMETHODIMP
+nsMsgComposeSecure::CacheValidCertForEmail(const nsACString& email,
+ const nsACString& certDBKey) {
+ mozilla::StaticMutexAutoLock lock(sMutex);
+ mValidCertForEmailAddr.InsertOrUpdate(email, certDBKey);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeSecure::HaveValidCertForEmail(const nsACString& email,
+ bool* _retval) {
+ mozilla::StaticMutexAutoLock lock(sMutex);
+ *_retval = mValidCertForEmailAddr.Get(email, nullptr);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeSecure::GetCertDBKeyForEmail(const nsACString& email,
+ nsACString& _retval) {
+ mozilla::StaticMutexAutoLock lock(sMutex);
+ nsCString dbKey;
+ bool found = mValidCertForEmailAddr.Get(email, &dbKey);
+ if (found) {
+ _retval = dbKey;
+ } else {
+ _retval.Truncate();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsMsgComposeSecure::AsyncFindCertByEmailAddr(
+ const nsACString& email, nsIDoneFindCertForEmailCallback* callback) {
+ RefPtr<CryptoTask> task = new FindSMimeCertTask(email, callback);
+ return task->Dispatch();
+}
+
+mozilla::StaticMutex nsMsgComposeSecure::sMutex;
diff --git a/comm/mailnews/extensions/smime/nsMsgComposeSecure.h b/comm/mailnews/extensions/smime/nsMsgComposeSecure.h
new file mode 100644
index 0000000000..14f7b54157
--- /dev/null
+++ b/comm/mailnews/extensions/smime/nsMsgComposeSecure.h
@@ -0,0 +1,103 @@
+/* -*- Mode: idl; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ *
+ * 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 http://mozilla.org/MPL/2.0/. */
+#ifndef _nsMsgComposeSecure_H_
+#define _nsMsgComposeSecure_H_
+
+#include "nsIMsgComposeSecure.h"
+#include "nsCOMPtr.h"
+#include "nsICMSEncoder.h"
+#include "nsIX509Cert.h"
+#include "nsIStringBundle.h"
+#include "nsICryptoHash.h"
+#include "nsICMSMessage.h"
+#include "nsString.h"
+#include "nsTHashMap.h"
+#include "nsIOutputStream.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/StaticMutex.h"
+
+class nsIMsgCompFields;
+namespace mozilla {
+namespace mailnews {
+class MimeEncoder;
+}
+} // namespace mozilla
+
+typedef enum {
+ mime_crypto_none, /* normal unencapsulated MIME message */
+ mime_crypto_clear_signed, /* multipart/signed encapsulation */
+ mime_crypto_opaque_signed, /* application/x-pkcs7-mime (signedData) */
+ mime_crypto_encrypted, /* application/x-pkcs7-mime */
+ mime_crypto_signed_encrypted /* application/x-pkcs7-mime */
+} mimeDeliveryCryptoState;
+
+class nsMsgComposeSecure : public nsIMsgComposeSecure {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIMSGCOMPOSESECURE
+
+ nsMsgComposeSecure();
+
+ void GetOutputStream(nsIOutputStream** stream) {
+ NS_IF_ADDREF(*stream = mStream);
+ }
+ nsresult GetSMIMEBundleString(const char16_t* name, nsString& outString);
+
+ private:
+ virtual ~nsMsgComposeSecure();
+ typedef mozilla::mailnews::MimeEncoder MimeEncoder;
+ nsresult MimeInitMultipartSigned(bool aOuter, nsIMsgSendReport* sendReport);
+ nsresult MimeInitEncryption(bool aSign, nsIMsgSendReport* sendReport);
+ nsresult MimeFinishMultipartSigned(bool aOuter, nsIMsgSendReport* sendReport);
+ nsresult MimeFinishEncryption(bool aSign, nsIMsgSendReport* sendReport);
+ nsresult MimeCryptoHackCerts(const char* aRecipients,
+ nsIMsgSendReport* sendReport, bool aEncrypt,
+ bool aSign, nsIMsgIdentity* aIdentity);
+ bool InitializeSMIMEBundle();
+ nsresult SMIMEBundleFormatStringFromName(const char* name,
+ nsTArray<nsString>& params,
+ nsAString& outString);
+ nsresult ExtractEncryptionState(nsIMsgIdentity* aIdentity,
+ nsIMsgCompFields* aComposeFields,
+ bool* aSignMessage, bool* aEncrypt);
+
+ bool mSignMessage;
+ bool mAlwaysEncryptMessage;
+ mimeDeliveryCryptoState mCryptoState;
+ nsCOMPtr<nsIOutputStream> mStream;
+ int16_t mHashType;
+ nsCOMPtr<nsICryptoHash> mDataHash;
+ mozilla::UniquePtr<MimeEncoder> mSigEncoder;
+ char* mMultipartSignedBoundary;
+ nsString mSigningCertName;
+ nsAutoCString mSigningCertDBKey;
+ nsCOMPtr<nsIX509Cert> mSelfSigningCert;
+ nsString mEncryptionCertName;
+ nsAutoCString mEncryptionCertDBKey;
+ nsCOMPtr<nsIX509Cert> mSelfEncryptionCert;
+ nsTArray<RefPtr<nsIX509Cert>> mCerts;
+ nsCOMPtr<nsICMSMessage> mEncryptionCinfo;
+ nsCOMPtr<nsICMSEncoder> mEncryptionContext;
+ nsCOMPtr<nsIStringBundle> mSMIMEBundle;
+
+ // Maps email address to nsIX509Cert.dbKey of a verified certificate.
+ nsTHashMap<nsCStringHashKey, nsCString> mValidCertForEmailAddr;
+ static mozilla::StaticMutex sMutex;
+
+ mozilla::UniquePtr<MimeEncoder> mCryptoEncoder;
+ bool mIsDraft;
+
+ enum { eBufferSize = 8192 };
+ char* mBuffer;
+ uint32_t mBufferedBytes;
+
+ bool mErrorAlreadyReported;
+ void SetError(nsIMsgSendReport* sendReport, const char16_t* bundle_string);
+ void SetErrorWithParam(nsIMsgSendReport* sendReport,
+ const char* bundle_string, const char* param);
+};
+
+#endif