diff options
Diffstat (limited to 'mailnews/extensions/smime')
40 files changed, 5128 insertions, 0 deletions
diff --git a/mailnews/extensions/smime/content/am-smime.js b/mailnews/extensions/smime/content/am-smime.js new file mode 100644 index 000000000..4a90d0cd7 --- /dev/null +++ b/mailnews/extensions/smime/content/am-smime.js @@ -0,0 +1,478 @@ +/* -*- Mode: Java; 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/. */ + +Components.utils.import("resource://gre/modules/Services.jsm"); + +var nsIX509CertDB = Components.interfaces.nsIX509CertDB; +var nsX509CertDBContractID = "@mozilla.org/security/x509certdb;1"; +var nsIX509Cert = Components.interfaces.nsIX509Cert; + +var email_recipient_cert_usage = 5; +var email_signing_cert_usage = 4; + +var gIdentity; +var gPref = null; +var gEncryptionCertName = null; +var gHiddenEncryptionPolicy = null; +var gEncryptionChoices = null; +var gSignCertName = null; +var gSignMessages = null; +var gEncryptAlways = null; +var gNeverEncrypt = null; +var gBundle = null; +var gBrandBundle; +var gSmimePrefbranch; +var gEncryptionChoicesLocked; +var gSigningChoicesLocked; +var kEncryptionCertPref = "identity.encryption_cert_name"; +var kSigningCertPref = "identity.signing_cert_name"; + +function onInit() +{ + smimeInitializeFields(); +} + +function smimeInitializeFields() +{ + // initialize all of our elements based on the current identity values.... + gEncryptionCertName = document.getElementById(kEncryptionCertPref); + gHiddenEncryptionPolicy = document.getElementById("identity.encryptionpolicy"); + gEncryptionChoices = document.getElementById("encryptionChoices"); + gSignCertName = document.getElementById(kSigningCertPref); + gSignMessages = document.getElementById("identity.sign_mail"); + gEncryptAlways = document.getElementById("encrypt_mail_always"); + gNeverEncrypt = document.getElementById("encrypt_mail_never"); + gBundle = document.getElementById("bundle_smime"); + gBrandBundle = document.getElementById("bundle_brand"); + + gEncryptionChoicesLocked = false; + gSigningChoicesLocked = false; + + if (!gIdentity) { + // The user is going to create a new identity. + // Set everything to default values. + // Do not take over the values from gAccount.defaultIdentity + // as the new identity is going to have a different mail address. + + gEncryptionCertName.value = ""; + gEncryptionCertName.nickname = ""; + gEncryptionCertName.dbKey = ""; + gSignCertName.value = ""; + gSignCertName.nickname = ""; + gSignCertName.dbKey = ""; + + gEncryptAlways.setAttribute("disabled", true); + gNeverEncrypt.setAttribute("disabled", true); + gSignMessages.setAttribute("disabled", true); + + gSignMessages.checked = false; + gEncryptionChoices.value = 0; + } + else { + var certdb = Components.classes[nsX509CertDBContractID].getService(nsIX509CertDB); + var x509cert = null; + + gEncryptionCertName.value = gIdentity.getUnicharAttribute("encryption_cert_name"); + gEncryptionCertName.dbKey = gIdentity.getCharAttribute("encryption_cert_dbkey"); + // If we succeed in looking up the certificate by the dbkey pref, then + // append the serial number " [...]" to the display value, and remember the + // nickname in a separate property. + try { + if (certdb && gEncryptionCertName.dbKey && + (x509cert = certdb.findCertByDBKey(gEncryptionCertName.dbKey))) { + gEncryptionCertName.value = x509cert.nickname + " [" + x509cert.serialNumber + "]"; + gEncryptionCertName.nickname = x509cert.nickname; + } + } catch(e) {} + + gEncryptionChoices.value = gIdentity.getIntAttribute("encryptionpolicy"); + + if (!gEncryptionCertName.value) { + gEncryptAlways.setAttribute("disabled", true); + gNeverEncrypt.setAttribute("disabled", true); + } + else { + enableEncryptionControls(true); + } + + gSignCertName.value = gIdentity.getUnicharAttribute("signing_cert_name"); + gSignCertName.dbKey = gIdentity.getCharAttribute("signing_cert_dbkey"); + x509cert = null; + // same procedure as with gEncryptionCertName (see above) + try { + if (certdb && gSignCertName.dbKey && + (x509cert = certdb.findCertByDBKey(gSignCertName.dbKey))) { + gSignCertName.value = x509cert.nickname + " [" + x509cert.serialNumber + "]"; + gSignCertName.nickname = x509cert.nickname; + } + } catch(e) {} + + gSignMessages.checked = gIdentity.getBoolAttribute("sign_mail"); + if (!gSignCertName.value) + { + gSignMessages.setAttribute("disabled", true); + } + else { + enableSigningControls(true); + } + } + + // Always start with enabling signing and encryption cert select buttons. + // This will keep the visibility of buttons in a sane state as user + // jumps from security panel of one account to another. + enableCertSelectButtons(); + + // Disable all locked elements on the panel + if (gIdentity) + onLockPreference(); +} + +function onPreInit(account, accountValues) +{ + gIdentity = account.defaultIdentity; +} + +function onSave() +{ + smimeSave(); +} + +function smimeSave() +{ + // find out which radio for the encryption radio group is selected and set that on our hidden encryptionChoice pref.... + var newValue = gEncryptionChoices.value; + gHiddenEncryptionPolicy.setAttribute('value', newValue); + gIdentity.setIntAttribute("encryptionpolicy", newValue); + gIdentity.setUnicharAttribute("encryption_cert_name", + gEncryptionCertName.nickname || gEncryptionCertName.value); + gIdentity.setCharAttribute("encryption_cert_dbkey", gEncryptionCertName.dbKey); + + gIdentity.setBoolAttribute("sign_mail", gSignMessages.checked); + gIdentity.setUnicharAttribute("signing_cert_name", + gSignCertName.nickname || gSignCertName.value); + gIdentity.setCharAttribute("signing_cert_dbkey", gSignCertName.dbKey); +} + +function smimeOnAcceptEditor() +{ + try { + if (!onOk()) + return false; + } + catch (ex) {} + + smimeSave(); + + return true; +} + +function onLockPreference() +{ + var initPrefString = "mail.identity"; + var finalPrefString; + + var allPrefElements = [ + { prefstring:"signingCertSelectButton", id:"signingCertSelectButton"}, + { prefstring:"encryptionCertSelectButton", id:"encryptionCertSelectButton"}, + { prefstring:"sign_mail", id:"identity.sign_mail"}, + { prefstring:"encryptionpolicy", id:"encryptionChoices"} + ]; + + finalPrefString = initPrefString + "." + gIdentity.key + "."; + gSmimePrefbranch = Services.prefs.getBranch(finalPrefString); + + disableIfLocked( allPrefElements ); +} + + +// Does the work of disabling an element given the array which contains xul id/prefstring pairs. +// Also saves the id/locked state in an array so that other areas of the code can avoid +// stomping on the disabled state indiscriminately. +function disableIfLocked( prefstrArray ) +{ + var i; + for (i=0; i<prefstrArray.length; i++) { + var id = prefstrArray[i].id; + var element = document.getElementById(id); + if (gSmimePrefbranch.prefIsLocked(prefstrArray[i].prefstring)) { + // If encryption choices radio group is locked, make sure the individual + // choices in the group are locked. Set a global (gEncryptionChoicesLocked) + // indicating the status so that locking can be maintained further. + if (id == "encryptionChoices") { + document.getElementById("encrypt_mail_never").setAttribute("disabled", "true"); + document.getElementById("encrypt_mail_always").setAttribute("disabled", "true"); + gEncryptionChoicesLocked = true; + } + // If option to sign mail is locked (with true/false set in config file), disable + // the corresponding checkbox and set a global (gSigningChoicesLocked) in order to + // honor the locking as user changes other elements on the panel. + if (id == "identity.sign_mail") { + document.getElementById("identity.sign_mail").setAttribute("disabled", "true"); + gSigningChoicesLocked = true; + } + else { + element.setAttribute("disabled", "true"); + if (id == "signingCertSelectButton") { + document.getElementById("signingCertClearButton").setAttribute("disabled", "true"); + } + else if (id == "encryptionCertSelectButton") { + document.getElementById("encryptionCertClearButton").setAttribute("disabled", "true"); + } + } + } + } +} + +function alertUser(message) +{ + Services.prompt.alert(window, + gBrandBundle.getString("brandShortName"), + message); +} + +function askUser(message) +{ + let button = Services.prompt.confirmEx( + window, + gBrandBundle.getString("brandShortName"), + message, + Services.prompt.STD_YES_NO_BUTTONS, + null, + null, + null, + null, + {}); + // confirmEx returns button index: + return (button == 0); +} + +function checkOtherCert(cert, pref, usage, msgNeedCertWantSame, msgWantSame, msgNeedCertWantToSelect, enabler) +{ + var otherCertInfo = document.getElementById(pref); + if (!otherCertInfo) + return; + + if (otherCertInfo.dbKey == cert.dbKey) + // all is fine, same cert is now selected for both purposes + return; + + var certdb = Components.classes[nsX509CertDBContractID].getService(nsIX509CertDB); + if (!certdb) + return; + + if (email_recipient_cert_usage == usage) { + matchingOtherCert = certdb.findEmailEncryptionCert(cert.nickname); + } + else if (email_signing_cert_usage == usage) { + matchingOtherCert = certdb.findEmailSigningCert(cert.nickname); + } + else + return; + + var userWantsSameCert = false; + + if (!otherCertInfo.value.length) { + if (matchingOtherCert && (matchingOtherCert.dbKey == cert.dbKey)) { + userWantsSameCert = askUser(gBundle.getString(msgNeedCertWantSame)); + } + else { + if (askUser(gBundle.getString(msgNeedCertWantToSelect))) { + smimeSelectCert(pref); + } + } + } + else { + if (matchingOtherCert && (matchingOtherCert.dbKey == cert.dbKey)) { + userWantsSameCert = askUser(gBundle.getString(msgWantSame)); + } + } + + if (userWantsSameCert) { + otherCertInfo.value = cert.nickname + " [" + cert.serialNumber + "]"; + otherCertInfo.nickname = cert.nickname; + otherCertInfo.dbKey = cert.dbKey; + enabler(true); + } +} + +function smimeSelectCert(smime_cert) +{ + var certInfo = document.getElementById(smime_cert); + if (!certInfo) + return; + + var picker = Components.classes["@mozilla.org/user_cert_picker;1"] + .createInstance(Components.interfaces.nsIUserCertPicker); + var canceled = new Object; + var x509cert = 0; + var certUsage; + var selectEncryptionCert; + + if (smime_cert == kEncryptionCertPref) { + selectEncryptionCert = true; + certUsage = email_recipient_cert_usage; + } else if (smime_cert == kSigningCertPref) { + selectEncryptionCert = false; + certUsage = email_signing_cert_usage; + } + + try { + x509cert = picker.pickByUsage(window, + certInfo.value, + certUsage, // this is from enum SECCertUsage + false, true, + gIdentity.email, + canceled); + } catch(e) { + canceled.value = false; + x509cert = null; + } + + if (!canceled.value) { + if (!x509cert) { + if (gIdentity.email) { + alertUser(gBundle.getFormattedString(selectEncryptionCert ? + "NoEncryptionCertForThisAddress" : + "NoSigningCertForThisAddress", + [ gIdentity.email ])); + } else { + alertUser(gBundle.getString(selectEncryptionCert ? + "NoEncryptionCert" : "NoSigningCert")); + } + } + else { + certInfo.removeAttribute("disabled"); + certInfo.value = x509cert.nickname + " [" + x509cert.serialNumber + "]"; + certInfo.nickname = x509cert.nickname; + certInfo.dbKey = x509cert.dbKey; + + if (selectEncryptionCert) { + enableEncryptionControls(true); + + checkOtherCert(x509cert, + kSigningCertPref, email_signing_cert_usage, + "signing_needCertWantSame", + "signing_wantSame", + "signing_needCertWantToSelect", + enableSigningControls); + } else { + enableSigningControls(true); + + checkOtherCert(x509cert, + kEncryptionCertPref, email_recipient_cert_usage, + "encryption_needCertWantSame", + "encryption_wantSame", + "encryption_needCertWantToSelect", + enableEncryptionControls); + } + } + } + + enableCertSelectButtons(); +} + +function enableEncryptionControls(do_enable) +{ + if (gEncryptionChoicesLocked) + return; + + if (do_enable) { + gEncryptAlways.removeAttribute("disabled"); + gNeverEncrypt.removeAttribute("disabled"); + gEncryptionCertName.removeAttribute("disabled"); + } + else { + gEncryptAlways.setAttribute("disabled", "true"); + gNeverEncrypt.setAttribute("disabled", "true"); + gEncryptionCertName.setAttribute("disabled", "true"); + gEncryptionChoices.value = 0; + } +} + +function enableSigningControls(do_enable) +{ + if (gSigningChoicesLocked) + return; + + if (do_enable) { + gSignMessages.removeAttribute("disabled"); + gSignCertName.removeAttribute("disabled"); + } + else { + gSignMessages.setAttribute("disabled", "true"); + gSignCertName.setAttribute("disabled", "true"); + gSignMessages.checked = false; + } +} + +function enableCertSelectButtons() +{ + document.getElementById("signingCertSelectButton").removeAttribute("disabled"); + + if (document.getElementById('identity.signing_cert_name').value.length) + document.getElementById("signingCertClearButton").removeAttribute("disabled"); + else + document.getElementById("signingCertClearButton").setAttribute("disabled", "true"); + + document.getElementById("encryptionCertSelectButton").removeAttribute("disabled"); + + if (document.getElementById('identity.encryption_cert_name').value.length) + document.getElementById("encryptionCertClearButton").removeAttribute("disabled"); + else + document.getElementById("encryptionCertClearButton").setAttribute("disabled", "true"); +} + +function smimeClearCert(smime_cert) +{ + var certInfo = document.getElementById(smime_cert); + if (!certInfo) + return; + + certInfo.setAttribute("disabled", "true"); + certInfo.value = ""; + certInfo.nickname = ""; + certInfo.dbKey = ""; + + if (smime_cert == kEncryptionCertPref) { + enableEncryptionControls(false); + } else if (smime_cert == kSigningCertPref) { + enableSigningControls(false); + } + + enableCertSelectButtons(); +} + +function openCertManager() +{ + // Check for an existing certManager window and focus it; it's not + // application modal. + let lastCertManager = Services.wm.getMostRecentWindow("mozilla:certmanager"); + if (lastCertManager) + lastCertManager.focus(); + else + window.openDialog("chrome://pippki/content/certManager.xul", "", + "centerscreen,resizable=yes,dialog=no"); +} + +function openDeviceManager() +{ + // Check for an existing deviceManager window and focus it; it's not + // application modal. + let lastCertManager = Services.wm.getMostRecentWindow("mozilla:devicemanager"); + if (lastCertManager) + lastCertManager.focus(); + else + window.openDialog("chrome://pippki/content/device_manager.xul", "", + "centerscreen,resizable=yes,dialog=no"); +} + +function smimeOnLoadEditor() +{ + smimeInitializeFields(); + + document.documentElement.setAttribute("ondialogaccept", + "return smimeOnAcceptEditor();"); +} + diff --git a/mailnews/extensions/smime/content/am-smime.xul b/mailnews/extensions/smime/content/am-smime.xul new file mode 100644 index 000000000..bb46bb49d --- /dev/null +++ b/mailnews/extensions/smime/content/am-smime.xul @@ -0,0 +1,26 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?> + +<?xul-overlay href="chrome://messenger/content/am-smimeOverlay.xul"?> + +<!DOCTYPE page SYSTEM "chrome://messenger/locale/am-smime.dtd"> + +<page xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + class="color-dialog" + onload="parent.onPanelLoaded('am-smime.xul');" + ondialogaccept="smimeOnAcceptEditor();"> + + <vbox flex="1" style="overflow: auto;"> + <script type="application/javascript" src="chrome://messenger/content/AccountManager.js"/> + <script type="application/javascript" src="chrome://messenger/content/am-smime.js"/> + + <dialogheader title="&securityTitle.label;"/> + + <vbox flex="1" id="smimeEditing"/> + </vbox> + +</page> diff --git a/mailnews/extensions/smime/content/am-smimeIdentityEditOverlay.xul b/mailnews/extensions/smime/content/am-smimeIdentityEditOverlay.xul new file mode 100644 index 000000000..2ff5c2b7d --- /dev/null +++ b/mailnews/extensions/smime/content/am-smimeIdentityEditOverlay.xul @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" + type="text/css"?> + +<?xul-overlay href="chrome://messenger/content/am-smimeOverlay.xul"?> + +<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/am-smime.dtd"> + +<!-- + This is the overlay that adds the SMIME configurator + to the identity editor of the account manager +--> +<overlay id="smimeAmIdEditOverlay" + xmlns:html="http://www.w3.org/1999/xhtml" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" + src="chrome://messenger/content/AccountManager.js"/> + <script type="application/javascript" + src="chrome://messenger/content/am-smime.js"/> + + <tabs id="identitySettings"> + <tab label="&securityTab.label;"/> + </tabs> + + <tabpanels id="identityTabsPanels"> + <vbox flex="1" name="smimeEditingContent" id="smimeEditing"/> + </tabpanels> + + <script type="application/javascript"> + <![CDATA[ + window.addEventListener("load", smimeOnLoadEditor, false); + ]]> + </script> +</overlay> diff --git a/mailnews/extensions/smime/content/am-smimeOverlay.xul b/mailnews/extensions/smime/content/am-smimeOverlay.xul new file mode 100644 index 000000000..eb76b4b2c --- /dev/null +++ b/mailnews/extensions/smime/content/am-smimeOverlay.xul @@ -0,0 +1,102 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" + type="text/css"?> + +<!DOCTYPE overlay SYSTEM "chrome://messenger/locale/am-smime.dtd"> + +<overlay xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <vbox id="smimeEditing"> + + <stringbundleset> + <stringbundle id="bundle_smime" src="chrome://messenger/locale/am-smime.properties"/> + <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/> + </stringbundleset> + + <label hidden="true" wsm_persist="true" id="identity.encryptionpolicy"/> + + <description>&securityHeading.label;</description> + + <groupbox id="signing.titlebox"> + <caption label="&signingGroupTitle.label;"/> + + <label value="&signingCert.message;" control="identity.signing_cert_name" + prefstring="mail.identity.%identitykey%.encryptionpolicy"/> + + <hbox align="center"> + <textbox id="identity.signing_cert_name" wsm_persist="true" flex="1" + prefstring="mail.identity.%identitykey%.signing_cert_name" + readonly="true" disabled="true"/> + + <button id="signingCertSelectButton" + label="&digitalSign.certificate.button;" + accesskey="&digitalSign.certificate.accesskey;" + oncommand="smimeSelectCert('identity.signing_cert_name')"/> + + <button id="signingCertClearButton" + label="&digitalSign.certificate_clear.button;" + accesskey="&digitalSign.certificate_clear.accesskey;" + oncommand="smimeClearCert('identity.signing_cert_name')"/> + </hbox> + + <separator class="thin"/> + + <checkbox id="identity.sign_mail" wsm_persist="true" + prefstring="mail.identity.%identitykey%.sign_mail" + label="&signMessage.label;" accesskey="&signMessage.accesskey;"/> + </groupbox> + + <groupbox id="encryption.titlebox"> + <caption label="&encryptionGroupTitle.label;"/> + + <label value="&encryptionCert.message;" + control="identity.encryption_cert_name"/> + + <hbox align="center"> + <textbox id="identity.encryption_cert_name" wsm_persist="true" flex="1" + prefstring="mail.identity.%identitykey%.encryption_cert_name" + readonly="true" disabled="true"/> + + <button id="encryptionCertSelectButton" + label="&encryption.certificate.button;" + accesskey="&encryption.certificate.accesskey;" + oncommand="smimeSelectCert('identity.encryption_cert_name')"/> + + <button id="encryptionCertClearButton" + label="&encryption.certificate_clear.button;" + accesskey="&encryption.certificate_clear.accesskey;" + oncommand="smimeClearCert('identity.encryption_cert_name')"/> + </hbox> + + <separator class="thin"/> + + <label value="&encryptionChoiceLabel.label;" control="encryptionChoices"/> + + <radiogroup id="encryptionChoices"> + <radio id="encrypt_mail_never" wsm_persist="true" value="0" + label="&neverEncrypt.label;" + accesskey="&neverEncrypt.accesskey;"/> + + <radio id="encrypt_mail_always" wsm_persist="true" value="2" + label="&alwaysEncryptMessage.label;" + accesskey="&alwaysEncryptMessage.accesskey;"/> + </radiogroup> + </groupbox> + + <!-- Certificate manager --> + <groupbox id="smimeCertificateManager" orient="horizontal"> + <caption label="&certificates.label;"/> + <button id="openCertManagerButton" oncommand="openCertManager();" + label="&manageCerts2.label;" accesskey="&manageCerts2.accesskey;" + prefstring="security.disable_button.openCertManager"/> + <button id="openDeviceManagerButton" oncommand="openDeviceManager();" + label="&manageDevices.label;" accesskey="&manageDevices.accesskey;" + prefstring="security.disable_button.openDeviceManager"/> + </groupbox> + </vbox> +</overlay> diff --git a/mailnews/extensions/smime/content/certFetchingStatus.js b/mailnews/extensions/smime/content/certFetchingStatus.js new file mode 100644 index 000000000..8848ff9b6 --- /dev/null +++ b/mailnews/extensions/smime/content/certFetchingStatus.js @@ -0,0 +1,265 @@ +/* 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/. */ + +/* We expect 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. +*/ + +Components.utils.import("resource://gre/modules/Services.jsm"); + +var nsIX509CertDB = Components.interfaces.nsIX509CertDB; +var nsX509CertDB = "@mozilla.org/security/x509certdb;1"; +var CertAttribute = "usercertificate;binary"; + +var gEmailAddresses; +var gDirectoryPref; +var gLdapServerURL; +var gLdapConnection; +var gCertDB; +var gLdapOperation; +var gLogin; + +function onLoad() +{ + gDirectoryPref = window.arguments[0]; + gEmailAddresses = window.arguments[1]; + + if (!gEmailAddresses.length) + { + window.close(); + return; + } + + setTimeout(search, 1); +} + +function search() +{ + // get the login to authenticate as, if there is one + try { + gLogin = Services.prefs.getComplexValue(gDirectoryPref + ".auth.dn", Components.interfaces.nsISupportsString).data; + } catch (ex) { + // if we don't have this pref, no big deal + } + + try { + let url = Services.prefs.getCharPref(gDirectoryPref + ".uri"); + + gLdapServerURL = Services.io + .newURI(url, null, null).QueryInterface(Components.interfaces.nsILDAPURL); + + gLdapConnection = Components.classes["@mozilla.org/network/ldap-connection;1"] + .createInstance().QueryInterface(Components.interfaces.nsILDAPConnection); + + gLdapConnection.init(gLdapServerURL, gLogin, new boundListener(), + null, Components.interfaces.nsILDAPConnection.VERSION3); + + } catch (ex) { + dump(ex); + dump(" exception creating ldap connection\n"); + window.close(); + } +} + +function stopFetching() +{ + if (gLdapOperation) { + try { + gLdapOperation.abandon(); + } + catch (e) { + } + } + return true; +} + +function importCert(ber_value) +{ + if (!gCertDB) { + gCertDB = Components.classes[nsX509CertDB].getService(nsIX509CertDB); + } + + var cert_length = new Object(); + var cert_bytes = ber_value.get(cert_length); + + if (cert_bytes) { + gCertDB.importEmailCertificate(cert_bytes, cert_length.value, null); + } +} + +function getLDAPOperation() +{ + gLdapOperation = Components.classes["@mozilla.org/network/ldap-operation;1"] + .createInstance().QueryInterface(Components.interfaces.nsILDAPOperation); + + gLdapOperation.init(gLdapConnection, + new ldapMessageListener(), + null); +} + +function getPassword() +{ + // we only need a password if we are using credentials + if (gLogin) + { + let authPrompter = Services.ww.getNewAuthPrompter(window.QueryInterface(Components.interfaces.nsIDOMWindow)); + 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 (authPrompter.promptPassword(strBundle.getString("authPromptTitle"), + strBundle.getFormattedString("authPromptText", [gLdapServerURL.asciiHost]), + gLdapServerURL.spec, + authPrompter.SAVE_PASSWORD_PERMANENTLY, + password)) + return password.value; + } + + return null; +} + +function kickOffBind() +{ + try { + getLDAPOperation(); + gLdapOperation.simpleBind(getPassword()); + } + catch (e) { + window.close(); + } +} + +function kickOffSearch() +{ + try { + var prefix1 = ""; + var suffix1 = ""; + + var urlFilter = gLdapServerURL.filter; + + if (urlFilter != null && urlFilter.length > 0 && urlFilter != "(objectclass=*)") { + if (urlFilter.startsWith('(')) { + prefix1 = "(&" + urlFilter; + } + else { + prefix1 = "(&(" + urlFilter + ")"; + } + suffix1 = ")"; + } + + var prefix2 = ""; + var suffix2 = ""; + + if (gEmailAddresses.length > 1) { + prefix2 = "(|"; + suffix2 = ")"; + } + + var mailFilter = ""; + + for (var i = 0; i < gEmailAddresses.length; ++i) { + mailFilter += "(mail=" + gEmailAddresses[i] + ")"; + } + + var filter = prefix1 + prefix2 + mailFilter + suffix2 + suffix1; + + var wanted_attributes = CertAttribute; + + // 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??? + + var maxEntriesWanted = gEmailAddresses.length * 2; + + getLDAPOperation(); + gLdapOperation.searchExt(gLdapServerURL.dn, gLdapServerURL.scope, + filter, wanted_attributes, 0, maxEntriesWanted); + } + catch (e) { + window.close(); + } +} + + +function boundListener() { +} + +boundListener.prototype.QueryInterface = + function(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsILDAPMessageListener)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + } + +boundListener.prototype.onLDAPMessage = + function(aMessage) { + } + +boundListener.prototype.onLDAPInit = + function(aConn, aStatus) { + kickOffBind(); + } + + +function ldapMessageListener() { +} + +ldapMessageListener.prototype.QueryInterface = + function(iid) { + if (iid.equals(Components.interfaces.nsISupports) || + iid.equals(Components.interfaces.nsILDAPMessageListener)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + } + +ldapMessageListener.prototype.onLDAPMessage = + function(aMessage) { + if (Components.interfaces.nsILDAPMessage.RES_SEARCH_RESULT == aMessage.type) { + window.close(); + return; + } + + if (Components.interfaces.nsILDAPMessage.RES_BIND == aMessage.type) { + if (Components.interfaces.nsILDAPErrors.SUCCESS != aMessage.errorCode) { + window.close(); + } + else { + kickOffSearch(); + } + return; + } + + if (Components.interfaces.nsILDAPMessage.RES_SEARCH_ENTRY == aMessage.type) { + var outSize = new Object(); + try { + var outBinValues = aMessage.getBinaryValues(CertAttribute, outSize); + + var i; + for (i=0; i < outSize.value; ++i) { + importCert(outBinValues[i]); + } + } + catch (e) { + } + return; + } + } + +ldapMessageListener.prototype.onLDAPInit = + function(aConn, aStatus) { + } diff --git a/mailnews/extensions/smime/content/certFetchingStatus.xul b/mailnews/extensions/smime/content/certFetchingStatus.xul new file mode 100644 index 000000000..29b824fc9 --- /dev/null +++ b/mailnews/extensions/smime/content/certFetchingStatus.xul @@ -0,0 +1,24 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/smime/certFetchingStatus.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://messenger-smime/locale/certFetchingStatus.dtd"> + +<dialog id="certFetchingStatus" title="&title.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="width: 50em;" + buttons="cancel" + buttonlabelcancel="&stop.label;" + ondialogcancel="return stopFetching();" + onload="onLoad();"> + + <stringbundle id="bundle_ldap" src="chrome://mozldap/locale/ldap.properties"/> +<script type="application/javascript" src="chrome://messenger-smime/content/certFetchingStatus.js"/> + + <description>&info.message;</description> + +</dialog> diff --git a/mailnews/extensions/smime/content/certpicker.js b/mailnews/extensions/smime/content/certpicker.js new file mode 100644 index 000000000..19554066f --- /dev/null +++ b/mailnews/extensions/smime/content/certpicker.js @@ -0,0 +1,73 @@ +/* -*- 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"; + +const nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock; + +var dialogParams; +var itemCount = 0; + +function onLoad() +{ + dialogParams = window.arguments[0].QueryInterface(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.createElement("menuitem"); + let nick = dialogParams.GetString(i); + menuItemNode.setAttribute("value", i); + menuItemNode.setAttribute("label", nick); // This is displayed. + selectElement.firstChild.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); + return true; +} + +function doCancel() +{ + dialogParams.SetInt(0, 0); // Signal that the user cancelled. + return true; +} diff --git a/mailnews/extensions/smime/content/certpicker.xul b/mailnews/extensions/smime/content/certpicker.xul new file mode 100644 index 000000000..2c4cd3b22 --- /dev/null +++ b/mailnews/extensions/smime/content/certpicker.xul @@ -0,0 +1,38 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> + +<!DOCTYPE dialog [ +<!ENTITY % amSMIMEDTD SYSTEM "chrome://messenger/locale/am-smime.dtd" > +%amSMIMEDTD; +]> + +<dialog id="certPicker" title="&certPicker.title;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="width: 50em;" + buttons="accept,cancel" + ondialogaccept="return doOK();" + ondialogcancel="return doCancel();" + onload="onLoad();"> + +<script type="application/javascript" + src="chrome://messenger/content/certpicker.js"/> + + <hbox align="center"> + <broadcaster id="certSelected" oncommand="onCertSelected();"/> + <label id="pickerInfo" value="&certPicker.info;"/> + <!-- The items in this menulist must never be sorted, + but remain in the order filled by the application + --> + <menulist id="nicknames" observes="certSelected"> + <menupopup/> + </menulist> + </hbox> + <separator class="thin"/> + <label value="&certPicker.detailsLabel;"/> + <textbox readonly="true" id="details" multiline="true" + style="height: 12em;" flex="1"/> +</dialog> diff --git a/mailnews/extensions/smime/content/msgCompSMIMEOverlay.js b/mailnews/extensions/smime/content/msgCompSMIMEOverlay.js new file mode 100644 index 000000000..582a073ea --- /dev/null +++ b/mailnews/extensions/smime/content/msgCompSMIMEOverlay.js @@ -0,0 +1,357 @@ +/* -*- Mode: Java; 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/. */ + +Components.utils.import("resource://gre/modules/Services.jsm"); + +// Account encryption policy values: +// const kEncryptionPolicy_Never = 0; +// 'IfPossible' was used by ns4. +// const kEncryptionPolicy_IfPossible = 1; +var kEncryptionPolicy_Always = 2; + +var gEncryptedURIService = + Components.classes["@mozilla.org/messenger-smime/smime-encrypted-uris-service;1"] + .getService(Components.interfaces.nsIEncryptedSMIMEURIsService); + +var gNextSecurityButtonCommand = ""; +var gSMFields = null; +var gEncryptOptionChanged; +var gSignOptionChanged; + +function onComposerLoad() +{ + // Are we already set up ? Or are the required fields missing ? + if (gSMFields || !gMsgCompose || !gMsgCompose.compFields) + return; + + gMsgCompose.compFields.securityInfo = null; + + gSMFields = Components.classes["@mozilla.org/messenger-smime/composefields;1"] + .createInstance(Components.interfaces.nsIMsgSMIMECompFields); + if (!gSMFields) + return; + + gMsgCompose.compFields.securityInfo = gSMFields; + + // Set up the intial security state. + gSMFields.requireEncryptMessage = + gCurrentIdentity.getIntAttribute("encryptionpolicy") == kEncryptionPolicy_Always; + if (!gSMFields.requireEncryptMessage && + gEncryptedURIService && + gEncryptedURIService.isEncrypted(gMsgCompose.originalMsgURI)) + { + // Override encryption setting if original is known as encrypted. + gSMFields.requireEncryptMessage = true; + } + if (gSMFields.requireEncryptMessage) + setEncryptionUI(); + else + setNoEncryptionUI(); + + gSMFields.signMessage = gCurrentIdentity.getBoolAttribute("sign_mail"); + if (gSMFields.signMessage) + setSignatureUI(); + else + setNoSignatureUI(); +} + +addEventListener("load", smimeComposeOnLoad, {capture: false, once: true}); + +// this function gets called multiple times +function smimeComposeOnLoad() +{ + onComposerLoad(); + + top.controllers.appendController(SecurityController); + + addEventListener("compose-from-changed", onComposerFromChanged, true); + addEventListener("compose-send-message", onComposerSendMessage, true); + + addEventListener("unload", smimeComposeOnUnload, {capture: false, once: true}); +} + +function smimeComposeOnUnload() +{ + removeEventListener("compose-from-changed", onComposerFromChanged, true); + removeEventListener("compose-send-message", onComposerSendMessage, true); + + top.controllers.removeController(SecurityController); +} + +function showNeedSetupInfo() +{ + let compSmimeBundle = document.getElementById("bundle_comp_smime"); + let brandBundle = document.getElementById("bundle_brand"); + if (!compSmimeBundle || !brandBundle) + return; + + let buttonPressed = Services.prompt.confirmEx(window, + brandBundle.getString("brandShortName"), + compSmimeBundle.getString("NeedSetup"), + Services.prompt.STD_YES_NO_BUTTONS, 0, 0, 0, null, {}); + if (buttonPressed == 0) + openHelp("sign-encrypt", "chrome://communicator/locale/help/suitehelp.rdf"); +} + +function toggleEncryptMessage() +{ + if (!gSMFields) + return; + + gSMFields.requireEncryptMessage = !gSMFields.requireEncryptMessage; + + if (gSMFields.requireEncryptMessage) + { + // Make sure we have a cert. + if (!gCurrentIdentity.getUnicharAttribute("encryption_cert_name")) + { + gSMFields.requireEncryptMessage = false; + showNeedSetupInfo(); + return; + } + + setEncryptionUI(); + } + else + { + setNoEncryptionUI(); + } + + gEncryptOptionChanged = true; +} + +function toggleSignMessage() +{ + if (!gSMFields) + return; + + gSMFields.signMessage = !gSMFields.signMessage; + + if (gSMFields.signMessage) // make sure we have a cert name... + { + if (!gCurrentIdentity.getUnicharAttribute("signing_cert_name")) + { + gSMFields.signMessage = false; + showNeedSetupInfo(); + return; + } + + setSignatureUI(); + } + else + { + setNoSignatureUI(); + } + + gSignOptionChanged = true; +} + +function setSecuritySettings(menu_id) +{ + if (!gSMFields) + return; + + document.getElementById("menu_securityEncryptRequire" + menu_id) + .setAttribute("checked", gSMFields.requireEncryptMessage); + document.getElementById("menu_securitySign" + menu_id) + .setAttribute("checked", gSMFields.signMessage); +} + +function setNextCommand(what) +{ + gNextSecurityButtonCommand = what; +} + +function doSecurityButton() +{ + var what = gNextSecurityButtonCommand; + gNextSecurityButtonCommand = ""; + + switch (what) + { + case "encryptMessage": + toggleEncryptMessage(); + break; + + case "signMessage": + toggleSignMessage(); + break; + + case "show": + default: + showMessageComposeSecurityStatus(); + } +} + +function setNoSignatureUI() +{ + top.document.getElementById("securityStatus").removeAttribute("signing"); + top.document.getElementById("signing-status").collapsed = true; +} + +function setSignatureUI() +{ + top.document.getElementById("securityStatus").setAttribute("signing", "ok"); + top.document.getElementById("signing-status").collapsed = false; +} + +function setNoEncryptionUI() +{ + top.document.getElementById("securityStatus").removeAttribute("crypto"); + top.document.getElementById("encryption-status").collapsed = true; +} + +function setEncryptionUI() +{ + top.document.getElementById("securityStatus").setAttribute("crypto", "ok"); + top.document.getElementById("encryption-status").collapsed = false; +} + +function showMessageComposeSecurityStatus() +{ + Recipients2CompFields(gMsgCompose.compFields); + + window.openDialog( + "chrome://messenger-smime/content/msgCompSecurityInfo.xul", + "", + "chrome,modal,resizable,centerscreen", + { + compFields : gMsgCompose.compFields, + subject : GetMsgSubjectElement().value, + smFields : gSMFields, + isSigningCertAvailable : + gCurrentIdentity.getUnicharAttribute("signing_cert_name") != "", + isEncryptionCertAvailable : + gCurrentIdentity.getUnicharAttribute("encryption_cert_name") != "", + currentIdentity : gCurrentIdentity + } + ); +} + +var SecurityController = +{ + supportsCommand: function(command) + { + switch (command) + { + case "cmd_viewSecurityStatus": + return true; + + default: + return false; + } + }, + + isCommandEnabled: function(command) + { + switch (command) + { + case "cmd_viewSecurityStatus": + return true; + + default: + return false; + } + } +}; + +function onComposerSendMessage() +{ + let missingCount = new Object(); + let emailAddresses = new Object(); + + try + { + if (!gMsgCompose.compFields.securityInfo.requireEncryptMessage) + return; + + Components.classes["@mozilla.org/messenger-smime/smimejshelper;1"] + .createInstance(Components.interfaces.nsISMimeJSHelper) + .getNoCertAddresses(gMsgCompose.compFields, + missingCount, + emailAddresses); + } + catch (e) + { + return; + } + + if (missingCount.value > 0) + { + // The rules here: If the current identity has a directoryServer set, then + // use that, otherwise, try the global preference instead. + + let autocompleteDirectory; + + // Does the current identity override the global preference? + if (gCurrentIdentity.overrideGlobalPref) + { + autocompleteDirectory = gCurrentIdentity.directoryServer; + } + else + { + // Try the global one + if (Services.prefs.getBoolPref("ldap_2.autoComplete.useDirectory")) + autocompleteDirectory = + Services.prefs.getCharPref("ldap_2.autoComplete.directoryServer"); + } + + if (autocompleteDirectory) + window.openDialog("chrome://messenger-smime/content/certFetchingStatus.xul", + "", + "chrome,modal,resizable,centerscreen", + autocompleteDirectory, + emailAddresses.value); + } +} + +function onComposerFromChanged() +{ + if (!gSMFields) + return; + + var encryptionPolicy = gCurrentIdentity.getIntAttribute("encryptionpolicy"); + var useEncryption = false; + + if (!gEncryptOptionChanged) + { + // Encryption wasn't manually checked. + // Set up the encryption policy from the setting of the new identity. + + // 0 == never, 1 == if possible (ns4), 2 == always encrypt. + useEncryption = (encryptionPolicy == kEncryptionPolicy_Always); + } + else + { + useEncryption = !!gCurrentIdentity.getUnicharAttribute("encryption_cert_name"); + } + + gSMFields.requireEncryptMessage = useEncryption; + if (useEncryption) + setEncryptionUI(); + else + setNoEncryptionUI(); + + // - If signing is disabled, we will not turn it on automatically. + // - If signing is enabled, but the new account defaults to not sign, we will turn signing off. + var signMessage = gCurrentIdentity.getBoolAttribute("sign_mail"); + var useSigning = false; + + if (!gSignOptionChanged) + { + // Signing wasn't manually checked. + // Set up the signing policy from the setting of the new identity. + useSigning = signMessage; + } + else + { + useSigning = !!gCurrentIdentity.getUnicharAttribute("signing_cert_name"); + } + gSMFields.signMessage = useSigning; + if (useSigning) + setSignatureUI(); + else + setNoSignatureUI(); +} diff --git a/mailnews/extensions/smime/content/msgCompSMIMEOverlay.xul b/mailnews/extensions/smime/content/msgCompSMIMEOverlay.xul new file mode 100644 index 000000000..ec6495e20 --- /dev/null +++ b/mailnews/extensions/smime/content/msgCompSMIMEOverlay.xul @@ -0,0 +1,85 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + + +<?xml-stylesheet href="chrome://messenger/skin/smime/msgCompSMIMEOverlay.css" type="text/css"?> + +<!DOCTYPE overlay SYSTEM "chrome://messenger-smime/locale/msgCompSMIMEOverlay.dtd"> + +<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://messenger-smime/content/msgCompSMIMEOverlay.js"/> + + <window id="msgcomposeWindow"> + <broadcaster id="securityStatus" crypto="" signing=""/> + <observes element="securityStatus" attribute="crypto" /> + <observes element="securityStatus" attribute="signing" /> + <stringbundle id="bundle_comp_smime" src="chrome://messenger-smime/locale/msgCompSMIMEOverlay.properties"/> + <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/> + </window> + + <menupopup id="optionsMenuPopup" + onpopupshowing="setSecuritySettings(1);"> + <menuseparator id="smimeOptionsSeparator"/> + + <menuitem id="menu_securityEncryptRequire1" + type="checkbox" + label="&menu_securityEncryptRequire.label;" + accesskey="&menu_securityEncryptRequire.accesskey;" + oncommand="toggleEncryptMessage();"/> + <menuitem id="menu_securitySign1" + type="checkbox" + label="&menu_securitySign.label;" + accesskey="&menu_securitySign.accesskey;" + oncommand="toggleSignMessage();"/> + </menupopup> + + <toolbarpalette id="MsgComposeToolbarPalette"> + <toolbarbutton id="button-security" + type="menu-button" + class="toolbarbutton-1" + label="&securityButton.label;" + tooltiptext="&securityButton.tooltip;" + oncommand="doSecurityButton();"> + <menupopup onpopupshowing="setSecuritySettings(2);"> + <menuitem id="menu_securityEncryptRequire2" + type="checkbox" + label="&menu_securityEncryptRequire.label;" + accesskey="&menu_securityEncryptRequire.accesskey;" + oncommand="setNextCommand('encryptMessage');"/> + <menuitem id="menu_securitySign2" + type="checkbox" + label="&menu_securitySign.label;" + accesskey="&menu_securitySign.accesskey;" + oncommand="setNextCommand('signMessage');"/> + <menuseparator id="smimeToolbarButtonSeparator"/> + <menuitem id="menu_securityStatus2" + label="&menu_securityStatus.label;" + accesskey="&menu_securityStatus.accesskey;" + oncommand="setNextCommand('show');"/> + </menupopup> + </toolbarbutton> + </toolbarpalette> + + <statusbar id="status-bar"> + <statusbarpanel insertbefore="offline-status" class="statusbarpanel-iconic" collapsed="true" + id="signing-status" oncommand="showMessageComposeSecurityStatus();"/> + <statusbarpanel insertbefore="offline-status" class="statusbarpanel-iconic" collapsed="true" + id="encryption-status" oncommand="showMessageComposeSecurityStatus();"/> + </statusbar> + + <commandset id="composeCommands"> + <command id="cmd_viewSecurityStatus" oncommand="showMessageComposeSecurityStatus();"/> + </commandset> + + <menupopup id="menu_View_Popup"> + <menuseparator id="viewMenuBeforeSecurityStatusSeparator"/> + <menuitem id="menu_viewSecurityStatus" + label="&menu_viewSecurityStatus.label;" + accesskey="&menu_viewSecurityStatus.accesskey;" + command="cmd_viewSecurityStatus"/> + </menupopup> + +</overlay> diff --git a/mailnews/extensions/smime/content/msgCompSecurityInfo.js b/mailnews/extensions/smime/content/msgCompSecurityInfo.js new file mode 100644 index 000000000..5a2a7432f --- /dev/null +++ b/mailnews/extensions/smime/content/msgCompSecurityInfo.js @@ -0,0 +1,244 @@ +/* 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/. */ + +Components.utils.import("resource://gre/modules/Services.jsm"); + +var gListBox; +var gViewButton; +var gBundle; + +var gEmailAddresses; +var gCertStatusSummaries; +var gCertIssuedInfos; +var gCertExpiresInfos; +var gCerts; +var gCount; + +var gSMimeContractID = "@mozilla.org/messenger-smime/smimejshelper;1"; +var gISMimeJSHelper = Components.interfaces.nsISMimeJSHelper; +var gIX509Cert = Components.interfaces.nsIX509Cert; +var nsICertificateDialogs = Components.interfaces.nsICertificateDialogs; +var nsCertificateDialogs = "@mozilla.org/nsCertificateDialogs;1" + +function getStatusExplanation(value) +{ + switch (value) + { + case gIX509Cert.VERIFIED_OK: + return gBundle.getString("StatusValid"); + + case gIX509Cert.NOT_VERIFIED_UNKNOWN: + case gIX509Cert.INVALID_CA: + case gIX509Cert.USAGE_NOT_ALLOWED: + return gBundle.getString("StatusInvalid"); + + case gIX509Cert.CERT_REVOKED: + return gBundle.getString("StatusRevoked"); + + case gIX509Cert.CERT_EXPIRED: + return gBundle.getString("StatusExpired"); + + case gIX509Cert.CERT_NOT_TRUSTED: + case gIX509Cert.ISSUER_NOT_TRUSTED: + case gIX509Cert.ISSUER_UNKNOWN: + return gBundle.getString("StatusUntrusted"); + } + + return ""; +} + +function onLoad() +{ + var params = window.arguments[0]; + if (!params) + return; + + var helper = Components.classes[gSMimeContractID].createInstance(gISMimeJSHelper); + + if (!helper) + return; + + gListBox = document.getElementById("infolist"); + gViewButton = document.getElementById("viewCertButton"); + gBundle = document.getElementById("bundle_smime_comp_info"); + + gEmailAddresses = new Object(); + gCertStatusSummaries = new Object(); + gCertIssuedInfos = new Object(); + gCertExpiresInfos = new Object(); + gCerts = new Object(); + gCount = new Object(); + var canEncrypt = new Object(); + + var allow_ldap_cert_fetching = false; + + try { + if (params.compFields.securityInfo.requireEncryptMessage) { + allow_ldap_cert_fetching = true; + } + } + catch (e) + { + } + + while (true) + { + try + { + helper.getRecipientCertsInfo( + params.compFields, + gCount, + gEmailAddresses, + gCertStatusSummaries, + gCertIssuedInfos, + gCertExpiresInfos, + gCerts, + canEncrypt); + } + catch (e) + { + dump(e); + return; + } + + if (!allow_ldap_cert_fetching) + break; + + allow_ldap_cert_fetching = false; + + var missing = new Array(); + + for (var j = gCount.value - 1; j >= 0; --j) + { + if (!gCerts.value[j]) + { + missing[missing.length] = gEmailAddresses.value[j]; + } + } + + if (missing.length > 0) + { + var autocompleteLdap = Services.prefs + .getBoolPref("ldap_2.autoComplete.useDirectory"); + + if (autocompleteLdap) + { + var autocompleteDirectory = null; + if (params.currentIdentity.overrideGlobalPref) { + autocompleteDirectory = params.currentIdentity.directoryServer; + } else { + autocompleteDirectory = Services.prefs + .getCharPref("ldap_2.autoComplete.directoryServer"); + } + + if (autocompleteDirectory) + { + window.openDialog('chrome://messenger-smime/content/certFetchingStatus.xul', + '', + 'chrome,resizable=1,modal=1,dialog=1', + autocompleteDirectory, + missing + ); + } + } + } + } + + if (gBundle) + { + var yes_string = gBundle.getString("StatusYes"); + var no_string = gBundle.getString("StatusNo"); + var not_possible_string = gBundle.getString("StatusNotPossible"); + + var signed_element = document.getElementById("signed"); + var encrypted_element = document.getElementById("encrypted"); + + if (params.smFields.requireEncryptMessage) + { + if (params.isEncryptionCertAvailable && canEncrypt.value) + { + encrypted_element.value = yes_string; + } + else + { + encrypted_element.value = not_possible_string; + } + } + else + { + encrypted_element.value = no_string; + } + + if (params.smFields.signMessage) + { + if (params.isSigningCertAvailable) + { + signed_element.value = yes_string; + } + else + { + signed_element.value = not_possible_string; + } + } + else + { + signed_element.value = no_string; + } + } + + var imax = gCount.value; + + for (var i = 0; i < imax; ++i) + { + var listitem = document.createElement("listitem"); + + listitem.appendChild(createCell(gEmailAddresses.value[i])); + + if (!gCerts.value[i]) + { + listitem.appendChild(createCell(gBundle.getString("StatusNotFound"))); + } + else + { + listitem.appendChild(createCell(getStatusExplanation(gCertStatusSummaries.value[i]))); + listitem.appendChild(createCell(gCertIssuedInfos.value[i])); + listitem.appendChild(createCell(gCertExpiresInfos.value[i])); + } + + gListBox.appendChild(listitem); + } +} + +function onSelectionChange(event) +{ + gViewButton.disabled = !(gListBox.selectedItems.length == 1 && + certForRow(gListBox.selectedIndex)); +} + +function viewCertHelper(parent, cert) { + var cd = Components.classes[nsCertificateDialogs].getService(nsICertificateDialogs); + cd.viewCert(parent, cert); +} + +function certForRow(aRowIndex) { + return gCerts.value[aRowIndex]; +} + +function viewSelectedCert() +{ + if (!gViewButton.disabled) + viewCertHelper(window, certForRow(gListBox.selectedIndex)); +} + +function doHelpButton() +{ + openHelp('compose_security', 'chrome://communicator/locale/help/suitehelp.rdf'); +} + +function createCell(label) +{ + var cell = document.createElement("listcell"); + cell.setAttribute("label", label) + return cell; +} diff --git a/mailnews/extensions/smime/content/msgCompSecurityInfo.xul b/mailnews/extensions/smime/content/msgCompSecurityInfo.xul new file mode 100644 index 000000000..c8769d621 --- /dev/null +++ b/mailnews/extensions/smime/content/msgCompSecurityInfo.xul @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/smime/msgCompSecurityInfo.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://messenger-smime/locale/msgCompSecurityInfo.dtd"> + +<dialog id="msgCompSecurityInfo" title="&title.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="width: 50em;" + persist="width height" + buttons="accept" + onload="onLoad();"> + + <script type="application/javascript" src="chrome://messenger-smime/content/msgCompSecurityInfo.js"/> + + <stringbundle id="bundle_smime_comp_info" src="chrome://messenger-smime/locale/msgCompSecurityInfo.properties"/> + + <description>&subject.plaintextWarning;</description> + <separator class="thin"/> + <description>&status.heading;</description> + <grid> + <columns> + <column/> + <column/> + </columns> + <rows> + <row> + <label value="&status.signed;"/> + <label id="signed"/> + </row> + <row> + <label value="&status.encrypted;"/> + <label id="encrypted"/> + </row> + </rows> + </grid> + + <separator class="thin"/> + <label value="&status.certificates;" control="infolist"/> + + <listbox id="infolist" flex="1" + onselect="onSelectionChange(event);"> + <listcols> + <listcol flex="3" width="0"/> + <splitter class="tree-splitter"/> + <listcol flex="1" width="0"/> + <splitter class="tree-splitter"/> + <listcol flex="2" width="0"/> + <splitter class="tree-splitter"/> + <listcol flex="2" width="0"/> + </listcols> + <listhead> + <listheader label="&tree.recipient;"/> + <listheader label="&tree.status;"/> + <listheader label="&tree.issuedDate;"/> + <listheader label="&tree.expiresDate;"/> + </listhead> + </listbox> + <hbox pack="start"> + <button id="viewCertButton" disabled="true" + label="&view.label;" accesskey="&view.accesskey;" + oncommand="viewSelectedCert();"/> + </hbox> +</dialog> diff --git a/mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.js b/mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.js new file mode 100644 index 000000000..2d9469d6c --- /dev/null +++ b/mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.js @@ -0,0 +1,264 @@ +/* -*- Mode: Java; 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/. */ + +var gSignedUINode = null; +var gEncryptedUINode = null; +var gSMIMEContainer = null; +var gStatusBar = null; +var gSignedStatusPanel = null; +var gEncryptedStatusPanel = null; + +var gEncryptedURIService = null; +var gMyLastEncryptedURI = null; + +var gSMIMEBundle = null; +// var gBrandBundle; -- defined in mailWindow.js + +// manipulates some globals from msgReadSMIMEOverlay.js + +var nsICMSMessageErrors = Components.interfaces.nsICMSMessageErrors; + +/// Get the necko URL for the message URI. +function neckoURLForMessageURI(aMessageURI) +{ + let msgSvc = Components.classes["@mozilla.org/messenger;1"] + .createInstance(Components.interfaces.nsIMessenger) + .messageServiceFromURI(aMessageURI); + let neckoURI = {}; + msgSvc.GetUrlForUri(aMessageURI, neckoURI, null); + return neckoURI.value.spec; +} + +var smimeHeaderSink = +{ + maxWantedNesting: function() + { + return 1; + }, + + signedStatus: function(aNestingLevel, aSignatureStatus, aSignerCert) + { + if (aNestingLevel > 1) { + // we are not interested + return; + } + + gSignatureStatus = aSignatureStatus; + gSignerCert = aSignerCert; + + gSMIMEContainer.collapsed = false; + gSignedUINode.collapsed = false; + gSignedStatusPanel.collapsed = false; + + switch (aSignatureStatus) { + case nsICMSMessageErrors.SUCCESS: + gSignedUINode.setAttribute("signed", "ok"); + gStatusBar.setAttribute("signed", "ok"); + break; + + case nsICMSMessageErrors.VERIFY_NOT_YET_ATTEMPTED: + gSignedUINode.setAttribute("signed", "unknown"); + gStatusBar.setAttribute("signed", "unknown"); + break; + + case nsICMSMessageErrors.VERIFY_CERT_WITHOUT_ADDRESS: + case nsICMSMessageErrors.VERIFY_HEADER_MISMATCH: + gSignedUINode.setAttribute("signed", "mismatch"); + gStatusBar.setAttribute("signed", "mismatch"); + break; + + default: + gSignedUINode.setAttribute("signed", "notok"); + gStatusBar.setAttribute("signed", "notok"); + break; + } + }, + + encryptionStatus: function(aNestingLevel, aEncryptionStatus, aRecipientCert) + { + if (aNestingLevel > 1) { + // we are not interested + return; + } + + gEncryptionStatus = aEncryptionStatus; + gEncryptionCert = aRecipientCert; + + gSMIMEContainer.collapsed = false; + gEncryptedUINode.collapsed = false; + gEncryptedStatusPanel.collapsed = false; + + if (nsICMSMessageErrors.SUCCESS == aEncryptionStatus) + { + gEncryptedUINode.setAttribute("encrypted", "ok"); + gStatusBar.setAttribute("encrypted", "ok"); + } + else + { + gEncryptedUINode.setAttribute("encrypted", "notok"); + gStatusBar.setAttribute("encrypted", "notok"); + } + + if (gEncryptedURIService) + { + // Remember the message URI and the corresponding necko URI. + gMyLastEncryptedURI = GetLoadedMessage(); + gEncryptedURIService.rememberEncrypted(gMyLastEncryptedURI); + gEncryptedURIService.rememberEncrypted( + neckoURLForMessageURI(gMyLastEncryptedURI)); + } + + switch (aEncryptionStatus) + { + case nsICMSMessageErrors.SUCCESS: + case nsICMSMessageErrors.ENCRYPT_INCOMPLETE: + break; + default: + var brand = gBrandBundle.getString("brandShortName"); + var title = gSMIMEBundle.getString("CantDecryptTitle").replace(/%brand%/g, brand); + var body = gSMIMEBundle.getString("CantDecryptBody").replace(/%brand%/g, brand); + + // insert our message + msgWindow.displayHTMLInMessagePane(title, + "<html>\n" + + "<body bgcolor=\"#fafaee\">\n" + + "<center><br><br><br>\n" + + "<table>\n" + + "<tr><td>\n" + + "<center><strong><font size=\"+3\">\n" + + title+"</font></center><br>\n" + + body+"\n" + + "</td></tr></table></center></body></html>", false); + } + }, + + QueryInterface : function(iid) + { + if (iid.equals(Components.interfaces.nsIMsgSMIMEHeaderSink) || iid.equals(Components.interfaces.nsISupports)) + return this; + throw Components.results.NS_NOINTERFACE; + } +}; + +function forgetEncryptedURI() +{ + if (gMyLastEncryptedURI && gEncryptedURIService) + { + gEncryptedURIService.forgetEncrypted(gMyLastEncryptedURI); + gEncryptedURIService.forgetEncrypted( + neckoURLForMessageURI(gMyLastEncryptedURI)); + gMyLastEncryptedURI = null; + } +} + +function onSMIMEStartHeaders() +{ + gEncryptionStatus = -1; + gSignatureStatus = -1; + + gSignerCert = null; + gEncryptionCert = null; + + gSMIMEContainer.collapsed = true; + + gSignedUINode.collapsed = true; + gSignedUINode.removeAttribute("signed"); + gSignedStatusPanel.collapsed = true; + gStatusBar.removeAttribute("signed"); + + gEncryptedUINode.collapsed = true; + gEncryptedUINode.removeAttribute("encrypted"); + gEncryptedStatusPanel.collapsed = true; + gStatusBar.removeAttribute("encrypted"); + + forgetEncryptedURI(); +} + +function onSMIMEEndHeaders() +{} + +function onSmartCardChange() +{ + // only reload encrypted windows + if (gMyLastEncryptedURI && gEncryptionStatus != -1) + ReloadMessage(); +} + +function msgHdrViewSMIMEOnLoad(event) +{ + window.crypto.enableSmartCardEvents = true; + document.addEventListener("smartcard-insert", onSmartCardChange, false); + document.addEventListener("smartcard-remove", onSmartCardChange, false); + if (!gSMIMEBundle) + gSMIMEBundle = document.getElementById("bundle_read_smime"); + + // we want to register our security header sink as an opaque nsISupports + // on the msgHdrSink used by mail..... + msgWindow.msgHeaderSink.securityInfo = smimeHeaderSink; + + gSignedUINode = document.getElementById('signedHdrIcon'); + gEncryptedUINode = document.getElementById('encryptedHdrIcon'); + gSMIMEContainer = document.getElementById('smimeBox'); + gStatusBar = document.getElementById('status-bar'); + gSignedStatusPanel = document.getElementById('signed-status'); + gEncryptedStatusPanel = document.getElementById('encrypted-status'); + + // add ourself to the list of message display listeners so we get notified when we are about to display a + // message. + var listener = {}; + listener.onStartHeaders = onSMIMEStartHeaders; + listener.onEndHeaders = onSMIMEEndHeaders; + gMessageListeners.push(listener); + + gEncryptedURIService = + Components.classes["@mozilla.org/messenger-smime/smime-encrypted-uris-service;1"] + .getService(Components.interfaces.nsIEncryptedSMIMEURIsService); +} + +function msgHdrViewSMIMEOnUnload(event) +{ + window.crypto.enableSmartCardEvents = false; + document.removeEventListener("smartcard-insert", onSmartCardChange, false); + document.removeEventListener("smartcard-remove", onSmartCardChange, false); + forgetEncryptedURI(); + removeEventListener("messagepane-loaded", msgHdrViewSMIMEOnLoad, true); + removeEventListener("messagepane-unloaded", msgHdrViewSMIMEOnUnload, true); + removeEventListener("messagepane-hide", msgHdrViewSMIMEOnMessagePaneHide, true); + removeEventListener("messagepane-unhide", msgHdrViewSMIMEOnMessagePaneUnhide, true); +} + +function msgHdrViewSMIMEOnMessagePaneHide() +{ + gSMIMEContainer.collapsed = true; + gSignedUINode.collapsed = true; + gSignedStatusPanel.collapsed = true; + gEncryptedUINode.collapsed = true; + gEncryptedStatusPanel.collapsed = true; +} + +function msgHdrViewSMIMEOnMessagePaneUnhide() +{ + if (gEncryptionStatus != -1 || gSignatureStatus != -1) + { + gSMIMEContainer.collapsed = false; + + if (gSignatureStatus != -1) + { + gSignedUINode.collapsed = false; + gSignedStatusPanel.collapsed = false; + } + + if (gEncryptionStatus != -1) + { + gEncryptedUINode.collapsed = false; + gEncryptedStatusPanel.collapsed = false; + } + } +} + +addEventListener('messagepane-loaded', msgHdrViewSMIMEOnLoad, true); +addEventListener('messagepane-unloaded', msgHdrViewSMIMEOnUnload, true); +addEventListener('messagepane-hide', msgHdrViewSMIMEOnMessagePaneHide, true); +addEventListener('messagepane-unhide', msgHdrViewSMIMEOnMessagePaneUnhide, true); diff --git a/mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.xul b/mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.xul new file mode 100644 index 000000000..957d2a15b --- /dev/null +++ b/mailnews/extensions/smime/content/msgHdrViewSMIMEOverlay.xul @@ -0,0 +1,29 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://messenger/skin/smime/msgHdrViewSMIMEOverlay.css" type="text/css"?> + +<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://messenger-smime/content/msgHdrViewSMIMEOverlay.js"/> +<!-- These stringbundles are already defined in msgReadSMIMEOverlay.xul! + <stringbundleset id="stringbundleset"> + <stringbundle id="bundle_read_smime" src="chrome://messenger-smime/locale/msgReadSMIMEOverlay.properties"/> + <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/> + </stringbundleset> +--> + + <hbox id="expandedHeaderView"> + <vbox id="smimeBox" insertafter="expandedHeaders" collapsed="true"> + <spacer flex="1"/> + <image id="signedHdrIcon" + onclick="showMessageReadSecurityInfo();" collapsed="true"/> + <image id="encryptedHdrIcon" + onclick="showMessageReadSecurityInfo();" collapsed="true"/> + <spacer flex="1"/> + </vbox> + </hbox> +</overlay> + diff --git a/mailnews/extensions/smime/content/msgReadSMIMEOverlay.js b/mailnews/extensions/smime/content/msgReadSMIMEOverlay.js new file mode 100644 index 000000000..ab362d418 --- /dev/null +++ b/mailnews/extensions/smime/content/msgReadSMIMEOverlay.js @@ -0,0 +1,102 @@ +/* -*- Mode: Java; 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/. */ + +Components.utils.import("resource://gre/modules/Services.jsm"); + +var gEncryptionStatus = -1; +var gSignatureStatus = -1; +var gSignerCert = null; +var gEncryptionCert = null; + +addEventListener("load", smimeReadOnLoad, {capture: false, once: true}); + +function smimeReadOnLoad() +{ + top.controllers.appendController(SecurityController); + + addEventListener("unload", smimeReadOnUnload, {capture: false, once: true}); +} + +function smimeReadOnUnload() +{ + top.controllers.removeController(SecurityController); +} + +function showImapSignatureUnknown() +{ + let readSmimeBundle = document.getElementById("bundle_read_smime"); + let brandBundle = document.getElementById("bundle_brand"); + if (!readSmimeBundle || !brandBundle) + return; + + if (Services.prompt.confirm(window, brandBundle.getString("brandShortName"), + readSmimeBundle.getString("ImapOnDemand"))) + { + gDBView.reloadMessageWithAllParts(); + } +} + +function showMessageReadSecurityInfo() +{ + let gSignedUINode = document.getElementById("signedHdrIcon"); + if (gSignedUINode && gSignedUINode.getAttribute("signed") == "unknown") + { + showImapSignatureUnknown(); + return; + } + + let params = Components.classes["@mozilla.org/embedcomp/dialogparam;1"] + .createInstance(Components.interfaces.nsIDialogParamBlock); + params.objects = Components.classes["@mozilla.org/array;1"] + .createInstance(Components.interfaces.nsIMutableArray); + // Append even if null... the receiver must handle that. + params.objects.appendElement(gSignerCert, false); + params.objects.appendElement(gEncryptionCert, false); + + // int array starts with index 0, but that is used for window exit status + params.SetInt(1, gSignatureStatus); + params.SetInt(2, gEncryptionStatus); + + window.openDialog("chrome://messenger-smime/content/msgReadSecurityInfo.xul", + "", "chrome,resizable,modal,dialog,centerscreen", params); +} + +var SecurityController = +{ + supportsCommand: function(command) + { + switch (command) + { + case "cmd_viewSecurityStatus": + return true; + + default: + return false; + } + }, + + isCommandEnabled: function(command) + { + switch (command) + { + case "cmd_viewSecurityStatus": + if (document.documentElement.getAttribute('windowtype') == "mail:messageWindow") + return GetNumSelectedMessages() > 0; + + if (GetNumSelectedMessages() > 0 && gDBView) + { + let enabled = {value: false}; + let checkStatus = {}; + gDBView.getCommandStatus(nsMsgViewCommandType.cmdRequiringMsgBody, + enabled, checkStatus); + return enabled.value; + } + // else: fall through. + + default: + return false; + } + } +}; diff --git a/mailnews/extensions/smime/content/msgReadSMIMEOverlay.xul b/mailnews/extensions/smime/content/msgReadSMIMEOverlay.xul new file mode 100644 index 000000000..a55828c0f --- /dev/null +++ b/mailnews/extensions/smime/content/msgReadSMIMEOverlay.xul @@ -0,0 +1,34 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://messenger/skin/smime/msgReadSMIMEOverlay.css" type="text/css"?> + +<!DOCTYPE overlay SYSTEM "chrome://messenger-smime/locale/msgReadSMIMEOverlay.dtd"> + +<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + + <script type="application/javascript" src="chrome://messenger-smime/content/msgReadSMIMEOverlay.js"/> + + <commandset id="mailViewMenuItems"> + <command id="cmd_viewSecurityStatus" oncommand="showMessageReadSecurityInfo();" disabled="true"/> + </commandset> + + <menupopup id="menu_View_Popup"> + <menuitem insertafter="pageSourceMenuItem" label="&menu_securityStatus.label;" + accesskey="&menu_securityStatus.accesskey;" command="cmd_viewSecurityStatus"/> + </menupopup> + + <statusbar id="status-bar"> + <statusbarpanel insertbefore="offline-status" class="statusbarpanel-iconic" + id="signed-status" collapsed="true" oncommand="showMessageReadSecurityInfo();"/> + <statusbarpanel insertbefore="offline-status" class="statusbarpanel-iconic" + id="encrypted-status" collapsed="true" oncommand="showMessageReadSecurityInfo();"/> + <stringbundle id="bundle_read_smime" src="chrome://messenger-smime/locale/msgReadSMIMEOverlay.properties"/> +<!-- This stringbundle is already defined on top window level! + <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/> +--> + </statusbar> + +</overlay> diff --git a/mailnews/extensions/smime/content/msgReadSecurityInfo.js b/mailnews/extensions/smime/content/msgReadSecurityInfo.js new file mode 100644 index 000000000..310cfc18a --- /dev/null +++ b/mailnews/extensions/smime/content/msgReadSecurityInfo.js @@ -0,0 +1,232 @@ +/* -*- 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/. */ + +var nsIDialogParamBlock = Components.interfaces.nsIDialogParamBlock; +var nsIX509Cert = Components.interfaces.nsIX509Cert; +var nsICMSMessageErrors = Components.interfaces.nsICMSMessageErrors; +var nsICertificateDialogs = Components.interfaces.nsICertificateDialogs; +var nsCertificateDialogs = "@mozilla.org/nsCertificateDialogs;1" + +var gSignerCert = null; +var gEncryptionCert = null; + +var gSignatureStatus = -1; +var gEncryptionStatus = -1; + +function setText(id, value) { + var element = document.getElementById(id); + if (!element) + return; + if (element.hasChildNodes()) + element.firstChild.remove(); + var textNode = document.createTextNode(value); + element.appendChild(textNode); +} + +function onLoad() +{ + var paramBlock = window.arguments[0].QueryInterface(nsIDialogParamBlock); + paramBlock.objects.QueryInterface(Components.interfaces.nsIMutableArray); + try { + gSignerCert = paramBlock.objects.queryElementAt(0, nsIX509Cert); + } catch(e) { } // maybe null + try { + gEncryptionCert = paramBlock.objects.queryElementAt(1, nsIX509Cert); + } catch(e) { } // maybe null + + gSignatureStatus = paramBlock.GetInt(1); + gEncryptionStatus = paramBlock.GetInt(2); + + var bundle = document.getElementById("bundle_smime_read_info"); + + if (bundle) { + var sigInfoLabel = null; + var sigInfoHeader = null; + var sigInfo = null; + var sigInfo_clueless = false; + + switch (gSignatureStatus) { + case -1: + case nsICMSMessageErrors.VERIFY_NOT_SIGNED: + sigInfoLabel = "SINoneLabel"; + sigInfo = "SINone"; + break; + + case nsICMSMessageErrors.SUCCESS: + sigInfoLabel = "SIValidLabel"; + sigInfo = "SIValid"; + break; + + + case nsICMSMessageErrors.VERIFY_BAD_SIGNATURE: + case nsICMSMessageErrors.VERIFY_DIGEST_MISMATCH: + sigInfoLabel = "SIInvalidLabel"; + sigInfoHeader = "SIInvalidHeader"; + sigInfo = "SIContentAltered"; + break; + + case nsICMSMessageErrors.VERIFY_UNKNOWN_ALGO: + case nsICMSMessageErrors.VERIFY_UNSUPPORTED_ALGO: + sigInfoLabel = "SIInvalidLabel"; + sigInfoHeader = "SIInvalidHeader"; + sigInfo = "SIInvalidCipher"; + break; + + case nsICMSMessageErrors.VERIFY_HEADER_MISMATCH: + sigInfoLabel = "SIPartiallyValidLabel"; + sigInfoHeader = "SIPartiallyValidHeader"; + sigInfo = "SIHeaderMismatch"; + break; + + case nsICMSMessageErrors.VERIFY_CERT_WITHOUT_ADDRESS: + sigInfoLabel = "SIPartiallyValidLabel"; + sigInfoHeader = "SIPartiallyValidHeader"; + sigInfo = "SICertWithoutAddress"; + break; + + case nsICMSMessageErrors.VERIFY_UNTRUSTED: + sigInfoLabel = "SIInvalidLabel"; + sigInfoHeader = "SIInvalidHeader"; + sigInfo = "SIUntrustedCA"; + // XXX Need to extend to communicate better errors + // might also be: + // SIExpired SIRevoked SINotYetValid SIUnknownCA SIExpiredCA SIRevokedCA SINotYetValidCA + break; + + case nsICMSMessageErrors.VERIFY_NOT_YET_ATTEMPTED: + case nsICMSMessageErrors.GENERAL_ERROR: + case nsICMSMessageErrors.VERIFY_NO_CONTENT_INFO: + case nsICMSMessageErrors.VERIFY_BAD_DIGEST: + case nsICMSMessageErrors.VERIFY_NOCERT: + case nsICMSMessageErrors.VERIFY_ERROR_UNVERIFIED: + case nsICMSMessageErrors.VERIFY_ERROR_PROCESSING: + case nsICMSMessageErrors.VERIFY_MALFORMED_SIGNATURE: + sigInfoLabel = "SIInvalidLabel"; + sigInfoHeader = "SIInvalidHeader"; + sigInfo_clueless = true; + break; + default: + Components.utils.reportError("Unexpected gSignatureStatus: " + + gSignatureStatus); + } + + document.getElementById("signatureLabel").value = + bundle.getString(sigInfoLabel); + + var label; + if (sigInfoHeader) { + label = document.getElementById("signatureHeader"); + label.collapsed = false; + label.value = bundle.getString(sigInfoHeader); + } + + var str; + if (sigInfo) { + str = bundle.getString(sigInfo); + } + else if (sigInfo_clueless) { + str = bundle.getString("SIClueless") + " (" + gSignatureStatus + ")"; + } + setText("signatureExplanation", str); + + var encInfoLabel = null; + var encInfoHeader = null; + var encInfo = null; + var encInfo_clueless = false; + + switch (gEncryptionStatus) { + case -1: + encInfoLabel = "EINoneLabel2"; + encInfo = "EINone"; + break; + + case nsICMSMessageErrors.SUCCESS: + encInfoLabel = "EIValidLabel"; + encInfo = "EIValid"; + break; + + case nsICMSMessageErrors.ENCRYPT_INCOMPLETE: + encInfoLabel = "EIInvalidLabel"; + encInfo = "EIContentAltered"; + break; + + case nsICMSMessageErrors.GENERAL_ERROR: + encInfoLabel = "EIInvalidLabel"; + encInfoHeader = "EIInvalidHeader"; + encInfo_clueless = 1; + break; + default: + Components.utils.reportError("Unexpected gEncryptionStatus: " + + gEncryptionStatus); + } + + document.getElementById("encryptionLabel").value = + bundle.getString(encInfoLabel); + + if (encInfoHeader) { + label = document.getElementById("encryptionHeader"); + label.collapsed = false; + label.value = bundle.getString(encInfoHeader); + } + + if (encInfo) { + str = bundle.getString(encInfo); + } + else if (encInfo_clueless) { + str = bundle.getString("EIClueless"); + } + setText("encryptionExplanation", str); + } + + if (gSignerCert) { + document.getElementById("signatureCert").collapsed = false; + if (gSignerCert.subjectName) { + document.getElementById("signedBy").value = gSignerCert.commonName; + } + if (gSignerCert.emailAddress) { + document.getElementById("signerEmail").value = gSignerCert.emailAddress; + } + if (gSignerCert.issuerName) { + document.getElementById("sigCertIssuedBy").value = gSignerCert.issuerCommonName; + } + } + + if (gEncryptionCert) { + document.getElementById("encryptionCert").collapsed = false; + if (gEncryptionCert.subjectName) { + document.getElementById("encryptedFor").value = gEncryptionCert.commonName; + } + if (gEncryptionCert.emailAddress) { + document.getElementById("recipientEmail").value = gEncryptionCert.emailAddress; + } + if (gEncryptionCert.issuerName) { + document.getElementById("encCertIssuedBy").value = gEncryptionCert.issuerCommonName; + } + } +} + +function viewCertHelper(parent, cert) { + var cd = Components.classes[nsCertificateDialogs].getService(nsICertificateDialogs); + cd.viewCert(parent, cert); +} + +function viewSignatureCert() +{ + if (gSignerCert) { + viewCertHelper(window, gSignerCert); + } +} + +function viewEncryptionCert() +{ + if (gEncryptionCert) { + viewCertHelper(window, gEncryptionCert); + } +} + +function doHelpButton() +{ + openHelp('received_security'); +} diff --git a/mailnews/extensions/smime/content/msgReadSecurityInfo.xul b/mailnews/extensions/smime/content/msgReadSecurityInfo.xul new file mode 100644 index 000000000..8e0a1f5f5 --- /dev/null +++ b/mailnews/extensions/smime/content/msgReadSecurityInfo.xul @@ -0,0 +1,68 @@ +<?xml version="1.0"?> +<!-- This Source Code Form is subject to the terms of the Mozilla Public + - License, v. 2.0. If a copy of the MPL was not distributed with this + - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + +<?xml-stylesheet href="chrome://global/skin/" type="text/css"?> +<?xml-stylesheet href="chrome://messenger/skin/smime/msgReadSecurityInfo.css" type="text/css"?> + +<!DOCTYPE dialog SYSTEM "chrome://messenger-smime/locale/msgReadSecurityInfo.dtd"> + +<dialog id="msgReadSecurityInfo" title="&status.label;" + xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" + style="width: 40em;" + buttons="accept" + onload="onLoad();"> + + <script type="application/javascript" src="chrome://messenger-smime/content/msgReadSecurityInfo.js"/> + + <stringbundle id="bundle_smime_read_info" src="chrome://messenger-smime/locale/msgSecurityInfo.properties"/> + + <vbox flex="1"> + <label id="signatureLabel"/> + <label id="signatureHeader" collapsed="true"/> + <description id="signatureExplanation"/> + <vbox id="signatureCert" collapsed="true"> + <hbox> + <label id="signedByLabel">&signer.name;</label> + <description id="signedBy"/> + </hbox> + <hbox> + <label id="signerEmailLabel">&email.address;</label> + <description id="signerEmail"/> + </hbox> + <hbox> + <label id="sigCertIssuedByLabel">&issuer.name;</label> + <description id="sigCertIssuedBy"/> + </hbox> + <hbox> + <button id="signatureCertView" label="&signatureCert.label;" + oncommand="viewSignatureCert()"/> + </hbox> + </vbox> + + <separator/> + + <label id="encryptionLabel"/> + <label id="encryptionHeader" collapsed="true"/> + <description id="encryptionExplanation"/> + <vbox id="encryptionCert" collapsed="true"> + <hbox> + <label id="encryptedForLabel">&recipient.name;</label> + <description id="encryptedFor"/> + </hbox> + <hbox> + <label id="recipientEmailLabel">&email.address;</label> + <description id="recipientEmail"/> + </hbox> + <hbox> + <label id="encCertIssuedByLabel">&issuer.name;</label> + <description id="encCertIssuedBy"/> + </hbox> + <hbox> + <button id="encryptionCertView" label="&encryptionCert.label;" + oncommand="viewEncryptionCert()"/> + </hbox> + </vbox> + </vbox> +</dialog> diff --git a/mailnews/extensions/smime/content/smime.js b/mailnews/extensions/smime/content/smime.js new file mode 100644 index 000000000..8259ead1a --- /dev/null +++ b/mailnews/extensions/smime/content/smime.js @@ -0,0 +1,14 @@ +/* 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/. */ + +/* + Add any default pref values we want for smime +*/ + +pref("mail.identity.default.encryption_cert_name",""); +pref("mail.identity.default.encryptionpolicy", 0); +pref("mail.identity.default.signing_cert_name", ""); +pref("mail.identity.default.sign_mail", false); + + diff --git a/mailnews/extensions/smime/jar.mn b/mailnews/extensions/smime/jar.mn new file mode 100644 index 000000000..548fef63c --- /dev/null +++ b/mailnews/extensions/smime/jar.mn @@ -0,0 +1,30 @@ +# 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/. + +#ifdef MOZ_SUITE +messenger.jar: +% content messenger-smime %content/messenger-smime/ +% overlay chrome://messenger/content/messengercompose/messengercompose.xul chrome://messenger-smime/content/msgCompSMIMEOverlay.xul +% overlay chrome://messenger/content/msgHdrViewOverlay.xul chrome://messenger-smime/content/msgHdrViewSMIMEOverlay.xul +% overlay chrome://messenger/content/mailWindowOverlay.xul chrome://messenger-smime/content/msgReadSMIMEOverlay.xul +% overlay chrome://messenger/content/am-identity-edit.xul chrome://messenger/content/am-smimeIdentityEditOverlay.xul + content/messenger/am-smime.xul (content/am-smime.xul) + content/messenger/am-smime.js (content/am-smime.js) + content/messenger/am-smimeIdentityEditOverlay.xul (content/am-smimeIdentityEditOverlay.xul) + content/messenger/am-smimeOverlay.xul (content/am-smimeOverlay.xul) + content/messenger/certpicker.js (content/certpicker.js) + content/messenger/certpicker.xul (content/certpicker.xul) + content/messenger-smime/msgCompSMIMEOverlay.js (content/msgCompSMIMEOverlay.js) + content/messenger-smime/msgCompSMIMEOverlay.xul (content/msgCompSMIMEOverlay.xul) + content/messenger-smime/msgReadSMIMEOverlay.js (content/msgReadSMIMEOverlay.js) + content/messenger-smime/msgReadSMIMEOverlay.xul (content/msgReadSMIMEOverlay.xul) + content/messenger-smime/msgHdrViewSMIMEOverlay.xul (content/msgHdrViewSMIMEOverlay.xul) + content/messenger-smime/msgHdrViewSMIMEOverlay.js (content/msgHdrViewSMIMEOverlay.js) + content/messenger-smime/msgCompSecurityInfo.xul (content/msgCompSecurityInfo.xul) + content/messenger-smime/msgCompSecurityInfo.js (content/msgCompSecurityInfo.js) + content/messenger-smime/msgReadSecurityInfo.xul (content/msgReadSecurityInfo.xul) + content/messenger-smime/msgReadSecurityInfo.js (content/msgReadSecurityInfo.js) + content/messenger-smime/certFetchingStatus.xul (content/certFetchingStatus.xul) + content/messenger-smime/certFetchingStatus.js (content/certFetchingStatus.js) +#endif diff --git a/mailnews/extensions/smime/moz.build b/mailnews/extensions/smime/moz.build new file mode 100644 index 000000000..3adf6a50e --- /dev/null +++ b/mailnews/extensions/smime/moz.build @@ -0,0 +1,15 @@ +# 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/. + +DIRS += [ + 'public', + 'src', +] + +JAR_MANIFESTS += ['jar.mn'] + +JS_PREFERENCE_FILES += [ + 'content/smime.js', +] diff --git a/mailnews/extensions/smime/public/moz.build b/mailnews/extensions/smime/public/moz.build new file mode 100644 index 000000000..b7acbb0b2 --- /dev/null +++ b/mailnews/extensions/smime/public/moz.build @@ -0,0 +1,15 @@ +# 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', + 'nsIEncryptedSMIMEURIsSrvc.idl', + 'nsIMsgSMIMECompFields.idl', + 'nsIMsgSMIMEHeaderSink.idl', + 'nsISMimeJSHelper.idl', + 'nsIUserCertPicker.idl', +] + +XPIDL_MODULE = 'msgsmime' diff --git a/mailnews/extensions/smime/public/nsICertPickDialogs.idl b/mailnews/extensions/smime/public/nsICertPickDialogs.idl new file mode 100644 index 000000000..01a7f3712 --- /dev/null +++ b/mailnews/extensions/smime/public/nsICertPickDialogs.idl @@ -0,0 +1,30 @@ +/* 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 generic UI for choosing a certificate + */ +[scriptable, uuid(51d59b08-1dd2-11b2-ad4a-a51b92f8a184)] +interface nsICertPickDialogs : nsISupports +{ + /** + * PickCertificate + * General purpose certificate prompter + */ + void PickCertificate(in nsIInterfaceRequestor ctx, + [array, size_is(count)] in wstring certNickList, + [array, size_is(count)] in wstring certDetailsList, + in unsigned long count, + inout long selectedIndex, + out boolean canceled); +}; + +%{C++ +#define NS_CERTPICKDIALOGS_CONTRACTID "@mozilla.org/nsCertPickDialogs;1" +%} diff --git a/mailnews/extensions/smime/public/nsIEncryptedSMIMEURIsSrvc.idl b/mailnews/extensions/smime/public/nsIEncryptedSMIMEURIsSrvc.idl new file mode 100644 index 000000000..4b2b7c25c --- /dev/null +++ b/mailnews/extensions/smime/public/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/mailnews/extensions/smime/public/nsIMsgSMIMECompFields.idl b/mailnews/extensions/smime/public/nsIMsgSMIMECompFields.idl new file mode 100644 index 000000000..0688afd76 --- /dev/null +++ b/mailnews/extensions/smime/public/nsIMsgSMIMECompFields.idl @@ -0,0 +1,18 @@ +/* -*- 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 + should have any knowledge nor should be referring to this interface. +*/ + +#include "nsISupports.idl" + +[scriptable, uuid(338E91F9-5970-4f81-B771-0822A32B1161)] +interface nsIMsgSMIMECompFields : nsISupports +{ + attribute boolean signMessage; + attribute boolean requireEncryptMessage; +}; diff --git a/mailnews/extensions/smime/public/nsIMsgSMIMEHeaderSink.idl b/mailnews/extensions/smime/public/nsIMsgSMIMEHeaderSink.idl new file mode 100644 index 000000000..9bfa41a04 --- /dev/null +++ b/mailnews/extensions/smime/public/nsIMsgSMIMEHeaderSink.idl @@ -0,0 +1,23 @@ +/* -*- 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); + void encryptionStatus(in long aNestingLevel, in long aEncryptionStatus, in nsIX509Cert aReceipientCert); + + long maxWantedNesting(); // 1 == only info on outermost nesting level wanted +}; diff --git a/mailnews/extensions/smime/public/nsISMimeJSHelper.idl b/mailnews/extensions/smime/public/nsISMimeJSHelper.idl new file mode 100644 index 000000000..c29a77939 --- /dev/null +++ b/mailnews/extensions/smime/public/nsISMimeJSHelper.idl @@ -0,0 +1,73 @@ +/* -*- 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" + +interface nsIMsgCompFields; +interface nsIX509Cert; + +[scriptable, uuid(a54e3c8f-a000-4901-898f-fafb297b1546)] +interface nsISMimeJSHelper : nsISupports +{ + /** + * Obtains detailed information about the certificate availability + * status of email recipients. + * + * @param compFields - Attributes of the composed message + * + * @param count - The number of entries in returned arrays + * + * @param emailAddresses - The list of all recipient email addresses + * + * @param certVerification - The verification/validity status of recipient certs + * + * @param certIssuedInfos - If a recipient cert was found, when has it been issued? + * + * @param certExpiredInfos - If a recipient cert was found, when will it expire? + * + * @param certs - The recipient certificates, which can contain null for not found + * + * @param canEncrypt - whether valid certificates have been found for all recipients + * + * @exception NS_ERROR_FAILURE - unexptected failure + * + * @exception NS_ERROR_OUT_OF_MEMORY - could not create the out list + * + * @exception NS_ERROR_INVALID_ARG + */ + void getRecipientCertsInfo(in nsIMsgCompFields compFields, + out unsigned long count, + [array, size_is(count)] out wstring emailAddresses, + [array, size_is(count)] out long certVerification, + [array, size_is(count)] out wstring certIssuedInfos, + [array, size_is(count)] out wstring certExpiresInfos, + [array, size_is(count)] out nsIX509Cert certs, + out boolean canEncrypt); + + /** + * Obtains a list of email addresses where valid email recipient certificates + * are not yet available. + * + * @param compFields - Attributes of the composed message + * + * @param count - The number of returned email addresses + * + * @param emailAddresses - The list of email addresses without valid certs + * + * @exception NS_ERROR_FAILURE - unexptected failure + * + * @exception NS_ERROR_OUT_OF_MEMORY - could not create the out list + * + * @exception NS_ERROR_INVALID_ARG + */ + void getNoCertAddresses(in nsIMsgCompFields compFields, + out unsigned long count, + [array, size_is(count)] out wstring emailAddresses); +}; diff --git a/mailnews/extensions/smime/public/nsIUserCertPicker.idl b/mailnews/extensions/smime/public/nsIUserCertPicker.idl new file mode 100644 index 000000000..666941c29 --- /dev/null +++ b/mailnews/extensions/smime/public/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/mailnews/extensions/smime/src/moz.build b/mailnews/extensions/smime/src/moz.build new file mode 100644 index 000000000..f3e888dd4 --- /dev/null +++ b/mailnews/extensions/smime/src/moz.build @@ -0,0 +1,23 @@ +# 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/. + +SOURCES += [ + 'nsCertPicker.cpp', + 'nsEncryptedSMIMEURIsService.cpp', + 'nsMsgComposeSecure.cpp', + 'nsSMimeJSHelper.cpp', +] + +EXTRA_COMPONENTS += [ + 'smime-service.js', + 'smime-service.manifest', +] + +FINAL_LIBRARY = 'mail' + +LOCAL_INCLUDES += [ + '/mozilla/security/manager/pki', + '/mozilla/security/pkix/include' +] diff --git a/mailnews/extensions/smime/src/nsCertPicker.cpp b/mailnews/extensions/smime/src/nsCertPicker.cpp new file mode 100644 index 000000000..183f9605c --- /dev/null +++ b/mailnews/extensions/smime/src/nsCertPicker.cpp @@ -0,0 +1,471 @@ +/* -*- 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 "nsIDOMWindow.h" +#include "nsIDialogParamBlock.h" +#include "nsIInterfaceRequestor.h" +#include "nsIServiceManager.h" +#include "nsIX509CertValidity.h" +#include "nsMemory.h" +#include "nsMsgComposeSecure.h" +#include "nsNSSCertificate.h" +#include "nsNSSComponent.h" +#include "nsNSSDialogHelper.h" +#include "nsNSSHelper.h" +#include "nsNSSShutDown.h" +#include "nsReadableUtils.h" +#include "nsString.h" +#include "pkix/pkixtypes.h" + +using namespace mozilla; + +MOZ_TYPE_SPECIFIC_UNIQUE_PTR_TEMPLATE(UniqueCERTCertNicknames, + CERTCertNicknames, + CERT_FreeNicknames) + +CERTCertNicknames* +getNSSCertNicknamesFromCertList(const UniqueCERTCertList& certList) +{ + static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); + + nsresult rv; + + nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(kNSSComponentCID, &rv)); + if (NS_FAILED(rv)) + return nullptr; + + nsAutoString expiredString, notYetValidString; + nsAutoString expiredStringLeadingSpace, notYetValidStringLeadingSpace; + + nssComponent->GetPIPNSSBundleString("NicknameExpired", expiredString); + nssComponent->GetPIPNSSBundleString("NicknameNotYetValid", notYetValidString); + + expiredStringLeadingSpace.Append(' '); + expiredStringLeadingSpace.Append(expiredString); + + notYetValidStringLeadingSpace.Append(' '); + notYetValidStringLeadingSpace.Append(notYetValidString); + + NS_ConvertUTF16toUTF8 aUtf8ExpiredString(expiredStringLeadingSpace); + NS_ConvertUTF16toUTF8 aUtf8NotYetValidString(notYetValidStringLeadingSpace); + + return CERT_NicknameStringsFromCertList(certList.get(), + const_cast<char*>(aUtf8ExpiredString.get()), + const_cast<char*>(aUtf8NotYetValidString.get())); +} + +nsresult +FormatUIStrings(nsIX509Cert* cert, const nsAutoString& nickname, + nsAutoString& nickWithSerial, nsAutoString& details) +{ + if (!NS_IsMainThread()) { + NS_ERROR("nsNSSCertificate::FormatUIStrings called off the main thread"); + return NS_ERROR_NOT_SAME_THREAD; + } + + RefPtr<nsMsgComposeSecure> mcs = new nsMsgComposeSecure; + if (!mcs) { + return NS_ERROR_FAILURE; + } + + nsAutoString info; + nsAutoString temp1; + + nickWithSerial.Append(nickname); + + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoIssuedFor", info))) { + details.Append(info); + details.Append(char16_t(' ')); + if (NS_SUCCEEDED(cert->GetSubjectName(temp1)) && !temp1.IsEmpty()) { + details.Append(temp1); + } + details.Append(char16_t('\n')); + } + + if (NS_SUCCEEDED(cert->GetSerialNumber(temp1)) && !temp1.IsEmpty()) { + details.AppendLiteral(" "); + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertDumpSerialNo", info))) { + details.Append(info); + details.AppendLiteral(": "); + } + details.Append(temp1); + + nickWithSerial.AppendLiteral(" ["); + nickWithSerial.Append(temp1); + nickWithSerial.Append(char16_t(']')); + + details.Append(char16_t('\n')); + } + + nsCOMPtr<nsIX509CertValidity> validity; + nsresult rv = cert->GetValidity(getter_AddRefs(validity)); + if (NS_SUCCEEDED(rv) && validity) { + details.AppendLiteral(" "); + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoValid", info))) { + details.Append(info); + } + + if (NS_SUCCEEDED(validity->GetNotBeforeLocalTime(temp1)) && !temp1.IsEmpty()) { + details.Append(char16_t(' ')); + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoFrom", info))) { + details.Append(info); + details.Append(char16_t(' ')); + } + details.Append(temp1); + } + + if (NS_SUCCEEDED(validity->GetNotAfterLocalTime(temp1)) && !temp1.IsEmpty()) { + details.Append(char16_t(' ')); + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertInfoTo", info))) { + details.Append(info); + details.Append(char16_t(' ')); + } + details.Append(temp1); + } + + details.Append(char16_t('\n')); + } + + if (NS_SUCCEEDED(cert->GetKeyUsages(temp1)) && !temp1.IsEmpty()) { + details.AppendLiteral(" "); + if (NS_SUCCEEDED(mcs->GetSMIMEBundleString(u"CertDumpKeyUsage", info))) { + details.Append(info); + details.AppendLiteral(": "); + } + 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() +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return; + } + + shutdown(ShutdownCalledFrom::Object); +} + +nsresult +nsCertPicker::Init() +{ + nsresult rv; + nsCOMPtr<nsISupports> psm = do_GetService("@mozilla.org/psm;1", &rv); + return rv; +} + +NS_IMETHODIMP +nsCertPicker::PickCertificate(nsIInterfaceRequestor *ctx, + const char16_t **certNickList, + const char16_t **certDetailsList, + uint32_t count, + int32_t *selectedIndex, + bool *canceled) +{ + nsresult rv; + uint32_t i; + + *canceled = false; + + nsCOMPtr<nsIDialogParamBlock> block = + do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID); + if (!block) return NS_ERROR_FAILURE; + + block->SetNumberStrings(1+count*2); + + for (i = 0; i < count; i++) { + rv = block->SetString(i, certNickList[i]); + if (NS_FAILED(rv)) return rv; + } + + for (i = 0; i < count; i++) { + rv = block->SetString(i+count, certDetailsList[i]); + if (NS_FAILED(rv)) return rv; + } + + rv = block->SetInt(0, count); + if (NS_FAILED(rv)) return rv; + + rv = block->SetInt(1, *selectedIndex); + if (NS_FAILED(rv)) return rv; + + rv = nsNSSDialogHelper::openDialog(nullptr, + "chrome://messenger/content/certpicker.xul", + block); + if (NS_FAILED(rv)) return rv; + + int32_t status; + + rv = block->GetInt(0, &status); + if (NS_FAILED(rv)) return 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) +{ + nsNSSShutDownPreventionLock locker; + if (isAlreadyShutDown()) { + return NS_ERROR_NOT_AVAILABLE; + } + + int32_t selectedIndex = -1; + bool selectionFound = false; + char16_t **certNicknameList = nullptr; + char16_t **certDetailsList = nullptr; + CERTCertListNode* node = nullptr; + nsresult rv = NS_OK; + + { + // Iterate over all certs. This assures that user is logged in to all hardware tokens. + nsCOMPtr<nsIInterfaceRequestor> ctx = new PipUIContext(); + UniqueCERTCertList allcerts(PK11_ListCerts(PK11CertListUnique, ctx)); + } + + /* find all user certs that are valid for the specified usage */ + /* note that we are allowing expired certs in this list */ + UniqueCERTCertList certList( + CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(), + (SECCertUsage)certUsage, + !allowDuplicateNicknames, + !allowInvalid, + ctx)); + if (!certList) { + return NS_ERROR_NOT_AVAILABLE; + } + + /* if a (non-empty) emailAddress argument is supplied to PickByUsage, */ + /* remove non-matching certificates from the candidate list */ + + if (!emailAddress.IsEmpty()) { + node = CERT_LIST_HEAD(certList); + while (!CERT_LIST_END(node, certList)) { + /* if the cert has at least one e-mail address, check if suitable */ + if (CERT_GetFirstEmailAddress(node->cert)) { + RefPtr<nsNSSCertificate> tempCert(nsNSSCertificate::Create(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; + } + + certNicknameList = (char16_t **)moz_xmalloc(sizeof(char16_t *) * nicknames->numnicknames); + certDetailsList = (char16_t **)moz_xmalloc(sizeof(char16_t *) * nicknames->numnicknames); + + if (!certNicknameList || !certDetailsList) { + free(certNicknameList); + free(certDetailsList); + return NS_ERROR_OUT_OF_MEMORY; + } + + int32_t CertsToUse; + + for (CertsToUse = 0, node = CERT_LIST_HEAD(certList.get()); + !CERT_LIST_END(node, certList.get()) && + CertsToUse < nicknames->numnicknames; + node = CERT_LIST_NEXT(node) + ) + { + RefPtr<nsNSSCertificate> tempCert(nsNSSCertificate::Create(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[CertsToUse] = ToNewUnicode(nickWithSerial); + certDetailsList[CertsToUse] = ToNewUnicode(details); + if (!selectionFound) { + /* for the case when selectedNickname refers to nickname + serial */ + if (nickWithSerial == nsDependentString(selectedNickname)) { + selectedIndex = CertsToUse; + selectionFound = true; + } + } + } + else { + certNicknameList[CertsToUse] = nullptr; + certDetailsList[CertsToUse] = nullptr; + } + + ++CertsToUse; + } + } + + if (CertsToUse) { + nsCOMPtr<nsICertPickDialogs> dialogs; + rv = getNSSDialogs(getter_AddRefs(dialogs), NS_GET_IID(nsICertPickDialogs), + NS_CERTPICKDIALOGS_CONTRACTID); + + if (NS_SUCCEEDED(rv)) { + // Show the cert picker dialog and get the index of the selected cert. + rv = dialogs->PickCertificate(ctx, (const char16_t**)certNicknameList, + (const char16_t**)certDetailsList, + CertsToUse, &selectedIndex, canceled); + } + } + + int32_t i; + for (i = 0; i < CertsToUse; ++i) { + free(certNicknameList[i]); + free(certDetailsList[i]); + } + free(certNicknameList); + free(certDetailsList); + + if (!CertsToUse) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (NS_SUCCEEDED(rv) && !*canceled) { + for (i = 0, node = CERT_LIST_HEAD(certList); + !CERT_LIST_END(node, certList); + ++i, node = CERT_LIST_NEXT(node)) { + + if (i == selectedIndex) { + RefPtr<nsNSSCertificate> cert = nsNSSCertificate::Create(node->cert); + if (!cert) { + rv = NS_ERROR_OUT_OF_MEMORY; + break; + } + + cert.forget(_retval); + break; + } + } + } + + return rv; +} diff --git a/mailnews/extensions/smime/src/nsCertPicker.h b/mailnews/extensions/smime/src/nsCertPicker.h new file mode 100644 index 000000000..e5881f14f --- /dev/null +++ b/mailnews/extensions/smime/src/nsCertPicker.h @@ -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/. */ + +#ifndef nsCertPicker_h +#define nsCertPicker_h + +#include "nsICertPickDialogs.h" +#include "nsIUserCertPicker.h" +#include "nsNSSShutDown.h" + +#define NS_CERT_PICKER_CID \ + { 0x735959a1, 0xaf01, 0x447e, { 0xb0, 0x2d, 0x56, 0xe9, 0x68, 0xfa, 0x52, 0xb4 } } + +class nsCertPicker : public nsICertPickDialogs + , public nsIUserCertPicker + , public nsNSSShutDownObject +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSICERTPICKDIALOGS + NS_DECL_NSIUSERCERTPICKER + + nsCertPicker(); + + // Nothing to actually release. + virtual void virtualDestroyNSSReference() override {} + + nsresult Init(); + +protected: + virtual ~nsCertPicker(); +}; + +#endif // nsCertPicker_h diff --git a/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.cpp b/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.cpp new file mode 100644 index 000000000..9276a07a6 --- /dev/null +++ b/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.cpp @@ -0,0 +1,36 @@ +/* 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/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.h b/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.h new file mode 100644 index 000000000..429acbf0a --- /dev/null +++ b/mailnews/extensions/smime/src/nsEncryptedSMIMEURIsService.h @@ -0,0 +1,25 @@ +/* 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 "nsStringGlue.h" + +class nsEncryptedSMIMEURIsService : public nsIEncryptedSMIMEURIsService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIENCRYPTEDSMIMEURISSERVICE + + nsEncryptedSMIMEURIsService(); + +protected: + virtual ~nsEncryptedSMIMEURIsService(); + nsTArray<nsCString> mEncryptedURIs; +}; + +#endif diff --git a/mailnews/extensions/smime/src/nsMsgComposeSecure.cpp b/mailnews/extensions/smime/src/nsMsgComposeSecure.cpp new file mode 100644 index 000000000..55383c828 --- /dev/null +++ b/mailnews/extensions/smime/src/nsMsgComposeSecure.cpp @@ -0,0 +1,1203 @@ +/* -*- 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 <algorithm> + +#include "ScopedNSSTypes.h" +#include "cert.h" +#include "keyhi.h" +#include "mozilla/RefPtr.h" +#include "mozilla/Services.h" +#include "mozilla/mailnews/MimeEncoder.h" +#include "mozilla/mailnews/MimeHeaderParser.h" +#include "msgCore.h" +#include "nsAlgorithm.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 "nsMsgMimeCID.h" +#include "nsNSSComponent.h" +#include "nsServiceManagerUtils.h" +#include "nspr.h" +#include "pkix/Result.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 nsMsgSMIMEComposeFields +///////////////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsMsgSMIMEComposeFields, nsIMsgSMIMECompFields) + +nsMsgSMIMEComposeFields::nsMsgSMIMEComposeFields() +:mSignMessage(false), mAlwaysEncryptMessage(false) +{ +} + +nsMsgSMIMEComposeFields::~nsMsgSMIMEComposeFields() +{ +} + +NS_IMETHODIMP nsMsgSMIMEComposeFields::SetSignMessage(bool value) +{ + mSignMessage = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgSMIMEComposeFields::GetSignMessage(bool *_retval) +{ + *_retval = mSignMessage; + return NS_OK; +} + +NS_IMETHODIMP nsMsgSMIMEComposeFields::SetRequireEncryptMessage(bool value) +{ + mAlwaysEncryptMessage = value; + return NS_OK; +} + +NS_IMETHODIMP nsMsgSMIMEComposeFields::GetRequireEncryptMessage(bool *_retval) +{ + *_retval = mAlwaysEncryptMessage; + return NS_OK; +} + +///////////////////////////////////////////////////////////////////////////////////////// +// Implementation of nsMsgComposeSecure +///////////////////////////////////////////////////////////////////////////////////////// + +NS_IMPL_ISUPPORTS(nsMsgComposeSecure, nsIMsgComposeSecure) + +nsMsgComposeSecure::nsMsgComposeSecure() +{ + /* member initializers and constructor code */ + mMultipartSignedBoundary = 0; + mBuffer = 0; + mBufferedBytes = 0; + mHashType = 0; +} + +nsMsgComposeSecure::~nsMsgComposeSecure() +{ + /* destructor code */ + if (mEncryptionContext) { + if (mBufferedBytes) { + mEncryptionContext->Update(mBuffer, mBufferedBytes); + mBufferedBytes = 0; + } + mEncryptionContext->Finish(); + } + + delete [] mBuffer; + + PR_FREEIF(mMultipartSignedBoundary); +} + +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(name, getter_Copies(outString)); +} + +nsresult +nsMsgComposeSecure:: +SMIMEBundleFormatStringFromName(const char16_t *name, + const char16_t **params, + uint32_t numParams, + char16_t **outString) +{ + NS_ENSURE_ARG_POINTER(name); + + if (!InitializeSMIMEBundle()) + return NS_ERROR_FAILURE; + + return mSMIMEBundle->FormatStringFromName(name, params, + numParams, outString); +} + +bool nsMsgComposeSecure::InitializeSMIMEBundle() +{ + if (mSMIMEBundle) + return true; + + nsCOMPtr<nsIStringBundleService> bundleService = + mozilla::services::GetStringBundleService(); + 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 char16_t *bundle_string, const char *param) +{ + if (!sendReport || !bundle_string || !param) + return; + + if (mErrorAlreadyReported) + return; + + mErrorAlreadyReported = true; + + nsString errorString; + nsresult res; + const char16_t *params[1]; + + NS_ConvertASCIItoUTF16 ucs2(param); + params[0]= ucs2.get(); + + res = SMIMEBundleFormatStringFromName(bundle_string, + params, + 1, + getter_Copies(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); + + nsCOMPtr<nsISupports> securityInfo; + if (aComposeFields) + aComposeFields->GetSecurityInfo(getter_AddRefs(securityInfo)); + + if (securityInfo) // if we were given security comp fields, use them..... + { + nsCOMPtr<nsIMsgSMIMECompFields> smimeCompFields = do_QueryInterface(securityInfo); + if (smimeCompFields) + { + smimeCompFields->GetSignMessage(aSignMessage); + smimeCompFields->GetRequireEncryptMessage(aEncrypt); + return NS_OK; + } + } + + // get the default info from the identity.... + int32_t ep = 0; + nsresult testrv = aIdentity->GetIntAttribute("encryptionpolicy", &ep); + if (NS_FAILED(testrv)) { + *aEncrypt = false; + } + else { + *aEncrypt = (ep > 0); + } + + testrv = aIdentity->GetBoolAttribute("sign_mail", aSignMessage); + if (NS_FAILED(testrv)) + { + *aSignMessage = false; + } + 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; + + bool encryptMessages = false; + bool signMessage = false; + ExtractEncryptionState(aIdentity, aCompFields, &signMessage, &encryptMessages); + + if (!signMessage && !encryptMessages) return NS_ERROR_FAILURE; + + mStream = aStream; + mIsDraft = aIsDraft; + + if (encryptMessages && signMessage) + mCryptoState = mime_crypto_signed_encrypted; + else if (encryptMessages) + mCryptoState = mime_crypto_encrypted; + else if (signMessage) + mCryptoState = mime_crypto_clear_signed; + else + PR_ASSERT(0); + + aIdentity->GetUnicharAttribute("signing_cert_name", mSigningCertName); + aIdentity->GetCharAttribute("signing_cert_dbkey", mSigningCertDBKey); + aIdentity->GetUnicharAttribute("encryption_cert_name", mEncryptionCertName); + aIdentity->GetCharAttribute("encryption_cert_dbkey", mEncryptionCertDBKey); + + rv = MimeCryptoHackCerts(aRecipients, sendReport, encryptMessages, signMessage, aIdentity); + if (NS_FAILED(rv)) { + goto FAIL; + } + + if (signMessage && mSelfSigningCert) { + rv = GetSigningHashFunction(mSelfSigningCert, &mHashType); + NS_ENSURE_SUCCESS(rv, rv); + } + + switch (mCryptoState) + { + case mime_crypto_clear_signed: + rv = MimeInitMultipartSigned(true, sendReport); + break; + case mime_crypto_opaque_signed: + PR_ASSERT(0); /* #### no api for this yet */ + rv = NS_ERROR_NOT_IMPLEMENTED; + break; + case mime_crypto_signed_encrypted: + rv = MimeInitEncryption(true, sendReport); + break; + case mime_crypto_encrypted: + rv = MimeInitEncryption(false, sendReport); + break; + case mime_crypto_none: + /* This can happen if mime_crypto_hack_certs() decided to turn off + encryption (by asking the user.) */ + // XXX 1 is not a valid nsresult + rv = static_cast<nsresult>(1); + break; + default: + PR_ASSERT(0); + break; + } + +FAIL: + return rv; +} + +/* void finishCryptoEncapsulation (in boolean aAbort); */ +NS_IMETHODIMP nsMsgComposeSecure::FinishCryptoEncapsulation(bool aAbort, nsIMsgSendReport *sendReport) +{ + nsresult rv = NS_OK; + + if (!aAbort) { + switch (mCryptoState) { + case mime_crypto_clear_signed: + rv = MimeFinishMultipartSigned (true, sendReport); + break; + case mime_crypto_opaque_signed: + PR_ASSERT(0); /* #### no api for this yet */ + rv = NS_ERROR_FAILURE; + break; + case mime_crypto_signed_encrypted: + rv = MimeFinishEncryption (true, sendReport); + break; + case mime_crypto_encrypted: + rv = MimeFinishEncryption (false, sendReport); + break; + default: + PR_ASSERT(0); + rv = NS_ERROR_FAILURE; + break; + } + } + return rv; +} + +nsresult nsMsgComposeSecure::MimeInitMultipartSigned(bool aOuter, nsIMsgSendReport *sendReport) +{ + /* First, construct and write out the multipart/signed MIME header data. + */ + nsresult rv = NS_OK; + char *header = 0; + uint32_t L; + + rv = make_multipart_signed_header_string(aOuter, &header, + &mMultipartSignedBoundary, mHashType); + + NS_ENSURE_SUCCESS(rv, rv); + + L = strlen(header); + + if (aOuter){ + /* If this is the outer block, write it to the file. */ + uint32_t n; + rv = mStream->Write(header, L, &n); + if (NS_FAILED(rv) || n < L) { + // XXX This is -1, not an nsresult + rv = static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE); + } + } else { + /* If this is an inner block, feed it through the crypto stream. */ + rv = MimeCryptoWriteBlock (header, L); + } + + PR_Free(header); + NS_ENSURE_SUCCESS(rv, rv); + + /* Now initialize the crypto library, so that we can compute a hash + on the object which we are signing. + */ + + PR_SetError(0,0); + mDataHash = do_CreateInstance("@mozilla.org/security/hash;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mDataHash->Init(mHashType); + NS_ENSURE_SUCCESS(rv, rv); + + PR_SetError(0,0); + return rv; +} + +nsresult nsMsgComposeSecure::MimeInitEncryption(bool aSign, nsIMsgSendReport *sendReport) +{ + nsresult rv; + nsCOMPtr<nsIStringBundleService> bundleSvc = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> sMIMEBundle; + nsString mime_smime_enc_content_desc; + + bundleSvc->CreateBundle(SMIME_STRBUNDLE_URL, getter_AddRefs(sMIMEBundle)); + + if (!sMIMEBundle) + return NS_ERROR_FAILURE; + + sMIMEBundle->GetStringFromName(u"mime_smimeEncryptedContentDesc", + getter_Copies(mime_smime_enc_content_desc)); + NS_ConvertUTF16toUTF8 enc_content_desc_utf8(mime_smime_enc_content_desc); + + nsCOMPtr<nsIMimeConverter> mimeConverter = + do_GetService(NS_MIME_CONVERTER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + nsCString encodedContentDescription; + mimeConverter->EncodeMimePartIIStr_UTF8(enc_content_desc_utf8, false, "UTF-8", + 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) { + uint32_t numCerts; + mCerts->GetLength(&numCerts); + PR_ASSERT(numCerts > 0); + if (numCerts == 0) return NS_ERROR_FAILURE; + } + + // Initialize the base64 encoder + MOZ_ASSERT(!mCryptoEncoder, "Shouldn't have an encoder already"); + mCryptoEncoder = 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); + 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) +{ + int status; + nsresult rv; + nsCOMPtr<nsICMSMessage> cinfo = do_CreateInstance(NS_CMSMESSAGE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsICMSEncoder> encoder = do_CreateInstance(NS_CMSENCODER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + char * header = nullptr; + nsCOMPtr<nsIStringBundleService> bundleSvc = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(bundleSvc, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsIStringBundle> sMIMEBundle; + nsString mime_smime_sig_content_desc; + + bundleSvc->CreateBundle(SMIME_STRBUNDLE_URL, getter_AddRefs(sMIMEBundle)); + + if (!sMIMEBundle) + return NS_ERROR_FAILURE; + + sMIMEBundle->GetStringFromName(u"mime_smimeSignatureContentDesc", + getter_Copies(mime_smime_sig_content_desc)); + + NS_ConvertUTF16toUTF8 sig_content_desc_utf8(mime_smime_sig_content_desc); + + /* Compute the hash... + */ + + nsAutoCString hashString; + mDataHash->Finish(false, hashString); + + mDataHash = nullptr; + + status = PR_GetError(); + if (status < 0) goto FAIL; + + /* 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) { + rv = NS_ERROR_OUT_OF_MEMORY; + goto FAIL; + } + + L = strlen(header); + if (aOuter) { + /* If this is the outer block, write it to the file. */ + uint32_t n; + rv = mStream->Write(header, L, &n); + if (NS_FAILED(rv) || n < L) { + // XXX This is -1, not an nsresult + rv = static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE); + } + } else { + /* If this is an inner block, feed it through the crypto stream. */ + rv = MimeCryptoWriteBlock (header, L); + } + + PR_Free(header); + + /* Create the signature... + */ + + NS_ASSERTION(mHashType, "Hash function for signature has not been set."); + + PR_ASSERT (mSelfSigningCert); + PR_SetError(0,0); + + rv = cinfo->CreateSigned(mSelfSigningCert, mSelfEncryptionCert, + (unsigned char*)hashString.get(), hashString.Length(), mHashType); + if (NS_FAILED(rv)) { + SetError(sendReport, u"ErrorCanNotSignMail"); + goto FAIL; + } + + // Initialize the base64 encoder for the signature data. + MOZ_ASSERT(!mSigEncoder, "Shouldn't already have a mSigEncoder"); + mSigEncoder = 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); + if (NS_FAILED(rv)) { + SetError(sendReport, u"ErrorCanNotSignMail"); + goto FAIL; + } + + // We're not passing in any data, so no update needed. + rv = encoder->Finish(); + if (NS_FAILED(rv)) { + SetError(sendReport, u"ErrorCanNotSignMail"); + goto FAIL; + } + + // Shut down the sig's base64 encoder. + rv = mSigEncoder->Flush(); + mSigEncoder = nullptr; + if (NS_FAILED(rv)) { + goto FAIL; + } + + /* Now write out the terminating boundary. + */ + { + uint32_t L; + char *header = PR_smprintf(CRLF "--%s--" CRLF, + mMultipartSignedBoundary); + PR_Free(mMultipartSignedBoundary); + mMultipartSignedBoundary = 0; + + if (!header) { + rv = NS_ERROR_OUT_OF_MEMORY; + goto FAIL; + } + L = strlen(header); + if (aOuter) { + /* If this is the outer block, write it to the file. */ + uint32_t n; + rv = mStream->Write(header, L, &n); + if (NS_FAILED(rv) || n < L) + // XXX This is -1, not an nsresult + rv = static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE); + } else { + /* If this is an inner block, feed it through the crypto stream. */ + rv = MimeCryptoWriteBlock (header, L); + } + } + +FAIL: + 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); + if (NS_FAILED(rv)) { + goto FAIL; + } + } + + /* 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); + goto FAIL; + } + } + + rv = mEncryptionContext->Finish(); + if (NS_FAILED(rv)) { + SetError(sendReport, u"ErrorEncryptMail"); + goto FAIL; + } + + mEncryptionContext = nullptr; + + PR_ASSERT(mEncryptionCinfo); + if (!mEncryptionCinfo) { + rv = NS_ERROR_FAILURE; + } + if (mEncryptionCinfo) { + mEncryptionCinfo = nullptr; + } + + // Shut down the base64 encoder. + mCryptoEncoder->Flush(); + mCryptoEncoder = nullptr; + + uint32_t n; + rv = mStream->Write(CRLF, 2, &n); + if (NS_FAILED(rv) || n < 2) + rv = NS_ERROR_FAILURE; + + FAIL: + return rv; +} + +/* Used to figure out what certs should be used when encrypting this message. + */ +nsresult nsMsgComposeSecure::MimeCryptoHackCerts(const char *aRecipients, + nsIMsgSendReport *sendReport, + bool aEncrypt, + bool aSign, + nsIMsgIdentity *aIdentity) +{ + nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID); + nsresult res; + + mCerts = do_CreateInstance(NS_ARRAY_CONTRACTID, &res); + if (NS_FAILED(res)) { + return res; + } + + 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) + + When retrieving the prefs, we try (in this order): + 1) *_cert_dbkey, if available + 2) *_cert_name (for maintaining backwards compatibility with preference + attributes written by earlier versions) + */ + + RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier()); + NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED); + + UniqueCERTCertList builtChain; + if (!mEncryptionCertDBKey.IsEmpty()) { + certdb->FindCertByDBKey(mEncryptionCertDBKey.get(), + getter_AddRefs(mSelfEncryptionCert)); + if (mSelfEncryptionCert && + (certVerifier->VerifyCert(mSelfEncryptionCert->GetCert(), + certificateUsageEmailRecipient, + mozilla::pkix::Now(), + nullptr, nullptr, + builtChain) != mozilla::pkix::Success)) { + // not suitable for encryption, so unset cert and clear pref + mSelfEncryptionCert = nullptr; + mEncryptionCertDBKey.Truncate(); + aIdentity->SetCharAttribute("encryption_cert_dbkey", + mEncryptionCertDBKey); + } + } + if (!mSelfEncryptionCert) { + certdb->FindEmailEncryptionCert(mEncryptionCertName, + getter_AddRefs(mSelfEncryptionCert)); + } + + // same procedure for the signing cert + if (!mSigningCertDBKey.IsEmpty()) { + certdb->FindCertByDBKey(mSigningCertDBKey.get(), + getter_AddRefs(mSelfSigningCert)); + if (mSelfSigningCert && + (certVerifier->VerifyCert(mSelfSigningCert->GetCert(), + certificateUsageEmailSigner, + mozilla::pkix::Now(), + nullptr, nullptr, + builtChain) != mozilla::pkix::Success)) { + // not suitable for signing, so unset cert and clear pref + mSelfSigningCert = nullptr; + mSigningCertDBKey.Truncate(); + aIdentity->SetCharAttribute("signing_cert_dbkey", mSigningCertDBKey); + } + } + if (!mSelfSigningCert) { + certdb->FindEmailSigningCert(mSigningCertName, + getter_AddRefs(mSelfSigningCert)); + } + + // must have both the signing and encryption certs to sign + if (!mSelfSigningCert && aSign) { + SetError(sendReport, u"NoSenderSigningCert"); + return NS_ERROR_FAILURE; + } + + if (!mSelfEncryptionCert && aEncrypt) { + SetError(sendReport, u"NoSenderEncryptionCert"); + return NS_ERROR_FAILURE; + } + + + if (aEncrypt && mSelfEncryptionCert) { + // Make sure self's configured cert is prepared for being used + // as an email recipient cert. + UniqueCERTCertificate nsscert(mSelfEncryptionCert->GetCert()); + if (!nsscert) { + return NS_ERROR_FAILURE; + } + // XXX: This does not respect the nsNSSShutDownObject protocol. + if (CERT_SaveSMimeProfile(nsscert.get(), nullptr, nullptr) != SECSuccess) { + return NS_ERROR_FAILURE; + } + } + + /* If the message is to be encrypted, then get the recipient certs */ + if (aEncrypt) { + nsTArray<nsCString> mailboxes; + ExtractEmails(EncodedHeader(nsDependentCString(aRecipients)), + UTF16ArrayAdapter<>(mailboxes)); + uint32_t count = mailboxes.Length(); + + bool already_added_self_cert = false; + + for (uint32_t i = 0; i < count; i++) { + nsCString mailbox_lowercase; + ToLowerCase(mailboxes[i], mailbox_lowercase); + nsCOMPtr<nsIX509Cert> cert; + res = certdb->FindCertByEmailAddress(mailbox_lowercase.get(), + getter_AddRefs(cert)); + if (NS_FAILED(res)) { + // Failure to find a valid encryption cert is fatal. + // Here I assume that mailbox is ascii rather than utf8. + SetErrorWithParam(sendReport, + u"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.) + */ + + bool isSame; + if (NS_SUCCEEDED(cert->Equals(mSelfEncryptionCert, &isSame)) + && isSame) { + already_added_self_cert = true; + } + + mCerts->AppendElement(cert, false); + } + + if (!already_added_self_cert) { + mCerts->AppendElement(mSelfEncryptionCert, false); + } + } + return res; +} + +NS_IMETHODIMP nsMsgComposeSecure::MimeCryptoWriteBlock (const char *buf, int32_t size) +{ + int status = 0; + nsresult rv; + + /* If this is a From line, mangle it before signing it. You just know + that something somewhere is going to mangle it later, and that's + going to cause the signature check to fail. + + (This assumes that, in the cases where From-mangling must happen, + this function is called a line at a time. That happens to be the + case.) + */ + if (size >= 5 && buf[0] == 'F' && !strncmp(buf, "From ", 5)) { + char mangle[] = ">"; + nsresult res = MimeCryptoWriteBlock (mangle, 1); + if (NS_FAILED(res)) + return res; + // This value will actually be cast back to an nsresult before use, so this + // cast is reasonable under the circumstances. + status = static_cast<int>(res); + } + + /* If we're signing, or signing-and-encrypting, feed this data into + the computation of the hash. */ + if (mDataHash) { + PR_SetError(0,0); + mDataHash->Update((const uint8_t*) buf, size); + status = PR_GetError(); + if (status < 0) goto FAIL; + } + + PR_SetError(0,0); + if (mEncryptionContext) { + /* If we're encrypting, or signing-and-encrypting, write this data + by filtering it through the crypto library. */ + + /* We want to create equally sized encryption strings */ + const char *inputBytesIterator = buf; + uint32_t inputBytesLeft = size; + + while (inputBytesLeft) { + const uint32_t spaceLeftInBuffer = eBufferSize - mBufferedBytes; + const uint32_t bytesToAppend = std::min(inputBytesLeft, spaceLeftInBuffer); + + memcpy(mBuffer+mBufferedBytes, inputBytesIterator, bytesToAppend); + mBufferedBytes += bytesToAppend; + + inputBytesIterator += bytesToAppend; + inputBytesLeft -= bytesToAppend; + + if (eBufferSize == mBufferedBytes) { + rv = mEncryptionContext->Update(mBuffer, mBufferedBytes); + mBufferedBytes = 0; + if (NS_FAILED(rv)) { + status = PR_GetError(); + PR_ASSERT(status < 0); + if (status >= 0) status = -1; + goto FAIL; + } + } + } + } else { + /* If we're not encrypting (presumably just signing) then write this + data directly to the file. */ + + uint32_t n; + rv = mStream->Write(buf, size, &n); + if (NS_FAILED(rv) || n < (uint32_t)size) { + // XXX MK_MIME_ERROR_WRITING_FILE is -1, which is not a valid nsresult + return static_cast<nsresult>(MK_MIME_ERROR_WRITING_FILE); + } + } + FAIL: + // XXX status sometimes has invalid nsresults like -1 or PR_GetError() + // assigned to it + return static_cast<nsresult>(status); +} + +/* Returns a string consisting of a Content-Type header, and a boundary + string, suitable for moving from the header block, down into the body + of a multipart object. The boundary itself is also returned (so that + the caller knows what to write to close it off.) + */ +static nsresult +make_multipart_signed_header_string(bool outer_p, + char **header_return, + char **boundary_return, + int16_t hash_type) +{ + const char *hashStr; + *header_return = 0; + *boundary_return = mime_make_separator("ms"); + + if (!*boundary_return) + return NS_ERROR_OUT_OF_MEMORY; + + switch (hash_type) { + case nsICryptoHash::SHA1: + hashStr = PARAM_MICALG_SHA1; + break; + case nsICryptoHash::SHA256: + hashStr = PARAM_MICALG_SHA256; + break; + case nsICryptoHash::SHA384: + hashStr = PARAM_MICALG_SHA384; + break; + case nsICryptoHash::SHA512: + hashStr = PARAM_MICALG_SHA512; + break; + default: + return NS_ERROR_INVALID_ARG; + } + + *header_return = PR_smprintf( + "Content-Type: " MULTIPART_SIGNED "; " + "protocol=\"" APPLICATION_PKCS7_SIGNATURE "\"; " + "micalg=%s; " + "boundary=\"%s\"" CRLF + CRLF + "%s%s" + "--%s" CRLF, + hashStr, + *boundary_return, + (outer_p ? crypto_multipart_blurb : ""), + (outer_p ? CRLF CRLF : ""), + *boundary_return); + + if (!*header_return) { + PR_Free(*boundary_return); + *boundary_return = 0; + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +/* Used as the output function of a SEC_PKCS7EncoderContext -- we feed + plaintext into the crypto engine, and it calls this function with encrypted + data; then this function writes a base64-encoded representation of that + data to the file (by filtering it through the given MimeEncoder object.) + + Also used as the output function of SEC_PKCS7Encode() -- but in that case, + it's used to write the encoded representation of the signature. The only + difference is which MimeEncoder object is used. + */ +static void +mime_crypto_write_base64 (void *closure, const char *buf, unsigned long size) +{ + MimeEncoder *encoder = (MimeEncoder *) closure; + nsresult rv = encoder->Write(buf, size); + PR_SetError(NS_FAILED(rv) ? static_cast<uint32_t>(rv) : 0, 0); +} + + +/* Used as the output function of MimeEncoder -- when we have generated + the signature for a multipart/signed object, this is used to write the + base64-encoded representation of the signature to the file. + */ +// TODO: size should probably be converted to uint32_t +nsresult mime_encoder_output_fn(const char *buf, int32_t size, void *closure) +{ + nsMsgComposeSecure *state = (nsMsgComposeSecure *) closure; + nsCOMPtr<nsIOutputStream> stream; + state->GetOutputStream(getter_AddRefs(stream)); + uint32_t n; + nsresult rv = stream->Write((char *) buf, size, &n); + if (NS_FAILED(rv) || n < (uint32_t)size) + return NS_ERROR_FAILURE; + else + return NS_OK; +} + +/* Like mime_encoder_output_fn, except this is used for the case where we + are both signing and encrypting -- the base64-encoded output of the + signature should be fed into the crypto engine, rather than being written + directly to the file. + */ +static nsresult +mime_nested_encoder_output_fn (const char *buf, int32_t size, void *closure) +{ + nsMsgComposeSecure *state = (nsMsgComposeSecure *) closure; + + // Copy to new null-terminated string so JS glue doesn't crash when + // MimeCryptoWriteBlock() is implemented in JS. + nsCString bufWithNull; + bufWithNull.Assign(buf, size); + return state->MimeCryptoWriteBlock(bufWithNull.get(), size); +} diff --git a/mailnews/extensions/smime/src/nsMsgComposeSecure.h b/mailnews/extensions/smime/src/nsMsgComposeSecure.h new file mode 100644 index 000000000..0f3b9ac60 --- /dev/null +++ b/mailnews/extensions/smime/src/nsMsgComposeSecure.h @@ -0,0 +1,106 @@ +/* -*- 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 "nsIMsgSMIMECompFields.h" +#include "nsCOMPtr.h" +#include "nsICMSEncoder.h" +#include "nsIX509Cert.h" +#include "nsIStringBundle.h" +#include "nsICryptoHash.h" +#include "nsICMSMessage.h" +#include "nsIMutableArray.h" +#include "nsStringGlue.h" +#include "nsIOutputStream.h" +#include "nsAutoPtr.h" + +class nsIMsgCompFields; +namespace mozilla { +namespace mailnews { +class MimeEncoder; +} +} + +class nsMsgSMIMEComposeFields : public nsIMsgSMIMECompFields +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIMSGSMIMECOMPFIELDS + + nsMsgSMIMEComposeFields(); + +private: + virtual ~nsMsgSMIMEComposeFields(); + bool mSignMessage; + bool mAlwaysEncryptMessage; +}; + +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 char16_t *name, + const char16_t **params, + uint32_t numParams, + char16_t **outString); + nsresult ExtractEncryptionState(nsIMsgIdentity * aIdentity, nsIMsgCompFields * aComposeFields, bool * aSignMessage, bool * aEncrypt); + + mimeDeliveryCryptoState mCryptoState; + nsCOMPtr<nsIOutputStream> mStream; + int16_t mHashType; + nsCOMPtr<nsICryptoHash> mDataHash; + nsAutoPtr<MimeEncoder> mSigEncoder; + char *mMultipartSignedBoundary; + nsString mSigningCertName; + nsAutoCString mSigningCertDBKey; + nsCOMPtr<nsIX509Cert> mSelfSigningCert; + nsString mEncryptionCertName; + nsAutoCString mEncryptionCertDBKey; + nsCOMPtr<nsIX509Cert> mSelfEncryptionCert; + nsCOMPtr<nsIMutableArray> mCerts; + nsCOMPtr<nsICMSMessage> mEncryptionCinfo; + nsCOMPtr<nsICMSEncoder> mEncryptionContext; + nsCOMPtr<nsIStringBundle> mSMIMEBundle; + + nsAutoPtr<MimeEncoder> mCryptoEncoder; + bool mIsDraft; + + enum {eBufferSize = 8192}; + char *mBuffer; + uint32_t mBufferedBytes; + + bool mErrorAlreadyReported; + void SetError(nsIMsgSendReport *sendReport, const char16_t *bundle_string); + void SetErrorWithParam(nsIMsgSendReport *sendReport, const char16_t *bundle_string, const char *param); +}; + +#endif diff --git a/mailnews/extensions/smime/src/nsMsgSMIMECID.h b/mailnews/extensions/smime/src/nsMsgSMIMECID.h new file mode 100644 index 000000000..0fbf2d1bf --- /dev/null +++ b/mailnews/extensions/smime/src/nsMsgSMIMECID.h @@ -0,0 +1,42 @@ +/* -*- 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 nsMsgSMIMECID_h__ +#define nsMsgSMIMECID_h__ + +#include "nsISupports.h" +#include "nsIFactory.h" +#include "nsIComponentManager.h" + +#define NS_MSGSMIMECOMPFIELDS_CONTRACTID \ + "@mozilla.org/messenger-smime/composefields;1" + +#define NS_MSGSMIMECOMPFIELDS_CID \ +{ /* 122C919C-96B7-49a0-BBC8-0ABC67EEFFE0 */ \ + 0x122c919c, 0x96b7, 0x49a0, \ + { 0xbb, 0xc8, 0xa, 0xbc, 0x67, 0xee, 0xff, 0xe0 }} + +#define NS_MSGCOMPOSESECURE_CID \ +{ /* dd753201-9a23-4e08-957f-b3616bf7e012 */ \ + 0xdd753201, 0x9a23, 0x4e08, \ + {0x95, 0x7f, 0xb3, 0x61, 0x6b, 0xf7, 0xe0, 0x12 }} + +#define NS_SMIMEJSHELPER_CONTRACTID \ + "@mozilla.org/messenger-smime/smimejshelper;1" + +#define NS_SMIMEJSJELPER_CID \ +{ /* d57d928c-60e4-4f81-999d-5c762e611205 */ \ + 0xd57d928c, 0x60e4, 0x4f81, \ + {0x99, 0x9d, 0x5c, 0x76, 0x2e, 0x61, 0x12, 0x05 }} + +#define NS_SMIMEENCRYPTURISERVICE_CONTRACTID \ + "@mozilla.org/messenger-smime/smime-encrypted-uris-service;1" + +#define NS_SMIMEENCRYPTURISERVICE_CID \ +{ /* a0134d58-018f-4d40-a099-fa079e5024a6 */ \ + 0xa0134d58, 0x018f, 0x4d40, \ + {0xa0, 0x99, 0xfa, 0x07, 0x9e, 0x50, 0x24, 0xa6 }} + +#endif // nsMsgSMIMECID_h__ diff --git a/mailnews/extensions/smime/src/nsSMimeJSHelper.cpp b/mailnews/extensions/smime/src/nsSMimeJSHelper.cpp new file mode 100644 index 000000000..c392980b6 --- /dev/null +++ b/mailnews/extensions/smime/src/nsSMimeJSHelper.cpp @@ -0,0 +1,335 @@ +/* -*- 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 "mozilla/mailnews/MimeHeaderParser.h" +#include "nspr.h" +#include "nsSMimeJSHelper.h" +#include "nsCOMPtr.h" +#include "nsMemory.h" +#include "nsStringGlue.h" +#include "nsIX509CertDB.h" +#include "nsIX509CertValidity.h" +#include "nsIServiceManager.h" +#include "nsServiceManagerUtils.h" +#include "nsCRTGlue.h" + +using namespace mozilla::mailnews; + +NS_IMPL_ISUPPORTS(nsSMimeJSHelper, nsISMimeJSHelper) + +nsSMimeJSHelper::nsSMimeJSHelper() +{ +} + +nsSMimeJSHelper::~nsSMimeJSHelper() +{ +} + +NS_IMETHODIMP nsSMimeJSHelper::GetRecipientCertsInfo( + nsIMsgCompFields *compFields, + uint32_t *count, + char16_t ***emailAddresses, + int32_t **certVerification, + char16_t ***certIssuedInfos, + char16_t ***certExpiresInfos, + nsIX509Cert ***certs, + bool *canEncrypt) +{ + NS_ENSURE_ARG_POINTER(count); + *count = 0; + + NS_ENSURE_ARG_POINTER(emailAddresses); + NS_ENSURE_ARG_POINTER(certVerification); + NS_ENSURE_ARG_POINTER(certIssuedInfos); + NS_ENSURE_ARG_POINTER(certExpiresInfos); + NS_ENSURE_ARG_POINTER(certs); + NS_ENSURE_ARG_POINTER(canEncrypt); + + NS_ENSURE_ARG_POINTER(compFields); + + nsTArray<nsCString> mailboxes; + nsresult rv = getMailboxList(compFields, mailboxes); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t mailbox_count = mailboxes.Length(); + + nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID); + + *count = mailbox_count; + *canEncrypt = false; + rv = NS_OK; + + if (mailbox_count) + { + char16_t **outEA = static_cast<char16_t **>(moz_xmalloc(mailbox_count * sizeof(char16_t *))); + int32_t *outCV = static_cast<int32_t *>(moz_xmalloc(mailbox_count * sizeof(int32_t))); + char16_t **outCII = static_cast<char16_t **>(moz_xmalloc(mailbox_count * sizeof(char16_t *))); + char16_t **outCEI = static_cast<char16_t **>(moz_xmalloc(mailbox_count * sizeof(char16_t *))); + nsIX509Cert **outCerts = static_cast<nsIX509Cert **>(moz_xmalloc(mailbox_count * sizeof(nsIX509Cert *))); + + if (!outEA || !outCV || !outCII || !outCEI || !outCerts) + { + free(outEA); + free(outCV); + free(outCII); + free(outCEI); + free(outCerts); + rv = NS_ERROR_OUT_OF_MEMORY; + } + else + { + char16_t **iEA = outEA; + int32_t *iCV = outCV; + char16_t **iCII = outCII; + char16_t **iCEI = outCEI; + nsIX509Cert **iCert = outCerts; + + bool found_blocker = false; + bool memory_failure = false; + + for (uint32_t i = 0; + i < mailbox_count; + ++i, ++iEA, ++iCV, ++iCII, ++iCEI, ++iCert) + { + *iCert = nullptr; + *iCV = 0; + *iCII = nullptr; + *iCEI = nullptr; + + if (memory_failure) { + *iEA = nullptr; + continue; + } + + nsCString &email = mailboxes[i]; + *iEA = ToNewUnicode(NS_ConvertUTF8toUTF16(email)); + if (!*iEA) { + memory_failure = true; + continue; + } + + nsCString email_lowercase; + ToLowerCase(email, email_lowercase); + + nsCOMPtr<nsIX509Cert> cert; + if (NS_SUCCEEDED(certdb->FindCertByEmailAddress( + email_lowercase.get(), getter_AddRefs(cert)))) + { + *iCert = cert; + NS_ADDREF(*iCert); + + nsCOMPtr<nsIX509CertValidity> validity; + rv = cert->GetValidity(getter_AddRefs(validity)); + + if (NS_SUCCEEDED(rv)) { + nsString id, ed; + + if (NS_SUCCEEDED(validity->GetNotBeforeLocalDay(id))) + { + *iCII = ToNewUnicode(id); + if (!*iCII) { + memory_failure = true; + continue; + } + } + + if (NS_SUCCEEDED(validity->GetNotAfterLocalDay(ed))) + { + *iCEI = ToNewUnicode(ed); + if (!*iCEI) { + memory_failure = true; + continue; + } + } + } + } + else + { + found_blocker = true; + } + } + + if (memory_failure) { + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mailbox_count, outEA); + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mailbox_count, outCII); + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(mailbox_count, outCEI); + NS_FREE_XPCOM_ISUPPORTS_POINTER_ARRAY(mailbox_count, outCerts); + free(outCV); + rv = NS_ERROR_OUT_OF_MEMORY; + } + else { + if (mailbox_count > 0 && !found_blocker) + { + *canEncrypt = true; + } + + *emailAddresses = outEA; + *certVerification = outCV; + *certIssuedInfos = outCII; + *certExpiresInfos = outCEI; + *certs = outCerts; + } + } + } + return rv; +} + +NS_IMETHODIMP nsSMimeJSHelper::GetNoCertAddresses( + nsIMsgCompFields *compFields, + uint32_t *count, + char16_t ***emailAddresses) +{ + NS_ENSURE_ARG_POINTER(count); + *count = 0; + + NS_ENSURE_ARG_POINTER(emailAddresses); + + NS_ENSURE_ARG_POINTER(compFields); + + nsTArray<nsCString> mailboxes; + nsresult rv = getMailboxList(compFields, mailboxes); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t mailbox_count = mailboxes.Length(); + + if (!mailbox_count) + { + *count = 0; + *emailAddresses = nullptr; + return NS_OK; + } + + nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID); + + uint32_t missing_count = 0; + bool *haveCert = new bool[mailbox_count]; + if (!haveCert) + { + return NS_ERROR_OUT_OF_MEMORY; + } + + rv = NS_OK; + + if (mailbox_count) + { + for (uint32_t i = 0; i < mailbox_count; ++i) + { + haveCert[i] = false; + + nsCString email_lowercase; + ToLowerCase(mailboxes[i], email_lowercase); + + nsCOMPtr<nsIX509Cert> cert; + if (NS_SUCCEEDED(certdb->FindCertByEmailAddress( + email_lowercase.get(), getter_AddRefs(cert)))) + haveCert[i] = true; + + if (!haveCert[i]) + ++missing_count; + } + } + + *count = missing_count; + + if (missing_count) + { + char16_t **outEA = static_cast<char16_t **>(moz_xmalloc(missing_count * sizeof(char16_t *))); + if (!outEA ) + { + rv = NS_ERROR_OUT_OF_MEMORY; + } + else + { + char16_t **iEA = outEA; + + bool memory_failure = false; + + for (uint32_t i = 0; i < mailbox_count; ++i) + { + if (!haveCert[i]) + { + if (memory_failure) { + *iEA = nullptr; + } + else { + *iEA = ToNewUnicode(NS_ConvertUTF8toUTF16(mailboxes[i])); + if (!*iEA) { + memory_failure = true; + } + } + ++iEA; + } + } + + if (memory_failure) { + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(missing_count, outEA); + rv = NS_ERROR_OUT_OF_MEMORY; + } + else { + *emailAddresses = outEA; + } + } + } + else + { + *emailAddresses = nullptr; + } + + delete [] haveCert; + return rv; +} + +nsresult nsSMimeJSHelper::getMailboxList(nsIMsgCompFields *compFields, + nsTArray<nsCString> &mailboxes) +{ + if (!compFields) + return NS_ERROR_INVALID_ARG; + + nsresult res; + nsString to, cc, bcc, ng; + + res = compFields->GetTo(to); + if (NS_FAILED(res)) + return res; + + res = compFields->GetCc(cc); + if (NS_FAILED(res)) + return res; + + res = compFields->GetBcc(bcc); + if (NS_FAILED(res)) + return res; + + res = compFields->GetNewsgroups(ng); + if (NS_FAILED(res)) + return res; + + { + nsCString all_recipients; + + if (!to.IsEmpty()) { + all_recipients.Append(NS_ConvertUTF16toUTF8(to)); + all_recipients.Append(','); + } + + if (!cc.IsEmpty()) { + all_recipients.Append(NS_ConvertUTF16toUTF8(cc)); + all_recipients.Append(','); + } + + if (!bcc.IsEmpty()) { + all_recipients.Append(NS_ConvertUTF16toUTF8(bcc)); + all_recipients.Append(','); + } + + if (!ng.IsEmpty()) + all_recipients.Append(NS_ConvertUTF16toUTF8(ng)); + + ExtractEmails(EncodedHeader(all_recipients), + UTF16ArrayAdapter<>(mailboxes)); + } + + return NS_OK; +} diff --git a/mailnews/extensions/smime/src/nsSMimeJSHelper.h b/mailnews/extensions/smime/src/nsSMimeJSHelper.h new file mode 100644 index 000000000..403ab2098 --- /dev/null +++ b/mailnews/extensions/smime/src/nsSMimeJSHelper.h @@ -0,0 +1,26 @@ +/* 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 _nsSMimeJSHelper_H_ +#define _nsSMimeJSHelper_H_ + +#include "nsISMimeJSHelper.h" +#include "nsIX509Cert.h" +#include "nsIMsgCompFields.h" + +class nsSMimeJSHelper : public nsISMimeJSHelper +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISMIMEJSHELPER + + nsSMimeJSHelper(); + +private: + virtual ~nsSMimeJSHelper(); + nsresult getMailboxList(nsIMsgCompFields *compFields, + nsTArray<nsCString> &mailboxes); +}; + +#endif diff --git a/mailnews/extensions/smime/src/smime-service.js b/mailnews/extensions/smime/src/smime-service.js new file mode 100644 index 000000000..9fa618fd6 --- /dev/null +++ b/mailnews/extensions/smime/src/smime-service.js @@ -0,0 +1,24 @@ +/* -*- Mode: C++; 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/. */ + +Components.utils.import("resource://gre/modules/XPCOMUtils.jsm"); + +function SMIMEService() {} + +SMIMEService.prototype = { + name: "smime", + chromePackageName: "messenger", + showPanel: function(server) { + // don't show the panel for news, rss, or local accounts + return (server.type != "nntp" && server.type != "rss" && + server.type != "im" && server.type != "none"); + }, + + QueryInterface: XPCOMUtils.generateQI([Components.interfaces.nsIMsgAccountManagerExtension]), + classID: Components.ID("{f2809796-1dd1-11b2-8c1b-8f15f007c699}"), +}; + +var components = [SMIMEService]; +var NSGetFactory = XPCOMUtils.generateNSGetFactory(components); diff --git a/mailnews/extensions/smime/src/smime-service.manifest b/mailnews/extensions/smime/src/smime-service.manifest new file mode 100644 index 000000000..1b799ed7b --- /dev/null +++ b/mailnews/extensions/smime/src/smime-service.manifest @@ -0,0 +1,3 @@ +component {f2809796-1dd1-11b2-8c1b-8f15f007c699} smime-service.js +contract @mozilla.org/accountmanager/extension;1?name=smime {f2809796-1dd1-11b2-8c1b-8f15f007c699} +category mailnews-accountmanager-extensions smime-account-manager-extension @mozilla.org/accountmanager/extension;1?name=smime |