From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../extensions/smime/certFetchingStatus.js | 255 ++++ .../extensions/smime/certFetchingStatus.xhtml | 40 + comm/mailnews/extensions/smime/certpicker.js | 69 ++ comm/mailnews/extensions/smime/certpicker.xhtml | 54 + comm/mailnews/extensions/smime/components.conf | 63 + comm/mailnews/extensions/smime/moz.build | 37 + .../extensions/smime/msgCompSecurityInfo.js | 122 ++ .../extensions/smime/msgCompSecurityInfo.xhtml | 69 ++ .../extensions/smime/msgReadSMIMEOverlay.js | 251 ++++ comm/mailnews/extensions/smime/nsCMS.cpp | 1187 ++++++++++++++++++ comm/mailnews/extensions/smime/nsCMS.h | 123 ++ .../extensions/smime/nsCMSSecureMessage.cpp | 92 ++ .../mailnews/extensions/smime/nsCMSSecureMessage.h | 39 + comm/mailnews/extensions/smime/nsCertPicker.cpp | 410 +++++++ comm/mailnews/extensions/smime/nsCertPicker.h | 32 + .../smime/nsEncryptedSMIMEURIsService.cpp | 32 + .../extensions/smime/nsEncryptedSMIMEURIsService.h | 24 + comm/mailnews/extensions/smime/nsICMSDecoder.idl | 29 + comm/mailnews/extensions/smime/nsICMSDecoderJS.idl | 24 + comm/mailnews/extensions/smime/nsICMSEncoder.idl | 29 + comm/mailnews/extensions/smime/nsICMSMessage.idl | 96 ++ .../extensions/smime/nsICMSMessageErrors.idl | 36 + .../extensions/smime/nsICMSSecureMessage.idl | 30 + .../extensions/smime/nsICertPickDialogs.idl | 27 + .../extensions/smime/nsIEncryptedSMIMEURIsSrvc.idl | 24 + .../extensions/smime/nsIMsgSMIMEHeaderSink.idl | 30 + .../extensions/smime/nsIUserCertPicker.idl | 28 + .../extensions/smime/nsMsgComposeSecure.cpp | 1277 ++++++++++++++++++++ .../mailnews/extensions/smime/nsMsgComposeSecure.h | 103 ++ 29 files changed, 4632 insertions(+) create mode 100644 comm/mailnews/extensions/smime/certFetchingStatus.js create mode 100644 comm/mailnews/extensions/smime/certFetchingStatus.xhtml create mode 100644 comm/mailnews/extensions/smime/certpicker.js create mode 100644 comm/mailnews/extensions/smime/certpicker.xhtml create mode 100644 comm/mailnews/extensions/smime/components.conf create mode 100644 comm/mailnews/extensions/smime/moz.build create mode 100644 comm/mailnews/extensions/smime/msgCompSecurityInfo.js create mode 100644 comm/mailnews/extensions/smime/msgCompSecurityInfo.xhtml create mode 100644 comm/mailnews/extensions/smime/msgReadSMIMEOverlay.js create mode 100644 comm/mailnews/extensions/smime/nsCMS.cpp create mode 100644 comm/mailnews/extensions/smime/nsCMS.h create mode 100644 comm/mailnews/extensions/smime/nsCMSSecureMessage.cpp create mode 100644 comm/mailnews/extensions/smime/nsCMSSecureMessage.h create mode 100644 comm/mailnews/extensions/smime/nsCertPicker.cpp create mode 100644 comm/mailnews/extensions/smime/nsCertPicker.h create mode 100644 comm/mailnews/extensions/smime/nsEncryptedSMIMEURIsService.cpp create mode 100644 comm/mailnews/extensions/smime/nsEncryptedSMIMEURIsService.h create mode 100644 comm/mailnews/extensions/smime/nsICMSDecoder.idl create mode 100644 comm/mailnews/extensions/smime/nsICMSDecoderJS.idl create mode 100644 comm/mailnews/extensions/smime/nsICMSEncoder.idl create mode 100644 comm/mailnews/extensions/smime/nsICMSMessage.idl create mode 100644 comm/mailnews/extensions/smime/nsICMSMessageErrors.idl create mode 100644 comm/mailnews/extensions/smime/nsICMSSecureMessage.idl create mode 100644 comm/mailnews/extensions/smime/nsICertPickDialogs.idl create mode 100644 comm/mailnews/extensions/smime/nsIEncryptedSMIMEURIsSrvc.idl create mode 100644 comm/mailnews/extensions/smime/nsIMsgSMIMEHeaderSink.idl create mode 100644 comm/mailnews/extensions/smime/nsIUserCertPicker.idl create mode 100644 comm/mailnews/extensions/smime/nsMsgComposeSecure.cpp create mode 100644 comm/mailnews/extensions/smime/nsMsgComposeSecure.h (limited to 'comm/mailnews/extensions/smime') 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 @@ + + + + + + + + + + + &title.label; + + + + + + + &info.message; + + + 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 @@ + + + + + + +%amE2EDTD; ]> + + + + &certPicker.title; + + + + + + + + + + + + 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 @@ + + + + + + + + + + + + + &title.label; + + + + + + + + + + + + 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 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 cert; + if (si->cert) { + MOZ_LOG(gCMSLog, LogLevel::Debug, + ("nsCMSMessage::GetSignerCert got signer cert")); + + nsCOMPtr certdb = do_GetService(NS_X509CERTDB_CONTRACTID); + nsTArray 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& 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 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 certBytes(cert->derCert.data, cert->derCert.len); + nsTArray> 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& 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( + 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(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(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& 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& 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 mMessage; + nsCOMPtr mListener; + nsTArray 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& aDigestData, int16_t aDigestType) { + RefPtr 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>& 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& 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(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(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 nssInitialized = + do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +nsresult nsCMSDecoderJS::Init() { + nsresult rv; + nsCOMPtr 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(arg); + self->mDecryptedData.AppendElements(input, length); +} + +NS_IMETHODIMP nsCMSDecoderJS::Decrypt(const nsTArray& aInput, + nsTArray& _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 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(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 m_ctx; + NSSCMSMessage* m_cmsMsg; + NSSCMSSignerInfo* GetTopLevelSignerInfo(); + nsresult CommonVerifySignature(int32_t verifyFlags, + const nsTArray& aDigestData, + int16_t aDigestType); + + nsresult CommonAsyncVerifySignature(int32_t verifyFlags, + nsISMimeVerificationListener* aListener, + const nsTArray& 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 m_ctx; + NSSCMSDecoderContext* m_dcx; +}; + +class nsCMSDecoderJS : public nsICMSDecoderJS { + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSICMSDECODERJS + + nsCMSDecoderJS(); + nsresult Init(); + + private: + virtual ~nsCMSDecoderJS(); + nsCOMPtr m_ctx; + NSSCMSDecoderContext* m_dcx; + + nsTArray mDecryptedData; + nsCOMPtr 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 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 + +#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 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 certdb = + do_GetService("@mozilla.org/security/x509certdb;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsTArray certBytes; + rv = aCert->GetRawDER(certBytes); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr certVerifier(GetDefaultCertVerifier()); + NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED); + + nsTArray> 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(aUtf8ExpiredString.get()), + const_cast(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 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 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(¬Before); + 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(¬After); + 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 psm = do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +NS_IMETHODIMP +nsCertPicker::PickCertificate(nsIInterfaceRequestor* ctx, + const nsTArray& certNickList, + const nsTArray& 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 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 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 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 certNicknameList(nicknames->numnicknames); + nsTArray 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 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 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 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 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 decrypt(in Array 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 aDigestData, + in int16_t aDigestType); + void CreateEncrypted(in Array aRecipientCerts); + + /* The parameter aDigestType must be one of the values in nsICryptoHash */ + void CreateSigned(in nsIX509Cert scert, in nsIX509Cert ecert, + in Array 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 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 certNickList, + in Array 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& 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 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 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(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(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 bundleSvc = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED); + + nsCOMPtr 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 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 cinfo = + do_CreateInstance(NS_CMSMESSAGE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr encoder = + do_CreateInstance(NS_CMSENCODER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + char* header = nullptr; + nsCOMPtr bundleSvc = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED); + + nsCOMPtr 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(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 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(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 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 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 ctx = new PipUIContext(); + CERTCertNicknames* result_unused = CERT_GetCertNicknames( + CERT_GetDefaultCertDB(), SEC_CERT_NICKNAMES_USER, ctx); + CERT_FreeNicknames(result_unused); + + nsTArray> builtChain; + if (!mEncryptionCertDBKey.IsEmpty()) { + res = certdb->FindCertByDBKey(mEncryptionCertDBKey, + getter_AddRefs(mSelfEncryptionCert)); + + if (NS_SUCCEEDED(res) && mSelfEncryptionCert) { + nsTArray 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 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 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 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(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(MK_MIME_ERROR_WRITING_FILE); + } + } +FAIL: + // XXX status sometimes has invalid nsresults like -1 or PR_GetError() + // assigned to it + return static_cast(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(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 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 mCert; + nsCOMPtr mListener; + + static mozilla::StaticMutex sMutex; +}; + +mozilla::StaticMutex FindSMimeCertTask::sMutex; + +void FindSMimeCertTask::CallCallback(nsresult rv) { + MOZ_ASSERT(NS_IsMainThread()); + nsCOMPtr cert; + nsCOMPtr 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 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 certBytes(node->cert->derCert.data, + node->cert->derCert.len); + nsTArray> 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&& 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 mListener; +}; + +FindSMimeCertTask::~FindSMimeCertTask() { + // Unless we already cleaned up inside CallCallback, we must release + // the listener on the main thread. + if (mListener && !NS_IsMainThread()) { + RefPtr 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 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& params, + nsAString& outString); + nsresult ExtractEncryptionState(nsIMsgIdentity* aIdentity, + nsIMsgCompFields* aComposeFields, + bool* aSignMessage, bool* aEncrypt); + + bool mSignMessage; + bool mAlwaysEncryptMessage; + mimeDeliveryCryptoState mCryptoState; + nsCOMPtr mStream; + int16_t mHashType; + nsCOMPtr mDataHash; + mozilla::UniquePtr mSigEncoder; + char* mMultipartSignedBoundary; + nsString mSigningCertName; + nsAutoCString mSigningCertDBKey; + nsCOMPtr mSelfSigningCert; + nsString mEncryptionCertName; + nsAutoCString mEncryptionCertDBKey; + nsCOMPtr mSelfEncryptionCert; + nsTArray> mCerts; + nsCOMPtr mEncryptionCinfo; + nsCOMPtr mEncryptionContext; + nsCOMPtr mSMIMEBundle; + + // Maps email address to nsIX509Cert.dbKey of a verified certificate. + nsTHashMap mValidCertForEmailAddr; + static mozilla::StaticMutex sMutex; + + mozilla::UniquePtr 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 -- cgit v1.2.3