Bug 1627736 - Improve the UI and UX of the OpenPGP configuration workflow. r=mkmelin a=wsmwk
authorAlessandro Castellani <alessandro@thunderbird.net>
Mon, 22 Jun 2020 13:29:07 +0200
changeset 39438 ba230614672e3f9ca567e11c4dec35d12bbd2882
parent 39437 877b593dafb5fb1f57356482c968aced8c637e79
child 39439 96bab75d9f30c8186205a845c9510d6ed0a0dba7
push id402
push userclokep@gmail.com
push dateMon, 29 Jun 2020 20:48:04 +0000
reviewersmkmelin, wsmwk
bugs1627736
Bug 1627736 - Improve the UI and UX of the OpenPGP configuration workflow. r=mkmelin a=wsmwk
mail/extensions/am-e2e/am-e2e.inc.xhtml
mail/extensions/am-e2e/am-e2e.js
mail/extensions/am-e2e/am-e2e.xhtml
mail/extensions/openpgp/content/modules/keyRing.jsm
mail/extensions/openpgp/content/strings/enigmail.ftl
mail/extensions/openpgp/content/strings/key-wizard.ftl
mail/extensions/openpgp/content/ui/enigmailKeygen.js
mail/extensions/openpgp/content/ui/keyDetailsDlg.js
mail/extensions/openpgp/content/ui/keyDetailsDlg.xhtml
mail/extensions/openpgp/content/ui/keyWizard.js
mail/extensions/openpgp/content/ui/keyWizard.xhtml
mail/locales/jar.mn
mail/themes/shared/jar.inc.mn
mail/themes/shared/mail/accountManage.css
mail/themes/shared/mail/icons/fingerprint.svg
mail/themes/shared/mail/icons/login.svg
mail/themes/shared/openpgp/inlineNotification.css
mail/themes/shared/openpgp/keyWizard.css
--- a/mail/extensions/am-e2e/am-e2e.inc.xhtml
+++ b/mail/extensions/am-e2e/am-e2e.inc.xhtml
@@ -6,59 +6,107 @@
 <script src="chrome://openpgp/content/BondOpenPGP.jsm"/>
 #endif
 
     <vbox id="e2eEditing">
 
       <stringbundle id="bundle_e2e" src="chrome://messenger/locale/am-smime.properties"/>
       <stringbundle id="bundle_brand" src="chrome://branding/locale/brand.properties"/>
 
+      <linkset>
+        <html:link rel="localization" href="messenger/openpgp/enigmail.ftl"/>
+      </linkset>
+
       <label hidden="true" wsm_persist="true" id="identity_encryptionpolicy"/>
       <label hidden="true" wsm_persist="true" id="identity_e2etechpref"/>
 
       <vbox>
         <description flex="1">
           <html:span class="tail-with-learn-more">&e2eIntro.description;</html:span>
           <label is="text-link" id="acceptLearnMoreE2E"
                  href="https://support.mozilla.org/kb/introduction-to-e2e-encryption"
                  value="&e2eLearnMore.label;"/>
         </description>
       </vbox>
 
 #ifdef MOZ_OPENPGP
       <html:div>
-      <html:fieldset id="openpgpOptions" class="openpgp-item">
-        <html:legend>&openpgpKeys.label;</html:legend>
+        <html:fieldset id="openpgpOptions" class="openpgp-item"
+                       aria-describedby="openPgpgDescription">
+          <html:legend>&openpgpKeys.label;</html:legend>
 
-        <label id="identity_openpgp_key_nameLabel"
-               value="&openpgpKey.message;" control="identity_openpgp_key_id"/>
-        <hbox align="center" class="input-container">
-          <html:input id="identity_openpgp_key_id" type="text"
-                      class="input-inline"
-                      readonly="readonly"
-                      disabled="disabled"
-                      aria-labelledby="identity_openpgp_key_nameLabel"
-                      wsm_persist="true"
-                      prefstring="mail.identity.%identitykey%.openpgp_key_id"/>
+          <vbox data-subcategory="openpgp" class="openpgp-container">
+            <hbox align="center" class="opengpg-intro-section">
+              <image id="openPgpKey"/>
+              <vbox flex="1">
+                <description class="description-with-side-element openpgp-description">
+                  <html:p id="openPgpgDescription"></html:p>
+                  <image id="openPgpStatusImage" class="status-success"
+                         hidden="true"/>
+                  <html:span id="openPgpgSelectionStatus"
+                             class="tail-with-learn-more"
+                             hidden="hidden"></html:span>
+                  <label is="text-link" id="openPgpLearnMore"
+                         href="https://support.mozilla.org/kb/introduction-to-e2e-encryption"
+                         data-l10n-id="e2e-learn-more"
+                         class="learnMore"
+                         hidden="true"/>
+                </description>
+              </vbox>
+              <vbox>
+                <!-- Please don't remove the wrapping hbox/vbox/box for these elements. It's used to properly compute the search tooltip position. -->
+                <hbox>
+                  <button id="addOpenPgpButton"
+                          data-l10n-id="openpgp-add-key-button"
+                          oncommand="openKeyWizard();"
+                          class="accessory-button"
+                          flex="1"/>
+                </hbox>
+              </vbox>
+            </hbox>
 
-          <button id="openpgpKeySelectButton"
-                  label="&openpgpKey.button;"
-                  accesskey="&openpgpKey.accesskey;"
-                  oncommand="pgpSelectKey('identity_openpgp_key_id')"/>
-          <button id="openpgpKeyClearButton"
-                  label="&encryption.certificate_clear.button;"
-                  oncommand="pgpClearKey('identity_openpgp_key_id')"/>
-        </hbox>
+            <hbox id="openPgpNotification"
+                  class="inline-notification-container success-container"
+                  collapsed="true">
+              <hbox class="inline-notification-wrapper">
+                <image class="notification-image notification-image-success"/>
+                <description id="openPgpNotificationDescription"/>
+                <button class="close-icon" oncommand="closeNotification()"/>
+              </hbox>
+            </hbox>
 
-        <hbox align="right">
-          <button id="openOpenPGPKeyManagerButton" oncommand="BondOpenPGP.openKeyManager(window);"
-                  label="&manageKeys.label;"/>
-        </hbox>
-      </html:fieldset>
+            <vbox id="openPgpKeyList">
+              <radiogroup id="openPgpKeyListRadio">
+                <vbox id="openPgpOptionNone" class="content-blocking-category">
+                  <hbox>
+                    <radio id="openPgpNone"
+                          value=""
+                          data-l10n-id="openpgp-radio-none"
+                          flex="1"/>
+                  </hbox>
+                  <vbox class="indent">
+                    <description data-l10n-id="openpgp-radio-none-desc"/>
+                  </vbox>
+                </vbox>
+                <!-- All available keys will be appended here. -->
+              </radiogroup>
+            </vbox>
+          </vbox>
+
+          <separator class="thin"/>
+
+          <hbox>
+            <button id="openOpenPGPKeyManagerButton"
+                    oncommand="BondOpenPGP.openKeyManager(window);"
+                    label="&manageKeys.label;"/>
+          </hbox>
+        </html:fieldset>
       </html:div>
+
+      <separator/>
 #endif
 
       <html:div>
       <html:fieldset id="smimeOptions">
         <html:legend>&certificates2.label;</html:legend>
 
         <label id="identity_signing_cert_nameLabel"
                value="&signingCert2.message;" control="identity_signing_cert_name"
@@ -79,16 +127,18 @@
                   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"/>
+
         <label value="&encryptionCert2.message;"
                control="identity_encryption_cert_name"/>
 
         <hbox align="center" class="input-container">
           <html:input id="identity_encryption_cert_name" type="text"
                       class="input-inline"
                       readonly="readonly"
                       disabled="disabled"
@@ -101,26 +151,30 @@
                   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"/>
+
         <hbox align="right">
           <button id="openCertManagerButton" oncommand="openCertManager();"
                   label="&manageCerts3.label;" accesskey="&manageCerts3.accesskey;"/>
           <button id="openDeviceManagerButton" oncommand="openDeviceManager();"
                   label="&manageDevices2.label;" accesskey="&manageDevices2.accesskey;"/>
         </hbox>
 
       </html:fieldset>
       </html:div>
 
+      <separator/>
+
       <html:legend>&sendingDefaults.label;</html:legend>
 
       <html:div>
       <html:fieldset id="encryption_titlebox">
         <description flex="1">&e2eEnc.description;</description>
 
         <radiogroup id="encryptionChoices" class="indent">
           <radio id="encrypt_no" wsm_persist="true" value="0"
--- a/mail/extensions/am-e2e/am-e2e.js
+++ b/mail/extensions/am-e2e/am-e2e.js
@@ -1,25 +1,36 @@
 /* -*- 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/. */
 
+/* import-globals-from ../../../../toolkit/content/preferencesBindings.js */
+
+// Modules
+/* global GetEnigmailSvc: false, PgpSqliteDb2: false */
+
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var { MailConstants } = ChromeUtils.import(
   "resource:///modules/MailConstants.jsm"
 );
 var { BondOpenPGP } = ChromeUtils.import(
   "chrome://openpgp/content/BondOpenPGP.jsm"
 );
+var { EnigmailKey } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/key.jsm"
+);
 
 if (MailConstants.MOZ_OPENPGP && BondOpenPGP.allDependenciesLoaded()) {
   var { EnigmailKeyRing } = ChromeUtils.import(
     "chrome://openpgp/content/modules/keyRing.jsm"
   );
+  var EnigmailCryptoAPI = ChromeUtils.import(
+    "chrome://openpgp/content/modules/cryptoAPI.jsm"
+  ).EnigmailCryptoAPI;
 }
 
 var nsIX509CertDB = Ci.nsIX509CertDB;
 var nsX509CertDBContractID = "@mozilla.org/security/x509certdb;1";
 var nsIX509Cert = Ci.nsIX509Cert;
 
 var email_signing_cert_usage = 4; // SECCertUsage.certUsageEmailSigner
 var email_recipient_cert_usage = 5; // SECCertUsage.certUsageEmailRecipient
@@ -35,17 +46,16 @@ var gSignMessages = null;
 var gRequireEncrypt = null;
 var gDoNotEncrypt = null;
 var gKeyId = null;
 var gBundle = null;
 var gBrandBundle;
 var gSmimePrefbranch;
 var kEncryptionCertPref = "identity_encryption_cert_name";
 var kSigningCertPref = "identity_signing_cert_name";
-var kOpenPGPKeyPref = "identity_openpgp_key_id";
 
 var gTechAuto = null;
 var gTechPrefOpenPGP = null;
 var gTechPrefSMIME = null;
 
 function onInit() {
   if (!MailConstants.MOZ_OPENPGP || !BondOpenPGP.allDependenciesLoaded()) {
     for (let item of document.querySelectorAll(".openpgp-item")) {
@@ -66,18 +76,35 @@ function e2eInitializeFields() {
   gSignCertName = document.getElementById(kSigningCertPref);
   gSignMessages = document.getElementById("identity_sign_mail");
   gRequireEncrypt = document.getElementById("encrypt_require");
   gDoNotEncrypt = document.getElementById("encrypt_no");
   gBundle = document.getElementById("bundle_e2e");
   gBrandBundle = document.getElementById("bundle_brand");
 
   if (MailConstants.MOZ_OPENPGP && BondOpenPGP.allDependenciesLoaded()) {
+    document
+      .getElementById("openPgpKeyListRadio")
+      .setAttribute(
+        "preference",
+        `mail.identity.${gIdentity.key}.openpgp_key_id`
+      );
+
+    if (!Preferences.get(`mail.identity.${gIdentity.key}.openpgp_key_id`)) {
+      Preferences.add({
+        id: `mail.identity.${gIdentity.key}.openpgp_key_id`,
+        type: "string",
+      });
+    }
+
     gTechChoices = document.getElementById("technologyChoices");
-    gKeyId = document.getElementById(kOpenPGPKeyPref);
+    gKeyId = Services.prefs.getStringPref(
+      `mail.identity.${gIdentity.key}.openpgp_key_id`,
+      ""
+    );
     gTechAuto = document.getElementById("technology_automatic");
     gTechPrefOpenPGP = document.getElementById("technology_prefer_openpgp");
     gTechPrefSMIME = document.getElementById("technology_prefer_smime");
   }
 
   if (!gIdentity) {
     // The user is going to create a new identity.
     // Set everything to default values.
@@ -87,36 +114,37 @@ function e2eInitializeFields() {
     gEncryptionCertName.value = "";
     gEncryptionCertName.displayName = "";
     gEncryptionCertName.dbKey = "";
 
     gSignCertName.value = "";
     gSignCertName.displayName = "";
     gSignCertName.dbKey = "";
 
+    // If the user doesn't have an identity defined but OpenPGP is available,
+    // we hide the entire section to avoid issues and edge cases.
     if (MailConstants.MOZ_OPENPGP && BondOpenPGP.allDependenciesLoaded()) {
-      gKeyId.value = "";
+      document
+        .getElementById("openpgpOptions")
+        .setAttribute("hidden", "hidden");
     }
 
     gRequireEncrypt.disabled = true;
     gDoNotEncrypt.disabled = true;
     gSignMessages.disabled = true;
 
     gSignMessages.checked = false;
     gEncryptionChoices.value = 0;
     if (MailConstants.MOZ_OPENPGP && BondOpenPGP.allDependenciesLoaded()) {
       gTechChoices.value = 0;
     }
   } else {
     var certdb = Cc[nsX509CertDBContractID].getService(nsIX509CertDB);
     var x509cert = null;
 
-    if (MailConstants.MOZ_OPENPGP && BondOpenPGP.allDependenciesLoaded()) {
-      gKeyId.value = gIdentity.getUnicharAttribute("openpgp_key_id");
-    }
     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
@@ -135,17 +163,17 @@ function e2eInitializeFields() {
 
     gEncryptionChoices.value = gIdentity.getIntAttribute("encryptionpolicy");
     if (MailConstants.MOZ_OPENPGP && BondOpenPGP.allDependenciesLoaded()) {
       gTechChoices.value = gIdentity.getIntAttribute("e2etechpref");
     }
 
     let enableEnc = !!gEncryptionCertName.value;
     if (MailConstants.MOZ_OPENPGP && BondOpenPGP.allDependenciesLoaded()) {
-      enableEnc = enableEnc || !!gKeyId.value;
+      enableEnc = enableEnc || !!gKeyId;
     }
 
     gRequireEncrypt.disabled = !enableEnc;
     gDoNotEncrypt.disabled = !enableEnc;
     enableEncryptionControls(enableEnc);
 
     gSignCertName.value = gIdentity.getUnicharAttribute("signing_cert_name");
     gSignCertName.dbKey = gIdentity.getCharAttribute("signing_cert_dbkey");
@@ -162,54 +190,91 @@ function e2eInitializeFields() {
         gSignCertName.displayName = x509cert.displayName;
       }
     } catch (e) {}
 
     gSignMessages.checked = gIdentity.getBoolAttribute("sign_mail");
 
     let enableSig = gSignCertName.value;
     if (MailConstants.MOZ_OPENPGP && BondOpenPGP.allDependenciesLoaded()) {
-      enableSig = enableSig || !!gKeyId.value;
+      enableSig = enableSig || !!gKeyId;
     }
 
     gSignMessages.disabled = !enableSig;
     enableSigningControls(enableSig);
   }
 
   // Always start with enabling select buttons.
   // This will keep the visibility of buttons in a sane state as user
   // jumps from security panel of one account to another.
   enableSelectButtons();
   updateTechPref();
+
+  initOpenPgpSettings();
+}
+
+/**
+ * Initialize the OpenPGP settings, apply strings, and load the key radio UI.
+ */
+async function initOpenPgpSettings() {
+  if (!MailConstants.MOZ_OPENPGP || !BondOpenPGP.allDependenciesLoaded()) {
+    return;
+  }
+
+  let result = {};
+  EnigmailKeyRing.getAllSecretKeysByEmail(gIdentity.email, result);
+
+  document.l10n.setAttributes(
+    document.getElementById("openPgpgDescription"),
+    "openpgp-description",
+    {
+      count: result.all.length,
+      identity: gIdentity.email,
+    }
+  );
+
+  // Force deselect the currently selected first index fo the radiogroup if
+  // an OpenPGP Key is currently set. This is necessary to allow the selection
+  // of the currently used key.
+  if (gKeyId) {
+    document.getElementById("openPgpKeyListRadio").selectedIndex = -1;
+  }
+
+  // Load all the available keys.
+  reloadOpenPgpUI();
+
+  // Listen for the preference changes.
+  Preferences.get(`mail.identity.${gIdentity.key}.openpgp_key_id`).on(
+    "change",
+    updateOpenPgpSettings
+  );
 }
 
 function onPreInit(account, accountValues) {
   gIdentity = account.defaultIdentity;
 }
 
 function onSave() {
   e2eSave();
   window.dispatchEvent(new CustomEvent("prefchange"));
 }
 
 function e2eSave() {
-  // find out which radio for the encryption radio group is selected and set that on our hidden encryptionChoice pref....
+  // 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);
 
   if (MailConstants.MOZ_OPENPGP && BondOpenPGP.allDependenciesLoaded()) {
     newValue = gTechChoices.value;
     gHiddenTechPref.setAttribute("value", newValue);
     gIdentity.setIntAttribute("e2etechpref", newValue);
   }
 
-  if (MailConstants.MOZ_OPENPGP && BondOpenPGP.allDependenciesLoaded()) {
-    gIdentity.setUnicharAttribute("openpgp_key_id", gKeyId.value);
-  }
   gIdentity.setUnicharAttribute(
     "encryption_cert_name",
     gEncryptionCertName.displayName || gEncryptionCertName.value
   );
   gIdentity.setCharAttribute(
     "encryption_cert_dbkey",
     gEncryptionCertName.dbKey
   );
@@ -296,58 +361,16 @@ function checkOtherCert(
   if (userWantsSameCert) {
     otherCertInfo.value = cert.displayName + " [" + cert.serialNumber + "]";
     otherCertInfo.displayName = cert.displayName;
     otherCertInfo.dbKey = cert.dbKey;
     enabler(true);
   }
 }
 
-function pgpSelectKey(pgp_key) {
-  if (!MailConstants.MOZ_OPENPGP || !BondOpenPGP.allDependenciesLoaded()) {
-    return;
-  }
-
-  var keyInfo = document.getElementById(pgp_key);
-  if (!keyInfo) {
-    return;
-  }
-
-  let result = {};
-  EnigmailKeyRing.getAllSecretKeysByEmail(gIdentity.email, result);
-
-  let params = {
-    keys: result.all,
-    identity: gIdentity.fullAddress,
-    canceled: true,
-    index: -1,
-  };
-
-  window.docShell.rootTreeItem.domWindow.openDialog(
-    "chrome://openpgp/content/ui/keyPicker.xhtml",
-    "",
-    "dialog,close,titlebar,modal,resizable",
-    params
-  );
-
-  if (params.canceled) {
-    return;
-  }
-
-  keyInfo.value = result.all[params.index].keyId;
-  keyInfo.displayName = result.all[params.index].keyId;
-
-  enableEncryptionControls(true);
-  enableSigningControls(true);
-
-  updateTechPref();
-  enableSelectButtons();
-  onSave();
-}
-
 function smimeSelectCert(smime_cert) {
   var certInfo = document.getElementById(smime_cert);
   if (!certInfo) {
     return;
   }
 
   var picker = Cc["@mozilla.org/user_cert_picker;1"].createInstance(
     Ci.nsIUserCertPicker
@@ -458,64 +481,32 @@ function enableSelectButtons() {
   document.getElementById(
     "signingCertClearButton"
   ).disabled = !gSignCertName.value;
 
   gEncryptionCertName.disabled = !gEncryptionCertName.value;
   document.getElementById(
     "encryptionCertClearButton"
   ).disabled = !gEncryptionCertName.value;
-
-  if (MailConstants.MOZ_OPENPGP && BondOpenPGP.allDependenciesLoaded()) {
-    gKeyId.disabled = !gKeyId.value;
-    document.getElementById("openpgpKeyClearButton").disabled = !gKeyId.value;
-  }
-}
-
-function pgpClearKey(pgp_key) {
-  if (!MailConstants.MOZ_OPENPGP || !BondOpenPGP.allDependenciesLoaded()) {
-    return;
-  }
-  var keyInfo = document.getElementById(pgp_key);
-  if (!keyInfo) {
-    return;
-  }
-
-  keyInfo.disabled = true;
-  keyInfo.value = "";
-
-  let stillHaveOtherSigning = gSignCertName && gSignCertName.value;
-  let stillHaveOtherEncryption =
-    gEncryptionCertName && gEncryptionCertName.value;
-
-  if (!stillHaveOtherEncryption) {
-    enableEncryptionControls(false);
-  }
-  if (!stillHaveOtherSigning) {
-    enableSigningControls(false);
-  }
-  updateTechPref();
-  enableSelectButtons();
-  onSave();
 }
 
 function smimeClearCert(smime_cert) {
   var certInfo = document.getElementById(smime_cert);
   if (!certInfo) {
     return;
   }
 
   certInfo.disabled = true;
   certInfo.value = "";
   certInfo.displayName = "";
   certInfo.dbKey = "";
 
   let stillHaveOther = false;
   if (MailConstants.MOZ_OPENPGP && BondOpenPGP.allDependenciesLoaded()) {
-    stillHaveOther = gKeyId && gKeyId.value;
+    stillHaveOther = gKeyId != "";
   }
 
   if (!stillHaveOther) {
     if (smime_cert == kEncryptionCertPref) {
       enableEncryptionControls(false);
     } else if (smime_cert == kSigningCertPref) {
       enableSigningControls(false);
     }
@@ -528,17 +519,17 @@ function smimeClearCert(smime_cert) {
 
 function updateTechPref() {
   if (!MailConstants.MOZ_OPENPGP || !BondOpenPGP.allDependenciesLoaded()) {
     return;
   }
 
   let haveSigCert = gSignCertName && gSignCertName.value;
   let haveEncCert = gEncryptionCertName && gEncryptionCertName.value;
-  let havePgpkey = gKeyId && gKeyId.value;
+  let havePgpkey = !!gKeyId;
 
   let enable = (haveSigCert || haveEncCert) && havePgpkey;
 
   gTechAuto.disabled = !enable;
   gTechPrefOpenPGP.disabled = !enable;
   gTechPrefSMIME.disabled = !enable;
 
   if (!enable) {
@@ -554,8 +545,528 @@ function openCertManager() {
 
 function openDeviceManager() {
   parent.gSubDialog.open("chrome://pippki/content/device_manager.xhtml");
 }
 
 function e2eOnLoadEditor() {
   e2eInitializeFields();
 }
+
+/**
+ * Open the subdialog to create or import an OpenPGP key.
+ */
+function openKeyWizard() {
+  if (!MailConstants.MOZ_OPENPGP || !BondOpenPGP.allDependenciesLoaded()) {
+    return;
+  }
+
+  let args = {
+    identity: gIdentity,
+    gSubDialog: parent.gSubDialog,
+    okCallback: keyWizardSuccess,
+  };
+  parent.gSubDialog.open(
+    "chrome://openpgp/content/ui/keyWizard.xhtml",
+    null,
+    args
+  );
+}
+
+/**
+ * Show a succesfull notification after a new OpenPGP key was created, and
+ * trigger the reload of the key listing UI.
+ */
+async function keyWizardSuccess() {
+  document.l10n.setAttributes(
+    document.getElementById("openPgpNotificationDescription"),
+    "openpgp-keygen-success"
+  );
+  document.getElementById("openPgpNotification").collapsed = false;
+  document.getElementById("openPgpKeyList").collapsed = false;
+
+  // Update the global key with the recently generated key that was assigned to
+  // this identity from the Key generation wizard.
+  gKeyId = gIdentity.getUnicharAttribute("openpgp_key_id");
+
+  reloadOpenPgpUI();
+}
+
+/**
+ * Collapse the inline notification.
+ */
+function closeNotification() {
+  document.getElementById("openPgpNotification").collapsed = true;
+}
+
+/**
+ * Refresh the UI on init or after a successful OpenPGP Key generation.
+ */
+async function reloadOpenPgpUI() {
+  let result = {};
+  EnigmailKeyRing.getAllSecretKeysByEmail(gIdentity.email, result);
+
+  // Show the radiogroup only if the current identity has keys.
+  document.getElementById("openPgpKeyList").collapsed = !result.all.length;
+
+  // Interrupt and udpate the UI accordingly if no Key is associated with the
+  // current identity.
+  if (!result.all.length) {
+    // Hide the selection status.
+    document
+      .getElementById("openPgpgSelectionStatus")
+      .setAttribute("hidden", "hidden");
+
+    // Hide the learn more link.
+    document.getElementById("openPgpLearnMore").setAttribute("hidden", "true");
+
+    gKeyId = null;
+    highlightOpenPgpKey();
+    return;
+  }
+
+  document.l10n.setAttributes(
+    document.getElementById("openPgpgDescription"),
+    "openpgp-description",
+    {
+      count: result.all.length,
+      identity: gIdentity.email,
+    }
+  );
+
+  let status = document.getElementById("openPgpgSelectionStatus");
+  status.removeAttribute("hidden");
+
+  document.l10n.setAttributes(status, "openpgp-selection-status", {
+    count: gKeyId ? 1 : 0,
+    key: `0x${gKeyId}`,
+  });
+
+  document.getElementById("openPgpLearnMore").removeAttribute("hidden");
+
+  let radiogroup = document.getElementById("openPgpKeyListRadio");
+
+  // Remove all the previously generated radio options, except the first.
+  while (radiogroup.lastChild.id != "openPgpOptionNone") {
+    radiogroup.removeChild(radiogroup.lastChild);
+  }
+
+  // List all the available keys.
+  for (let key of result.all) {
+    let container = document.createXULElement("vbox");
+    container.id = `openPgpOption${key.keyId}`;
+    container.classList.add("content-blocking-category");
+
+    let box = document.createXULElement("hbox");
+
+    let radio = document.createXULElement("radio");
+    radio.setAttribute("flex", "1");
+    radio.id = `openPgp${key.keyId}`;
+    radio.value = key.keyId;
+    radio.label = `0x${key.keyId}`;
+
+    if (key.keyId == gIdentity.getUnicharAttribute("openpgp_key_id")) {
+      radio.setAttribute("selected", "true");
+    }
+
+    let toggle = document.createXULElement("button");
+    toggle.classList.add("arrowhead");
+    toggle.setAttribute("aria-expanded", "false");
+    document.l10n.setAttributes(toggle, "openpgp-key-expand-section");
+    toggle.addEventListener("command", toggleExpansion);
+
+    box.appendChild(radio);
+    box.appendChild(toggle);
+
+    let indent = document.createXULElement("vbox");
+    indent.classList.add("indent");
+
+    let dateContainer = document.createXULElement("hbox");
+    dateContainer.classList.add("expiration-date-container");
+    dateContainer.setAttribute("align", "center");
+
+    let dateIcon = document.createXULElement("image");
+    dateIcon.classList.add("expiration-date-icon");
+
+    let dateButton = document.createXULElement("button");
+    document.l10n.setAttributes(dateButton, "openpgp-key-man-change-expiry");
+    dateButton.addEventListener("command", enigmailEditKeyDate);
+    dateButton.setAttribute("hidden", "true");
+    dateButton.classList.add("expiration-date-button");
+
+    let today = new Date();
+    today.setMonth(today.getMonth() + 6);
+
+    // If the key expires in less than 6 months.
+    if (
+      key.expiryTime &&
+      Math.round(Date.parse(today) / 1000) > key.expiryTime
+    ) {
+      dateContainer.classList.add("key-is-expiring");
+      document.l10n.setAttributes(dateIcon, "openpgp-key-expires-image");
+      dateButton.removeAttribute("hidden");
+    }
+
+    let fluentExpireKey = "openpgp-radio-key-expires";
+    // If the key passed its expiration date.
+    if (key.expiryTime && Math.round(Date.now() / 1000) > key.expiryTime) {
+      dateContainer.classList.add("key-expired");
+      fluentExpireKey = "openpgp-radio-key-expired";
+      document.l10n.setAttributes(dateIcon, "openpgp-key-expired-image");
+      dateButton.removeAttribute("hidden");
+    }
+
+    let description = document.createXULElement("description");
+    if (key.expiryTime) {
+      document.l10n.setAttributes(description, fluentExpireKey, {
+        date: key.expiry,
+      });
+    } else {
+      document.l10n.setAttributes(description, "key-does-not-expire");
+    }
+
+    dateContainer.appendChild(dateIcon);
+    dateContainer.appendChild(description);
+    dateContainer.appendChild(dateButton);
+
+    let hiddenContainer = document.createXULElement("vbox");
+    hiddenContainer.classList.add(
+      "content-blocking-extra-information",
+      "indent"
+    );
+
+    // Start key info section.
+
+    // Key type.
+    let grid = document.createXULElement("hbox");
+    grid.classList.add("extra-information-label");
+
+    let typeImage = document.createXULElement("image");
+    typeImage.classList.add("content-blocking-openpgp-type");
+
+    let typeLabel = document.createXULElement("label");
+    document.l10n.setAttributes(
+      typeLabel,
+      "openpgp-key-details-key-type-label"
+    );
+    typeLabel.classList.add("extra-information-label-type");
+
+    let typeValueContainer = document.createXULElement("hbox");
+    typeValueContainer.classList.add("input-container");
+    typeValueContainer.setAttribute("flex", "1");
+
+    let typeValue = document.createElement("input");
+    typeValue.setAttribute("type", "text");
+    typeValue.classList.add("plain");
+    typeValue.setAttribute("readonly", "readonly");
+    typeValue.value = await document.l10n.formatValue("key-type-pair-2");
+
+    typeValueContainer.appendChild(typeValue);
+
+    grid.appendChild(typeImage);
+    grid.appendChild(typeLabel);
+    grid.appendChild(typeValueContainer);
+
+    // Key fingerprint.
+    let fingerprintImage = document.createXULElement("image");
+    fingerprintImage.classList.add("content-blocking-openpgp-fingerprint");
+
+    let fingerprintLabel = document.createXULElement("label");
+    document.l10n.setAttributes(
+      fingerprintLabel,
+      "openpgp-key-details-fingerprint-label"
+    );
+    fingerprintLabel.classList.add("extra-information-label-type");
+
+    let fgrInputContainer = document.createXULElement("hbox");
+    fgrInputContainer.classList.add("input-container");
+    fgrInputContainer.setAttribute("flex", "1");
+
+    let fingerprintInput = document.createElement("input");
+    fingerprintInput.setAttribute("type", "text");
+    fingerprintInput.classList.add("plain");
+    fingerprintInput.setAttribute("readonly", "readonly");
+    fingerprintInput.value = EnigmailKey.formatFpr(key.fpr);
+
+    fgrInputContainer.appendChild(fingerprintInput);
+
+    grid.appendChild(fingerprintImage);
+    grid.appendChild(fingerprintLabel);
+    grid.appendChild(fgrInputContainer);
+
+    // Key creation date.
+    let createdImage = document.createXULElement("image");
+    createdImage.classList.add("content-blocking-openpgp-created");
+
+    let createdLabel = document.createXULElement("label");
+    document.l10n.setAttributes(
+      createdLabel,
+      "openpgp-key-details-created-header"
+    );
+    createdLabel.classList.add("extra-information-label-type");
+
+    let createdValueContainer = document.createXULElement("hbox");
+    createdValueContainer.classList.add("input-container");
+    createdValueContainer.setAttribute("flex", "1");
+
+    let createdValue = document.createElement("input");
+    createdValue.setAttribute("type", "text");
+    createdValue.classList.add("plain");
+    createdValue.setAttribute("readonly", "readonly");
+    createdValue.value = key.created;
+
+    createdValueContainer.appendChild(createdValue);
+
+    grid.appendChild(createdImage);
+    grid.appendChild(createdLabel);
+    grid.appendChild(createdValueContainer);
+    // End key info section.
+
+    hiddenContainer.appendChild(grid);
+
+    // Action buttons.
+    let btnContainer = document.createXULElement("hbox");
+
+    let remove = document.createXULElement("button");
+    document.l10n.setAttributes(remove, "openpgp-key-man-del-key");
+    remove.addEventListener("command", () => {
+      enigmailDeleteKey(key);
+    });
+
+    let edit = document.createXULElement("button");
+    document.l10n.setAttributes(edit, "openpgp-key-man-edit-menu");
+    edit.addEventListener("command", enigmailEditKey);
+
+    let revoke = document.createXULElement("button");
+    document.l10n.setAttributes(revoke, "openpgp-key-man-revoke-key");
+    revoke.addEventListener("command", enigmailRevokeKey);
+
+    let info = document.createXULElement("button");
+    document.l10n.setAttributes(info, "openpgp-key-man-key-props");
+    info.addEventListener("command", () => {
+      enigmailKeyDetails(key.keyId);
+    });
+
+    let btnSeparator = document.createXULElement("separator");
+    btnSeparator.setAttribute("flex", "1");
+
+    btnContainer.appendChild(info);
+    btnContainer.appendChild(btnSeparator);
+    btnContainer.appendChild(edit);
+    btnContainer.appendChild(revoke);
+    btnContainer.appendChild(remove);
+
+    hiddenContainer.appendChild(btnContainer);
+
+    indent.appendChild(dateContainer);
+    indent.appendChild(hiddenContainer);
+
+    container.appendChild(box);
+    container.appendChild(indent);
+
+    radiogroup.appendChild(container);
+  }
+
+  highlightOpenPgpKey();
+}
+
+/**
+ * Open the Key Properties subdialog.
+ *
+ * @param {string} keyId - The ID of the selected OpenPGP Key.
+ */
+function enigmailKeyDetails(keyId) {
+  keyId = keyId.replace(/^0x/, "");
+
+  parent.gSubDialog.open(
+    "chrome://openpgp/content/ui/keyDetailsDlg.xhtml",
+    null,
+    { keyId }
+  );
+}
+
+/**
+ * Delete an OpenPGP Key.
+ *
+ * @param {Object} key - The selected OpenPGP Key.
+ */
+async function enigmailDeleteKey(key) {
+  if (!GetEnigmailSvc()) {
+    return;
+  }
+
+  // Interrupt if the selected key is currently being used.
+  if (key.keyId == gIdentity.getUnicharAttribute("openpgp_key_id")) {
+    let alertTitle = await document.l10n.formatValue("delete-key-in-use-title");
+    let alertDescription = await document.l10n.formatValue(
+      "delete-key-in-use-description"
+    );
+
+    Services.prompt.alert(null, alertTitle, alertDescription);
+    return;
+  }
+
+  let l10nKey = key.secretAvailable ? "delete-secret-key" : "delete-pub-key";
+  let title = await document.l10n.formatValue("delete-key-title", {
+    userId: key.userId,
+  });
+  let description = await document.l10n.formatValue(l10nKey, {
+    userId: key.userId,
+  });
+
+  // Ask for confirmation before proceeding.
+  if (!Services.prompt.confirm(null, title, description)) {
+    return;
+  }
+
+  let cApi = EnigmailCryptoAPI();
+  cApi.sync(cApi.deleteKey(key.fpr, key.secretAvailable));
+  cApi.sync(PgpSqliteDb2.deleteAcceptance(key.fpr));
+
+  EnigmailKeyRing.clearCache();
+  reloadOpenPgpUI();
+}
+
+/**
+ * Open the subdialog to enable the user to edit the the selected OpenPGP Key.
+ *
+ * @param {Event} event - The DOM event.
+ */
+async function enigmailEditKey(event) {
+  // TODO: Not yet implemented. Alert the user of the WIP status.
+  let title = await document.l10n.formatValue("openpgp-key-edit-title");
+
+  Services.prompt.alert(
+    window,
+    title,
+    "Work in Progress: Key editing not yet implemented"
+  );
+}
+
+/**
+ * Open the subdialog to enable the user to revoke the selected OpenPGP Key.
+ *
+ * @param {Event} event - The DOM event.
+ */
+async function enigmailRevokeKey(event) {
+  // TODO: Not yet implemented. Alert the user of the WIP status.
+  let title = await document.l10n.formatValue("openpgp-key-revoke-title");
+
+  Services.prompt.alert(
+    window,
+    title,
+    "Work in Progress: Key revocation not yet implemented"
+  );
+}
+
+/**
+ * Open the subdialog to enable the user to edit the expiration date of the
+ * selected OpenPGP Key.
+ *
+ * @param {Event} event - The DOM event.
+ */
+async function enigmailEditKeyDate(event) {
+  // TODO: Not yet implemented. Alert the user of the WIP status.
+  let title = await document.l10n.formatValue("openpgp-key-edit-date-title");
+
+  Services.prompt.alert(
+    window,
+    title,
+    "Work in Progress: Key date extention not yet implemented"
+  );
+}
+
+/**
+ * Toggle the visibility of the OpenPgp Key radio container.
+ *
+ * @param {Event} event - The DOM event.
+ */
+function toggleExpansion(event) {
+  let carat = event.target;
+  carat.classList.toggle("up");
+  carat.closest(".content-blocking-category").classList.toggle("expanded");
+  carat.setAttribute(
+    "aria-expanded",
+    carat.getAttribute("aria-expanded") === "false"
+  );
+}
+
+/**
+ * Update all the encryption options based on the newly selected OpenPGP Key.
+ */
+function updateOpenPgpSettings() {
+  // Get the newly selected OpenPgp Key for this identity.
+  let newKey = Services.prefs.getStringPref(
+    `mail.identity.${gIdentity.key}.openpgp_key_id`,
+    ""
+  );
+
+  // Avoid running the method if the key didn't change.
+  if (gKeyId == newKey) {
+    return;
+  }
+
+  gKeyId = newKey;
+
+  if (gKeyId) {
+    enableEncryptionControls(true);
+    enableSigningControls(true);
+  } else {
+    let stillHaveOtherEncryption =
+      gEncryptionCertName && gEncryptionCertName.value;
+    if (!stillHaveOtherEncryption) {
+      enableEncryptionControls(false);
+    }
+
+    let stillHaveOtherSigning = gSignCertName && gSignCertName.value;
+    if (!stillHaveOtherSigning) {
+      enableSigningControls(false);
+    }
+  }
+
+  updateTechPref();
+  enableSelectButtons();
+  onSave();
+
+  highlightOpenPgpKey();
+}
+
+/**
+ * Apply a .selected class to the radio container of the currently selected
+ * OpenPGP Key.
+ */
+function highlightOpenPgpKey() {
+  // Remove a previously selected container, if any.
+  let current = document.querySelector(".content-blocking-category.selected");
+
+  if (current) {
+    current.classList.remove("selected");
+  }
+
+  // Highlight the parent container of the currently selected radio button.
+  // The condition needs to be sure the key is not null as a selection of "None"
+  // results with a value of "".
+  if (gKeyId !== null) {
+    document
+      .querySelector(`radio[value="${gKeyId}"]`)
+      .closest(".content-blocking-category")
+      .classList.add("selected");
+  }
+
+  document.l10n.setAttributes(
+    document.getElementById("openPgpgSelectionStatus"),
+    "openpgp-selection-status",
+    {
+      count: gKeyId ? 1 : 0,
+      key: `0x${gKeyId}`,
+    }
+  );
+
+  // Show a green checkmark if a key is currently being used.
+  // TODO: This needs further iterations in order to show a proper flag in case
+  // the selected Key is expired or revoked.
+  let image = document.getElementById("openPgpStatusImage");
+  if (gKeyId) {
+    image.removeAttribute("hidden");
+  } else {
+    image.setAttribute("hidden", "true");
+  }
+}
--- a/mail/extensions/am-e2e/am-e2e.xhtml
+++ b/mail/extensions/am-e2e/am-e2e.xhtml
@@ -1,24 +1,27 @@
 <?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"?>
+<?xml-stylesheet href="chrome://messenger/skin/openpgp/inlineNotification.css" type="text/css"?>
 
 <!DOCTYPE window SYSTEM "chrome://messenger/locale/am-smime.dtd">
 
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
-        class="color-dialog"
+<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns:html="http://www.w3.org/1999/xhtml"
         onload="parent.onPanelLoaded('am-e2e.xhtml');">
 
   <script src="chrome://global/content/globalOverlay.js"/>
   <script src="chrome://global/content/editMenuOverlay.js"/>
+  <script src="chrome://global/content/preferencesBindings.js"/>
   <script src="chrome://messenger/content/AccountManager.js"/>
+  <script src="chrome://openpgp/content/ui/enigmailCommon.js"/>
   <script src="chrome://messenger/content/am-e2e.js"/>
 
   <vbox flex="1" style="overflow: auto;"><vbox id="containerBox" flex="1">
     <hbox class="dialogheader">
       <label class="dialogheader-title" value="&e2eTitle.label;"/>
     </hbox>
 
     <separator class="thin"/>
--- a/mail/extensions/openpgp/content/modules/keyRing.jsm
+++ b/mail/extensions/openpgp/content/modules/keyRing.jsm
@@ -229,31 +229,50 @@ var EnigmailKeyRing = {
    *
    * @param emailAddr: String - email address to search for without any angulars
    *                            or names
    *
    * @return KeyObject with the found key, or null if no key found
    */
   getSecretKeyByEmail(emailAddr) {
     let result = {};
-    this.getAllSecretKeysByEmail(emailAddr, result);
+    this.getSecretKeysByEmail(emailAddr, result);
     return result.best;
   },
 
-  getAllSecretKeysByEmail(emailAddr, result) {
+  getSecretKeysByEmail(emailAddr, result) {
     // sanitize email address
     emailAddr = emailAddr.replace(/([\.\[\]\-\\])/g, "\\$1");
 
     let searchTerm =
       "(<" + emailAddr + ">| " + emailAddr + "$|^" + emailAddr + "$)";
 
     this.getAllSecretKeysByUserId(searchTerm, result);
   },
 
   /**
+   * Return the full unfiltered list of keys that are specifics of email
+   * addresses in UIDs.
+   *
+   * @param {String} emailAddr - email address to search for without any
+   *   angulars or names.
+   *
+   * @return {Object | null} - Object with the found keys, or null.
+   */
+  getAllSecretKeysByEmail(emailAddr, result) {
+    // Sanitize email address.
+    emailAddr = emailAddr.replace(/([\.\[\]\-\\])/g, "\\$1");
+
+    let searchTerm =
+      "(<" + emailAddr + ">| " + emailAddr + "$|^" + emailAddr + "$)";
+
+    result.all = this.getKeysByUserId(searchTerm, false);
+  },
+
+  /**
    * get the "best" possible secret key for a given user ID
    *
    * @param searchTerm   - String: a regular expression to match against all UIDs of the keys.
    *                               The search is always performed case-insensitively
    * @return KeyObject with the found key, or null if no key found
    */
   getSecretKeyByUserId(searchTerm) {
     let result = {};
--- a/mail/extensions/openpgp/content/strings/enigmail.ftl
+++ b/mail/extensions/openpgp/content/strings/enigmail.ftl
@@ -1,8 +1,12 @@
+# 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/.
+
 openpgp-key-user-id-label = Account / User ID
 openpgp-keygen-title-label =
     .title = Generate OpenPGP Key
 openpgp-cancel-key =
     .label = Cancel
     .tooltiptext = Cancel Key Generation
 openpgp-key-gen-expiry-title =
     .label = Key expiry
@@ -182,16 +186,69 @@ openpgp-acceptance-undecided-label =
 openpgp-acceptance-unverified-label =
     .label = Yes, but I have not verified that it is the correct key.
 openpgp-acceptance-verified-label =
     .label = Yes, I've verified in person this key has the correct fingerprint.
 
 openpgp-copy-cmd-label =
     .label = Copy
 
+## e2e encryption settings
+
+#   $count (Number) - the number of configured keys associated with the current identity
+#   $identity (String) - the email address of the currently selected identity
+openpgp-description = { $count ->
+    [0]     Thunderbird doesn't have a personal OpenPGP key for <b>{ $identity }</b>
+    [one]   Thunderbird found { $count } personal OpenPGP key associated with <b>{ $identity }</b>
+   *[other] Thunderbird found { $count } personal OpenPGP keys associated with <b>{ $identity }</b>
+}
+
+#   $count (Number) - the number of configured keys associated with the current identity
+#   $key (String) - the currently selected OpenPGP key
+openpgp-selection-status = { $count ->
+    [0]     Select a valid key to enable the OpenPGP protocol.
+   *[other] Your current configuration uses key ID <b>{ $key }</b>
+}
+
+openpgp-add-key-button =
+    .label = Add Key…
+    .accesskey = A
+
+e2e-learn-more = Learn more
+
+openpgp-keygen-success = OpenPGP Key created successfully!
+
+## OpenPGP Key selection area
+
+openpgp-radio-none =
+    .label = None
+
+openpgp-radio-none-desc = Do not use OpenPGP for this identity.
+
+#   $key (String) - the expiration date of the OpenPGP key
+openpgp-radio-key-expires = Expires on: { $date }
+
+openpgp-key-expires-image =
+    .tooltiptext = Key is expiring in less than 6 months
+
+#   $key (String) - the expiration date of the OpenPGP key
+openpgp-radio-key-expired = Expired on: { $date }
+
+openpgp-key-expired-image =
+    .tooltiptext = Key expired
+
+openpgp-key-expand-section =
+  .tooltiptext = More information
+
+openpgp-key-revoke-title = Revoke Key
+
+openpgp-key-edit-title = Change OpenPGP Key
+
+openpgp-key-edit-date-title = Extend Expiration Date
+
 # Strings in keyDetailsDlg.xhtml
 key-type-public = public key
 key-type-primary = primary key
 key-type-subkey = subkey
 key-type-pair-2 = personal key (secret key and public key)
 key-expiry-never = never
 key-usage-encrypt = Encrypt
 key-usage-sign = Sign
@@ -312,16 +369,22 @@ user-att-photo = User attribute (JPEG im
 revoke-key-question = You are about to revoke the key '{ $userId }'.\n\nYou will no longer be able to sign with this key, and once distributed, others will no longer be able to encrypt with that key. You can still use the key to decrypt old messages.\n\nDo you want to proceed?
 revoke-key-not-present = You have no key (0x{ $keyId }) which matches this revocation certificate!\n\nIf you have lost your key, you must import it (e.g. from a keyserver) before importing the revocation certificate!
 revoke-key-already-revoked = The key 0x{ $keyId } has already been revoked.
 key-man-button-revoke-key = &Revoke Key
 
 # Strings in keyRing.jsm & decryption.jsm
 key-man-button-import = &Import
 
+delete-key-title = Delete OpenPGP Key
+
+delete-key-in-use-title = OpenPGP Key currently in use
+
+delete-key-in-use-description = Unable to proceed! The Key you selected for deletion is currently being used by this identity.
+
 # Strings used in errorHandling.jsm
 key-error-key-spec-not-found = The email address '{ $keySpec }' cannot be matched to a key on your keyring.
 key-error-key-id-not-found = The configured key ID '{ $keySpec }' cannot be found on your keyring.
 
 # Strings used in enigmailKeyManager.js & windows.jsm
 need-online = The function you have selected is not available in offline mode. Please go online and try again.
 
 # Strings used in keyRing.jsm & keyLookupHelper.jsm
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/strings/key-wizard.ftl
@@ -0,0 +1,110 @@
+# 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/.
+
+#   $identity (String) - the email address of the currently selected identity
+key-wizard-dialog-window =
+    .title = Add a Personal OpenPGPG Key for { $identity }
+
+key-wizard-button =
+    .buttonlabelaccept = Continue
+    .buttonlabelhelp = Go back
+
+key-wizard-warning = <b>If you have an existing personal key</b> for this email address, you should import it. Otherwise you will not have access to your archives of encrypted emails, nor be able to read incoming encrypted emails from people who are still using your existing key.
+
+key-wizard-learn-more = Learn more
+
+radio-create-key =
+    .label = Create a new OpenPGP Key
+    .accesskey = C
+
+radio-import-key =
+    .label = Import an existing OpenPGP Key
+    .accesskey = I
+
+radio-gnupg-key =
+    .label = Use your external key through GnuPG (e.g. from a smartcard)
+    .accesskey = U
+
+## Generate key section
+
+openpgp-generate-key-title = Generate OpenPGP Key
+
+openpgp-generate-key-info = <b>Key generation may take up to several minutes to complete.</b> Do not exit the application while key generation is in progress. Actively browsing or performing disk-intensive operations during key generation will replenish the 'randomness pool' and speed-up the process. You will be alerted when key generation is completed.
+
+openpgp-keygen-expiry-title = Key expiry
+
+openpgp-keygen-expiry-description = Define the expiration time of your newly generated key. You can later control the date to extend it if necessary.
+
+radio-keygen-expiry =
+    .label = Key expires in
+    .accesskey = e
+
+radio-keygen-no-expiry =
+    .label = Key does not expire
+    .accesskey = d
+
+openpgp-keygen-days-label =
+    .label = days
+openpgp-keygen-months-label =
+    .label = months
+openpgp-keygen-years-label =
+    .label = years
+
+openpgp-keygen-advanced-title = Advanced settings
+
+openpgp-keygen-advanced-description = Control the advanced settings of your OpenPGP Key.
+
+openpgp-keygen-keytype =
+    .value = Key type:
+    .accesskey = t
+
+openpgp-keygen-keysize =
+    .value = Key size:
+    .accesskey = s
+
+openpgp-keygen-type-rsa =
+    .label = RSA
+
+openpgp-keygen-type-ecc =
+    .label = ECC (Elliptic Curve)
+
+openpgp-keygen-button = Generate key
+
+openpgp-keygen-progress-title = Generating your new OpenPGP Key…
+
+openpgp-keygen-confirm =
+    .label = Confirm
+
+openpgp-keygen-dismiss =
+    .label = Cancel
+
+openpgp-keygen-cancel =
+    .label = Cancel process…
+
+openpgp-keygen-missing-username = There is no name specified for the current account. Please enter a value in the field  "Your name" in the account settings.
+openpgp-keygen-long-expiry = You cannot create a key that expires in more than 100 years.
+openpgp-keygen-short-expiry = Your key must be valid for at least one day.
+
+openpgp-keygen-ongoing = Key generation already in progress!
+
+openpgp-keygen-error-core = Unable to initialize OpenPGP Core Service
+
+openpgp-keygen-error-failed = OpenPGP Key generation unexpectedly failed
+
+#   $identity (String) - the newly generate OpenPGP Key
+openpgp-keygen-error-revocation = OpenPGP Key created successfully, but failed to obtain revocation for key { $key }
+
+openpgp-keygen-abort-title = Abort key generation?
+openpgp-keygen-abort = OpenPGP Key generation currently in progress, are you sure you want to cancel it?
+
+#   $identity (String) - the name and email address of the currently selected identity
+openpgp-key-confirm = Generate public and secret key for { $identity }?
+
+## Import Key section
+
+openpgp-import-key-title = Import existing OpenPGP Key
+
+## External Key section
+
+openpgp-external-key-title = External GnuPG Key
--- a/mail/extensions/openpgp/content/ui/enigmailKeygen.js
+++ b/mail/extensions/openpgp/content/ui/enigmailKeygen.js
@@ -47,17 +47,17 @@ var gGeneratedKey = null;
 var gUsedId;
 
 const KEYGEN_CANCELLED = "cancelled";
 const DEFAULT_FILE_PERMS = 0o600;
 
 let revocationFilePrefix1 =
   "This is a revocation certificate for the OpenPGP key:";
 let revocationFilePrefix2 = `
-A revocation certificate is a kind of "kill switch" to publicly
+A revocation certificate is kind of a "kill switch" to publicly
 declare that a key shall no longer be used.  It is not possible
 to retract such a revocation certificate once it has been published.
 
 Use it to revoke this key in case of a secret key compromise, or loss of
 the secret key, or loss of passphrase of the secret key.
 
 To avoid an accidental use of this file, a colon has been inserted
 before the 5 dashes below.  Remove this colon with a text editor
@@ -254,17 +254,17 @@ function enigmailKeygenStart() {
 
   var progMeter = document.getElementById("keygenProgress");
   progMeter.setAttribute("value", 100);
 
   if (!gGeneratedKey || gGeneratedKey == KEYGEN_CANCELLED) {
     throw new Error("key generation failed");
   } else {
     console.debug("saving new key id " + gGeneratedKey);
-    curId.setCharAttribute("openpgp_key_id", gGeneratedKey);
+    curId.setUnicharAttribute("openpgp_key_id", gGeneratedKey);
     EnigSavePrefs();
   }
 
   closeAndReset();
 
   let rev = cApi.sync(cApi.getNewRevocation("0x" + gGeneratedKey));
   if (!rev) {
     throw new Error("failed to obtain revocation for key " + gGeneratedKey);
--- a/mail/extensions/openpgp/content/ui/keyDetailsDlg.js
+++ b/mail/extensions/openpgp/content/ui/keyDetailsDlg.js
@@ -34,17 +34,19 @@ var gTreeFuncs = null;
 var gAllEmails = [];
 var gFingerprint = "";
 
 var gAcceptanceRadio = null;
 var gOriginalAcceptance;
 var gUpdateAllowed = false;
 
 async function onLoad() {
-  window.arguments[1].refresh = false;
+  if (window.arguments[1]) {
+    window.arguments[1].refresh = false;
+  }
 
   gAcceptanceRadio = document.getElementById("acceptanceRadio");
 
   gKeyId = window.arguments[0].keyId;
 
   let accept = document
     .getElementById("enigmailKeyDetailsDlg")
     .getButton("accept");
--- a/mail/extensions/openpgp/content/ui/keyDetailsDlg.xhtml
+++ b/mail/extensions/openpgp/content/ui/keyDetailsDlg.xhtml
@@ -2,26 +2,27 @@
 <!--
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 -->
 
 <?xml-stylesheet href="chrome://messenger/skin/messenger.css" type="text/css"?>
 <?xml-stylesheet href="chrome://messenger/skin/openpgp/enigmail.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/accountManage.css" type="text/css"?>
 
 <!DOCTYPE window [
 <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
 %brandDTD;
 ]>
 
 <window data-l10n-id="openpgp-key-details-title"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml"
-        style="min-width:60em; min-height:30em"
+        style="width:60em;"
         persist="width height"
         onload="onLoad();">
 <dialog id="enigmailKeyDetailsDlg"
         buttons="accept"
         data-l10n-id="openpgp-card-details-close-window-label">
 
   <script type="application/x-javascript" src="chrome://openpgp/content/ui/enigmailCommon.js"/>
   <script type="application/x-javascript" src="chrome://openpgp/content/ui/keyDetailsDlg.js"/>
@@ -35,56 +36,58 @@
   <broadcasterset>
     <broadcaster id="ownKeyCommands" hidden="true"/>
   </broadcasterset>
 
   <vbox>
     <html:table>
       <html:tr>
         <html:th data-l10n-id="openpgp-key-details-user-id2-label"/>
-        <html:td>
-          <html:input id="userId" class="plain" style="white-space: pre;"
+        <html:td class="input-container">
+          <html:input id="userId" type="text" class="plain"
                       readonly="readonly" value="?" size="60"/>
         </html:td>
       </html:tr>
       <html:tr>
         <html:th data-l10n-id="openpgp-key-details-key-type-label"/>
-        <html:td>
-          <html:input id="keyType" class="plain" style="white-space: pre;"
+        <html:td class="input-container">
+          <html:input id="keyType" type="text" class="plain"
                       readonly="readonly" value="?" size="60"/>
         </html:td>
       </html:tr>
       <html:tr>
         <html:th data-l10n-id="openpgp-key-details-fingerprint-label"/>
-        <html:td>
-          <html:input id="fingerprint" class="plain" style="white-space: pre;"
+        <html:td class="input-container">
+          <html:input id="fingerprint" type="text" class="plain"
                       readonly="readonly" value="?" size="60"/>
         </html:td>
       </html:tr>
       <html:tr>
         <html:th data-l10n-id="openpgp-key-details-created-header"/>
-        <html:td>
-          <html:input id="keyCreated" class="plain" style="white-space: pre;"
+        <html:td class="input-container">
+          <html:input id="keyCreated" type="text" class="plain"
                       readonly="readonly" value="?" size="60"/>
         </html:td>
       </html:tr>
       <html:tr>
         <html:th data-l10n-id="openpgp-key-details-expiry-header"/>
-        <html:td>
-          <html:input id="keyExpiry" class="plain" style="white-space: pre;"
+        <html:td class="input-container">
+          <html:input id="keyExpiry" type="text" class="plain"
                       readonly="readonly" value="?" size="60"/>
         </html:td>
       </html:tr>
     </html:table>
     <vbox class="enigmailCaptionbox" id="alsoknown" flex="1">
       <html:h1 data-l10n-id="openpgp-key-details-also-known-label"/>
       <richlistbox id="additionalUid" style="height: 4em;" flex="1"/>
     </vbox>
   </vbox>
 
+  <separator/>
+
   <tabbox flex="1" style="margin:5px" id="mainTabs">
     <tabs id="mainTabBox">
       <tab id="acceptanceTab" data-l10n-id="openpgp-acceptance-label"/>
       <tab id="signaturesTab" data-l10n-id="openpgp-key-details-signatures-tab"/>
       <tab id="structureTab" data-l10n-id="openpgp-key-details-structure-tab"/>
     </tabs>
 
     <tabpanels flex="1" id="mainTabPanel">
@@ -112,17 +115,16 @@
               hidecolumnpicker="true"
               ondblclick="sigHandleDblClick(event)">
 
           <treecols>
             <treecol id="sig_uid_col" flex="1"
                      data-l10n-id="openpgp-key-details-uid-certified-col"
                      primary="true"/>
             <splitter class="tree-splitter"/>
-            <splitter class="tree-splitter"/>
             <treecol id="sig_keyid_col"
                      data-l10n-id="openpgp-key-id-label"
                      persist="width"/>
             <treecol id="sig_created_col"
                      data-l10n-id="openpgp-key-details-created-label"
                      persist="width"/>
           </treecols>
 
@@ -134,17 +136,17 @@
       <vbox id ="structurePanel">
         <hbox flex="1">
           <tree id="subkeyList" flex="1"
                 enableColumnDrag="true"
                 style="height:100px"
                 hidecolumnpicker="false">
 
             <treecols>
-              <treecol id="keyTypeCol" primary="true"
+              <treecol id="keyTypeCol"
                        data-l10n-id="openpgp-key-details-key-part-label"
                        style="width:71px"
                        persist="width"/>
               <splitter class="tree-splitter"/>
               <treecol id="keyUsageCol"
                        data-l10n-id="openpgp-key-details-usage-label"
                        flex="1"/>
               <splitter class="tree-splitter"/>
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/ui/keyWizard.js
@@ -0,0 +1,569 @@
+/* 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/. */
+
+// Uses: chrome://openpgp/content/ui/enigmailCommon.js
+
+"use strict";
+
+// Modules
+/* global EnigmailApp: false, EnigmailKeyRing: false, GetEnigmailSvc: false,
+   EnigInitCommon: false, EnigSavePrefs: false, EnigFilePicker: false,
+   EnigGetFilePath: false, EnigmailWindows: false */
+
+// Initialize enigmailCommon.
+EnigInitCommon("enigmailKeygen");
+
+var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { AppConstants } = ChromeUtils.import(
+  "resource://gre/modules/AppConstants.jsm"
+);
+var EnigmailCryptoAPI = ChromeUtils.import(
+  "chrome://openpgp/content/modules/cryptoAPI.jsm"
+).EnigmailCryptoAPI;
+var { EnigmailFiles } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/files.jsm"
+);
+var OpenPGPMasterpass = ChromeUtils.import(
+  "chrome://openpgp/content/modules/masterpass.jsm"
+).OpenPGPMasterpass;
+var { RNP } = ChromeUtils.import("chrome://openpgp/content/modules/RNP.jsm");
+
+// UI variables.
+var gIdentity;
+var gSubDialog;
+var kStartSection;
+var kDialog;
+var kCurrentSection = "start";
+var kGenerating = false;
+var kButtonLabel;
+
+// OpenPGP variables.
+var gKeygenRequest;
+var gAllData = "";
+var gGeneratedKey = null;
+
+const DEFAULT_FILE_PERMS = 0o600;
+
+// The revocation strings are not localization since the revocation certificate
+// will be published to others who may not know the native language of the user.
+const revocationFilePrefix1 =
+  "This is a revocation certificate for the OpenPGP key:";
+const revocationFilePrefix2 = `
+A revocation certificate is kind of a "kill switch" to publicly
+declare that a key shall no longer be used.  It is not possible
+to retract such a revocation certificate once it has been published.
+
+Use it to revoke this key in case of a secret key compromise, or loss of
+the secret key, or loss of passphrase of the secret key.
+
+To avoid an accidental use of this file, a colon has been inserted
+before the 5 dashes below.  Remove this colon with a text editor
+before importing and publishing this revocation certificate.
+
+:`;
+
+// Dialog event listeners.
+document.addEventListener("dialogaccept", wizardContinue);
+document.addEventListener("dialoghelp", goBack);
+document.addEventListener("dialogcancel", onClose);
+
+/**
+ * Initialize the keyWizard dialog.
+ */
+async function init() {
+  gIdentity = window.arguments[0].identity;
+  gSubDialog = window.arguments[0].gSubDialog;
+
+  kStartSection = document.getElementById("wizardStart");
+  kDialog = document.querySelector("dialog");
+
+  document.l10n.setAttributes(
+    document.documentElement,
+    "key-wizard-dialog-window",
+    {
+      identity: gIdentity.email,
+    }
+  );
+
+  // Show the GnuPG radio selection if the pref is enabled.
+  if (Services.prefs.getBoolPref("mail.openpgp.allow_external_gnupg")) {
+    document.getElementById("externalOpenPgp").removeAttribute("hidden");
+  }
+
+  // After the dialog is visible, disable the event listeners causing it to
+  // close when clicking on the overlay or hitting the Esc key, and remove the
+  // close button from the header. This is necessary to control the escape
+  // point and prevent the accidental dismiss of the dialog during important
+  // processes, like the generation or importing of a key.
+  setTimeout(() => {
+    gSubDialog._topDialog._removeDialogEventListeners();
+    gSubDialog._topDialog._closeButton.remove();
+  }, 150);
+}
+
+/**
+ * Intercept the dialogaccept command to implement a wizard like setup workflow.
+ *
+ * @param {Event} event - The DOM Event.
+ */
+function wizardContinue(event) {
+  event.preventDefault();
+
+  // Pretty impossible scenario but just in case if no radio button is
+  // currently selected, bail out.
+  if (!document.getElementById("openPgpKeyChoices").value) {
+    return;
+  }
+
+  // Trigger an action based on the currently visible section.
+  if (kCurrentSection != "start") {
+    wizardNextStep();
+    return;
+  }
+
+  // Disable the `Continue` button.
+  kDialog.getButton("accept").setAttribute("disabled", true);
+
+  kStartSection.addEventListener("transitionend", switchSection);
+  kStartSection.classList.add("hide");
+}
+
+/**
+ * Separated method dealing with the section switching to allow the removal of
+ * the event listener to prevent stacking.
+ */
+function switchSection() {
+  kStartSection.setAttribute("hidden", true);
+  kStartSection.removeEventListener("transitionend", switchSection);
+
+  // Save the current label of the accept button in order to restore it later.
+  kButtonLabel = kDialog.getButton("accept").label;
+
+  // Update the UI based on the radiogroup selection.
+  switch (document.getElementById("openPgpKeyChoices").value) {
+    case "0":
+      wizardCreateKey();
+      break;
+
+    case "1":
+      wizardImportKey();
+      break;
+
+    case "2":
+      wizardExternalKey();
+      break;
+  }
+
+  // Show the `Go Back` button.
+  kDialog.getButton("help").removeAttribute("hidden");
+}
+
+/**
+ * Handle the next step of the wizard based on the currently visible section.
+ */
+async function wizardNextStep() {
+  switch (kCurrentSection) {
+    case "create":
+      await openPgpKeygenStart();
+      break;
+
+    case "import":
+      break;
+
+    case "external":
+      break;
+  }
+}
+
+/**
+ * Go back to the initial view of the wizard.
+ */
+function goBack() {
+  let section = document.querySelector(".wizard-section:not([hidden])");
+  section.addEventListener("transitionend", backToStart);
+  section.classList.add("hide-reverse");
+}
+
+/**
+ * Hide the currently visible section at the end of the animation, remove the
+ * listener to prevent stacking, and trigger the reveal of the first section.
+ *
+ * @param {Event} event - The DOM Event.
+ */
+function backToStart(event) {
+  // Hide the `Go Back` button.
+  kDialog.getButton("help").setAttribute("hidden", true);
+  // Enable the `Continue` button.
+  kDialog.getButton("accept").removeAttribute("disabled");
+  kDialog.getButton("accept").label = kButtonLabel;
+
+  event.target.setAttribute("hidden", true);
+  event.target.removeEventListener("transitionend", backToStart);
+
+  // Reset section key.
+  kCurrentSection = "start";
+
+  revealSection("wizardStart");
+}
+
+/**
+ * Show the Key Creation section.
+ */
+async function wizardCreateKey() {
+  kCurrentSection = "create";
+  revealSection("wizardCreateKey");
+
+  let createLabel = await document.l10n.formatValue("openpgp-keygen-button");
+
+  kDialog.getButton("accept").label = createLabel;
+
+  if (!gIdentity.fullName) {
+    document.getElementById("openPgpWarning").collapsed = false;
+    document.l10n.setAttributes(
+      document.getElementById("openPgpWarningDescription"),
+      "openpgp-keygen-long-expiry"
+    );
+    return;
+  }
+
+  kDialog.getButton("accept").removeAttribute("disabled");
+}
+
+/**
+ * Show the Key Import section.
+ */
+function wizardImportKey() {
+  kCurrentSection = "import";
+  revealSection("wizardImportKey");
+}
+
+/**
+ * Show the Key Setup via external smartcard section.
+ */
+function wizardExternalKey() {
+  kCurrentSection = "external";
+  revealSection("wizardExternalKey");
+}
+
+/**
+ * Animate the reveal of a section of the wizard.
+ *
+ * @param {string} id - The id of the section to reveal.
+ */
+function revealSection(id) {
+  let section = document.getElementById(id);
+  section.removeAttribute("hidden");
+
+  // Timeout to animate after the hidden attribute has been removed.
+  setTimeout(() => {
+    section.classList.remove("hide", "hide-reverse");
+  });
+
+  resizeDialog();
+}
+
+/**
+ * Enable or disable the elements based on the radiogroup selection.
+ *
+ * @param {Event} event - The DOM event triggered on change.
+ */
+function onExpirationChange(event) {
+  document
+    .getElementById("expireInput")
+    .toggleAttribute("disabled", event.target.value != 0);
+  document.getElementById("timeScale").disabled = event.target.value != 0;
+
+  validateExpiration();
+}
+
+/**
+ * Enable or disable the #keySize input field based on the current selection of
+ * the #keyType radio group.
+ *
+ * @param {Event} event - The DOM Event.
+ */
+function onKeyTypeChange(event) {
+  document.getElementById("keySize").disabled = event.target.value == "ECC";
+}
+
+/**
+ * Intercept the cancel event to prevent accidental closing if the generation of
+ * a key is currently in progress.
+ *
+ * @param {Event} event - The DOM event.
+ */
+function onClose(event) {
+  if (kGenerating) {
+    event.preventDefault();
+  }
+}
+
+/**
+ * Validate the expiration time of a newly generated key when the user changes
+ * values. Disable the "Generate Key" button and show an alert if the selected
+ * value is less than 1 day or more than 100 years.
+ */
+async function validateExpiration() {
+  // If the key doesn't have an expiration date, hide the warning message and
+  // enable the "Generate Key" button.
+  if (document.getElementById("openPgpKeygeExpiry").value == 1) {
+    document.getElementById("openPgpWarning").collapsed = true;
+    kDialog.getButton("accept").removeAttribute("disabled");
+    return;
+  }
+
+  // Calculate the selected expiration date.
+  let expiryTime =
+    Number(document.getElementById("expireInput").value) *
+    Number(document.getElementById("timeScale").value);
+
+  // If the expiration date exceeds 100 years.
+  if (expiryTime > 36500) {
+    document.getElementById("openPgpWarning").collapsed = false;
+    document.l10n.setAttributes(
+      document.getElementById("openPgpWarningDescription"),
+      "openpgp-keygen-long-expiry"
+    );
+    kDialog.getButton("accept").setAttribute("disabled", true);
+    resizeDialog();
+    return;
+  }
+
+  // If the expiration date is shorter than 1 day.
+  if (expiryTime <= 0) {
+    document.getElementById("openPgpWarning").collapsed = false;
+    document.l10n.setAttributes(
+      document.getElementById("openPgpWarningDescription"),
+      "openpgp-keygen-short-expiry"
+    );
+    kDialog.getButton("accept").setAttribute("disabled", true);
+    resizeDialog();
+    return;
+  }
+
+  // If the previous conditions are false, hide the warning message and
+  // enable the "Generate Key" button since the expiration date is valid.
+  document.getElementById("openPgpWarning").collapsed = true;
+  kDialog.getButton("accept").removeAttribute("disabled");
+}
+
+/**
+ * Resize the dialog to account for the newly visible sections. The timeout is
+ * neccessary in order to wait until the end of revealing animations.
+ */
+function resizeDialog() {
+  // Timeout to trigger the dialog resize after the reveal animation completed.
+  setTimeout(() => {
+    gSubDialog._topDialog.resizeVertically();
+  }, 230);
+}
+
+/**
+ * Start the generation of a new OpenPGP Key.
+ */
+async function openPgpKeygenStart() {
+  let openPgpWarning = document.getElementById("openPgpWarning");
+  let openPgpWarningText = document.getElementById("openPgpWarningDescription");
+  openPgpWarning.collapsed = true;
+
+  // If a key generation request is already pending, warn the user and
+  // don't proceed.
+  if (gKeygenRequest) {
+    let req = gKeygenRequest.QueryInterface(Ci.nsIRequest);
+
+    if (req.isPending()) {
+      openPgpWarning.collapsed = false;
+      document.l10n.setAttributes(openPgpWarningText, "openpgp-keygen-ongoing");
+      return;
+    }
+  }
+
+  // Reset global variables to be sure.
+  gGeneratedKey = null;
+  gAllData = "";
+
+  let enigmailSvc = GetEnigmailSvc();
+  if (!enigmailSvc) {
+    openPgpWarning.collapsed = false;
+    document.l10n.setAttributes(
+      openPgpWarningText,
+      "openpgp-keygen-error-core"
+    );
+    closeOverlay();
+
+    throw new Error("GetEnigmailSvc failed");
+  }
+
+  // Show wizard overlay before the start of the generation process. This is
+  // necessary because the generation happens synchronously and blocks the UI.
+  // We need to show the overlay before it, otherwise it would flash and freeze.
+  // This should be moved after the Services.prompt.confirmEx() method
+  // once Bug 1617444 is implemented.
+  let overlay = document.getElementById("wizardOverlay");
+  overlay.removeAttribute("hidden");
+  overlay.classList.remove("hide");
+
+  // Ask for confirmation before triggering the generation of a new key.
+  document.l10n.setAttributes(
+    document.getElementById("wizardOverlayQuestion"),
+    "openpgp-key-confirm",
+    {
+      identity: `${gIdentity.fullName} <b>"${gIdentity.email}"</b>`,
+    }
+  );
+
+  document.l10n.setAttributes(
+    document.getElementById("wizardOverlayTitle"),
+    "openpgp-keygen-progress-title"
+  );
+}
+
+async function openPgpKeygenConfirm() {
+  document.getElementById("openPgpKeygenConfirm").collapsed = true;
+  document.getElementById("openPgpKeygenProcess").removeAttribute("collapsed");
+
+  let openPgpWarning = document.getElementById("openPgpWarning");
+  let openPgpWarningText = document.getElementById("openPgpWarningDescription");
+  openPgpWarning.collapsed = true;
+
+  kGenerating = true;
+
+  let cApi;
+  try {
+    let newId = null;
+    cApi = EnigmailCryptoAPI();
+    newId = cApi.sync(
+      cApi.genKey(
+        `${gIdentity.fullName} <${gIdentity.email}>`,
+        document.getElementById("keyType").value,
+        Number(document.getElementById("keySize").value),
+        document.getElementById("openPgpKeygeExpiry").value == 1
+          ? 0
+          : Number(document.getElementById("expireInput").value) *
+              Number(document.getElementById("timeScale").value),
+        OpenPGPMasterpass.retrieveOpenPGPPassword()
+      )
+    );
+    console.log("created new key with id: " + newId);
+    gGeneratedKey = newId;
+  } catch (ex) {
+    console.log(ex);
+  }
+
+  EnigmailWindows.keyManReloadKeys();
+
+  gKeygenRequest = null;
+  kGenerating = true;
+
+  // For wathever reason, the key wasn't generated. Show an error message and
+  // hide the processing overlay.
+  if (!gGeneratedKey) {
+    openPgpWarning.collapsed = false;
+    document.l10n.setAttributes(
+      openPgpWarningText,
+      "openpgp-keygen-error-failed"
+    );
+    closeOverlay();
+
+    throw new Error("key generation failed");
+  }
+
+  console.debug("saving new key id " + gGeneratedKey);
+  EnigSavePrefs();
+
+  // Hide wizard overlay at the end of the generation process.
+  closeOverlay();
+  EnigmailKeyRing.clearCache();
+
+  let rev = cApi.sync(cApi.getNewRevocation(`0x${gGeneratedKey}`));
+  if (!rev) {
+    openPgpWarning.collapsed = false;
+    document.l10n.setAttributes(
+      openPgpWarningText,
+      "openpgp-keygen-error-revocation",
+      {
+        key: gGeneratedKey,
+      }
+    );
+    closeOverlay();
+
+    throw new Error("failed to obtain revocation for key " + gGeneratedKey);
+  }
+
+  let revFull =
+    revocationFilePrefix1 +
+    "\n\n" +
+    gGeneratedKey +
+    "\n" +
+    revocationFilePrefix2 +
+    rev;
+
+  let revFile = EnigmailApp.getProfileDirectory();
+  revFile.append(`0x${gGeneratedKey}_rev.asc`);
+
+  // Create a revokation cert in the Thunderbird profile directoy.
+  EnigmailFiles.writeFileContents(revFile, revFull, DEFAULT_FILE_PERMS);
+
+  // Key succesfully created. Assign the new key to the current identity, close
+  // the dialog and show a confirmation message.
+  gIdentity.setUnicharAttribute("openpgp_key_id", gGeneratedKey);
+  window.arguments[0].okCallback();
+  window.close();
+}
+
+/**
+ * Cancel the keygen process, ask for confirmation before proceeding.
+ */
+async function openPgpKeygenCancel() {
+  let abortTitle = await document.l10n.formatValue(
+    "openpgp-keygen-abort-title"
+  );
+  let abortText = await document.l10n.formatValue("openpgp-keygen-abort");
+
+  if (
+    kGenerating &&
+    Services.prompt.confirmEx(
+      window,
+      abortTitle,
+      abortText,
+      Services.prompt.STD_YES_NO_BUTTONS,
+      "",
+      "",
+      "",
+      "",
+      {}
+    ) != 0
+  ) {
+    return;
+  }
+
+  closeOverlay();
+  gKeygenRequest.kill(false);
+  kGenerating = false;
+}
+
+/**
+ * Close the processing wizard overlay.
+ */
+function closeOverlay() {
+  document.getElementById("openPgpKeygenConfirm").removeAttribute("collapsed");
+  document.getElementById("openPgpKeygenProcess").collapsed = true;
+
+  let overlay = document.getElementById("wizardOverlay");
+
+  overlay.removeAttribute("hidden");
+  overlay.addEventListener("transitionend", hideOverlay);
+  overlay.classList.add("hide");
+}
+
+/**
+ * Add the "hidden" attribute tot he processing wizard overlay after the CSS
+ * transition ended.
+ *
+ * @param {Event} event - The DOM Event.
+ */
+function hideOverlay(event) {
+  event.target.setAttribute("hidden", true);
+  event.target.removeEventListener("transitionend", hideOverlay);
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/ui/keyWizard.xhtml
@@ -0,0 +1,207 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/global.css"?>
+<?xml-stylesheet href="chrome://messenger/skin/openpgp/keyWizard.css"?>
+<?xml-stylesheet href="chrome://messenger/skin/openpgp/inlineNotification.css"?>
+
+<!DOCTYPE window>
+
+<window type="child"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns:html="http://www.w3.org/1999/xhtml"
+        onload="init();"
+        style="width: 45em;">
+<dialog id="openPgpKeyWizardDialog"
+        data-l10n-id="key-wizard-button"
+        data-l10n-attrs="buttonlabelaccept, buttonlabelhelp"
+        buttons="accept,cancel">
+
+  <prefpane id="openPgpKeyWizardDialogPane">
+    <script src="chrome://openpgp/content/ui/enigmailCommon.js"/>
+    <script src="chrome://openpgp/content/ui/keyWizard.js"/>
+
+    <linkset>
+      <html:link rel="localization" href="branding/brand.ftl"/>
+      <html:link rel="localization" href="messenger/openpgp/key-wizard.ftl"/>
+    </linkset>
+
+    <html:div id="wizardOverlay" class="wizard-section overlay hide" hidden="hidden">
+      <hbox class="inline-notification-container info-container self-center">
+        <hbox class="inline-notification-wrapper">
+          <image class="notification-image notification-image-info"/>
+          <description data-l10n-id="openpgp-generate-key-info"/>
+        </hbox>
+      </hbox>
+
+      <vbox id="openPgpKeygenConfirm" class="self-center" align="center">
+        <description id="wizardOverlayQuestion"/>
+        <separator class="thin"/>
+        <hbox>
+          <button data-l10n-id="openpgp-keygen-dismiss"
+                  oncommand="closeOverlay();"/>
+          <button data-l10n-id="openpgp-keygen-confirm"
+                  oncommand="openPgpKeygenConfirm();"/>
+        </hbox>
+      </vbox>
+
+      <vbox id="openPgpKeygenProcess" class="self-center" align="center"
+            collapsed="true">
+        <html:legend id="wizardOverlayTitle"></html:legend>
+        <image class="loading-status"/>
+        <button data-l10n-id="openpgp-keygen-cancel"
+                class="self-center"
+                oncommand="openPgpKeygenCancel();"/>
+      </vbox>
+
+    </html:div>
+
+    <vbox id="wizardStart" class="wizard-section">
+      <hbox class="inline-notification-container info-container">
+        <hbox class="inline-notification-wrapper">
+          <image class="notification-image notification-image-info"/>
+          <description>
+            <html:span class="tail-with-learn-more"
+                       data-l10n-id="key-wizard-warning">
+            </html:span>
+            <label is="text-link"
+                   href="https://support.mozilla.org/kb/introduction-to-e2e-encryption"
+                   data-l10n-id="key-wizard-learn-more"
+                   class="learnMore text-link"/>
+          </description>
+        </hbox>
+      </hbox>
+
+      <html:fieldset>
+        <radiogroup id="openPgpKeyChoices" class="indent">
+          <radio id="createOpenPgp" value="0"
+                 data-l10n-id="radio-create-key"/>
+          <radio id="importOpenPgp" value="1"
+                 data-l10n-id="radio-import-key"/>
+          <radio id="externalOpenPgp" value="2"
+                 data-l10n-id="radio-gnupg-key"
+                 hidden="true"/>
+        </radiogroup>
+      </html:fieldset>
+    </vbox>
+
+    <vbox id="wizardCreateKey" class="wizard-section hide-reverse" hidden="true">
+      <label data-l10n-id="openpgp-generate-key-title"
+             class="dialogheader-title"/>
+
+      <html:fieldset>
+        <html:legend data-l10n-id="openpgp-keygen-expiry-title"></html:legend>
+        <description data-l10n-id="openpgp-keygen-expiry-description"/>
+
+        <radiogroup id="openPgpKeygeExpiry" class="indent">
+          <hbox flex="1" align="center">
+            <radio id="keygenExpiration" value="0"
+                   data-l10n-id="radio-keygen-expiry"
+                   oncommand="onExpirationChange(event);"/>
+            <html:input id="expireInput" type="number"
+                        class="size4 input-inline autosync"
+                        maxlength="5" value="1"
+                        aria-labelledby="keygenExpiration"
+                        oninput="validateExpiration();"/>
+            <menulist id="timeScale">
+              <menupopup>
+                <menuitem id="years" value="365"
+                          data-l10n-id="openpgp-keygen-years-label"
+                          oncommand="validateExpiration();"/>
+                <menuitem id="months" value="30"
+                          data-l10n-id="openpgp-keygen-months-label"
+                          selected="true"
+                          oncommand="validateExpiration();"/>
+                <menuitem id="days" value="1"
+                          data-l10n-id="openpgp-keygen-days-label"
+                          oncommand="validateExpiration();"/>
+              </menupopup>
+            </menulist>
+          </hbox>
+          <radio value="1"
+                 data-l10n-id="radio-keygen-no-expiry"
+                 oncommand="onExpirationChange(event);"/>
+        </radiogroup>
+      </html:fieldset>
+
+      <separator/>
+
+      <html:fieldset>
+        <html:legend data-l10n-id="openpgp-keygen-advanced-title"></html:legend>
+        <description data-l10n-id="openpgp-keygen-advanced-description"/>
+
+        <vbox class="indent grid-size">
+          <hbox align="center">
+            <label for="keyType" data-l10n-id="openpgp-keygen-keytype"/>
+          </hbox>
+          <hbox align="center">
+            <menulist id="keyType">
+              <menupopup>
+                <menuitem id="keySize_rsa" value="RSA"
+                          data-l10n-id="openpgp-keygen-type-rsa"
+                          selected="true"
+                          oncommand="onKeyTypeChange(event);"/>
+                <menuitem id="keyType_ecc" value="ECC"
+                          data-l10n-id="openpgp-keygen-type-ecc"
+                          oncommand="onKeyTypeChange(event);"/>
+              </menupopup>
+            </menulist>
+          </hbox>
+          <spacer/>
+
+          <hbox align="center">
+            <label for="keySize" data-l10n-id="openpgp-keygen-keysize"/>
+          </hbox>
+          <hbox align="center">
+            <menulist id="keySize">
+              <menupopup>
+                <menuitem id="keySize_3072" value="3072" label="3072"
+                          selected="true"/>
+                <menuitem id="keySize_4096" value="4096" label="4096"/>
+              </menupopup>
+            </menulist>
+          </hbox>
+          <spacer/>
+
+        </vbox>
+      </html:fieldset>
+
+      <separator/>
+
+      <hbox id="openPgpWarning"
+            class="inline-notification-container error-container self-center"
+            collapsed="true">
+        <hbox class="inline-notification-wrapper">
+          <image class="notification-image notification-image-error"/>
+          <description id="openPgpWarningDescription"/>
+        </hbox>
+      </hbox>
+
+    </vbox>
+
+    <vbox id="wizardImportKey" class="wizard-section hide-reverse" hidden="true">
+      <label data-l10n-id="openpgp-import-key-title"
+             class="dialogheader-title"/>
+
+      <description>
+        Work in Progress: this feature is not yet implemented
+      </description>
+
+      <separator/>
+    </vbox>
+
+    <vbox id="wizardExternalKey" class="wizard-section hide-reverse" hidden="true">
+      <label data-l10n-id="openpgp-external-key-title"
+             class="dialogheader-title"/>
+
+      <description>
+        Work in Progress: this feature is not yet implemented
+      </description>
+
+      <separator/>
+    </vbox>
+  </prefpane>
+</dialog>
+</window>
--- a/mail/locales/jar.mn
+++ b/mail/locales/jar.mn
@@ -9,16 +9,17 @@
 #filter substitution
 
 [localization] @AB_CD@.jar:
   messenger/openpgp/bond.ftl                                            (../extensions/openpgp/content/strings/bond.ftl)
   messenger/openpgp/composeKeyStatus.ftl                                (../extensions/openpgp/content/strings/composeKeyStatus.ftl)
   messenger/openpgp/msgReadStatus.ftl                                   (../extensions/openpgp/content/strings/msgReadStatus.ftl)
   messenger/openpgp/oneRecipientStatus.ftl                              (../extensions/openpgp/content/strings/oneRecipientStatus.ftl)
   messenger/openpgp/enigmail.ftl                                        (../extensions/openpgp/content/strings/enigmail.ftl)
+  messenger/openpgp/key-wizard.ftl                                      (../extensions/openpgp/content/strings/key-wizard.ftl)
   messenger                                                             (%messenger/**/*.ftl)
 
 
 @AB_CD@.jar:
 % locale messenger @AB_CD@ %locale/@AB_CD@/messenger/
   locale/@AB_CD@/messenger/aboutDialog.dtd                              (%chrome/messenger/aboutDialog.dtd)
   locale/@AB_CD@/messenger/aboutDownloads.dtd                           (%chrome/messenger/aboutDownloads.dtd)
   locale/@AB_CD@/messenger/aboutRights.properties                       (%chrome/messenger/aboutRights.properties)
--- a/mail/themes/shared/jar.inc.mn
+++ b/mail/themes/shared/jar.inc.mn
@@ -67,16 +67,17 @@
   skin/classic/messenger/icons/developer.svg                  (../shared/mail/icons/developer.svg)
   skin/classic/messenger/icons/exclude.svg                    (../shared/mail/icons/exclude.svg)
   skin/classic/messenger/icons/encryption-key.svg             (../shared/mail/icons/encryption-key.svg)
   skin/classic/messenger/icons/feeds.svg                      (../shared/mail/icons/feeds.svg)
   skin/classic/messenger/icons/feeds-folder.svg               (../shared/mail/icons/feeds-folder.svg)
   skin/classic/messenger/icons/file.svg                       (../shared/mail/icons/file.svg)
   skin/classic/messenger/icons/file-item.svg                  (../shared/mail/icons/file-item.svg)
   skin/classic/messenger/icons/filter.svg                     (../shared/mail/icons/filter.svg)
+  skin/classic/messenger/icons/fingerprint.svg                (../shared/mail/icons/fingerprint.svg)
   skin/classic/messenger/icons/flag-col.svg                   (../shared/mail/icons/flag-col.svg)
   skin/classic/messenger/icons/flagged.svg                    (../shared/mail/icons/flagged.svg)
   skin/classic/messenger/icons/folder.svg                     (../shared/mail/icons/folder.svg)
   skin/classic/messenger/icons/folder-local.svg               (../shared/mail/icons/folder-local.svg)
   skin/classic/messenger/icons/forget.svg                     (../shared/mail/icons/forget.svg)
   skin/classic/messenger/icons/forward.svg                    (../shared/mail/icons/forward.svg)
   skin/classic/messenger/icons/get-all.svg                    (../shared/mail/icons/get-all.svg)
   skin/classic/messenger/icons/getmsg.svg                     (../shared/mail/icons/getmsg.svg)
@@ -261,16 +262,18 @@
   skin/classic/messenger/openpgp/enigSignInactiveNone.png     (../shared/openpgp/enigSignInactiveNone.png)
   skin/classic/messenger/openpgp/enigSignInactivePlus.png     (../shared/openpgp/enigSignInactivePlus.png)
   skin/classic/messenger/openpgp/enigSignNotOk.png            (../shared/openpgp/enigSignNotOk.png)
   skin/classic/messenger/openpgp/enigSignOk.png               (../shared/openpgp/enigSignOk.png)
   skin/classic/messenger/openpgp/enigSignUnkown.png           (../shared/openpgp/enigSignUnkown.png)
   skin/classic/messenger/openpgp/headerProtected-18.svg       (../shared/openpgp/headerProtected-18.svg)
   skin/classic/messenger/openpgp/headerUnprotected-18.svg     (../shared/openpgp/headerUnprotected-18.svg)
   skin/classic/messenger/openpgp/importSuccess.png            (../shared/openpgp/importSuccess.png)
+  skin/classic/messenger/openpgp/keyWizard.css                (../shared/openpgp/keyWizard.css)
+  skin/classic/messenger/openpgp/inlineNotification.css       (../shared/openpgp/inlineNotification.css)
   skin/classic/messenger/openpgp/ok-sign.svg                  (../shared/openpgp/ok-sign.svg)
   skin/classic/messenger/openpgp/password-error.svg           (../shared/openpgp/password-error.svg)
   skin/classic/messenger/openpgp/sign-active-18.svg           (../shared/openpgp/sign-active-18.svg)
   skin/classic/messenger/openpgp/sign-disabled-18.svg         (../shared/openpgp/sign-disabled-18.svg)
   skin/classic/messenger/openpgp/sign-inactive-18.svg         (../shared/openpgp/sign-inactive-18.svg)
   skin/classic/messenger/openpgp/spinning-wheel.png           (../shared/openpgp/spinning-wheel.png)
   skin/classic/messenger/openpgp/twisty-clsd.png              (../shared/openpgp/twisty-clsd.png)
   skin/classic/messenger/openpgp/twisty-open.png              (../shared/openpgp/twisty-open.png)
--- a/mail/themes/shared/mail/accountManage.css
+++ b/mail/themes/shared/mail/accountManage.css
@@ -295,8 +295,245 @@ treechildren::-moz-tree-image(folderName
 
 .identity-table td {
   padding-inline-end: 10px;
 }
 
 .identity-table td input {
   width: 100%;
 }
+
+/* ::::: e2e encryption ::::: */
+
+#openPgpKey {
+  list-style-image: url("chrome://messenger/skin/icons/login.svg");
+  -moz-context-properties: fill, fill-opacity;
+  fill: currentColor;
+  fill-opacity: 0.5;
+  width: 48px;
+  height: 48px;
+  margin-inline-end: 10px;
+}
+
+/* Add a bit of space to the end of descriptions to
+ * leave margin with e.g. additional buttons on the side. */
+.description-with-side-element {
+  margin-inline-end: 10px !important;
+}
+
+.openpgp-description p {
+  margin-block: 0 6px;
+}
+
+.status-success:not([hidden="true"]) {
+  vertical-align: text-top;
+  display: inline-block;
+  list-style-image: url("chrome://global/skin/icons/check.svg");
+  -moz-context-properties: fill;
+  fill: #12bc00;
+  margin-inline-end: 2px;
+  width: 16px;
+}
+
+/* ::::: OpenPGP key selection ::::: */
+
+.openpgp-container {
+  margin-top: 10px;
+}
+
+.opengpg-intro-section {
+  margin-bottom: 10px;
+}
+
+#openPgpKeyList {
+  margin-top: 16px;
+}
+
+.content-blocking-category .checkbox-label-box,
+.extra-information-label image,
+.arrowhead,
+.content-blocking-info-image {
+  -moz-context-properties: fill;
+  fill: currentColor;
+}
+
+.content-blocking-category {
+  border-radius: 4px;
+  margin: 3px 0;
+  padding: 9px;
+  border: 1px solid var(--in-content-border-color);
+  background-color: rgba(215, 215, 219, 0.2);
+}
+
+.content-blocking-category.disabled {
+  opacity: 0.5;
+}
+
+.content-blocking-category.disabled .radio-check {
+  opacity: 1;
+}
+
+.content-blocking-warning > .indent,
+.content-blocking-category > .indent {
+  margin-inline-end: 28px;
+  margin-inline-start: 30px;
+}
+
+.arrowhead {
+  -moz-appearance: none;
+  border: none;
+  border-radius: 2px;
+  min-height: 20px;
+  min-width: 20px;
+  max-height: 20px;
+  max-width: 20px;
+  list-style-image: url("chrome://global/skin/icons/arrow-dropdown-12.svg");
+  background-color: transparent;
+  padding: 3px;
+}
+
+.arrowhead:not([disabled]):hover {
+  cursor: pointer;
+  background-color: var(--grey-90-a10);
+}
+
+.arrowhead:not([disabled]):hover:active {
+  background-color: var(--grey-90-a20);
+}
+
+.arrowhead.up {
+  list-style-image: url("chrome://global/skin/icons/arrow-up-12.svg");
+}
+
+.arrowhead > .button-box {
+  padding: 0 !important;
+}
+
+.content-blocking-category.expanded:not(.selected) .content-blocking-warning {
+  background-color: var(--grey-90-a10);
+}
+
+.content-blocking-category.selected {
+  border: 1px solid #45A1FF;
+  background-color: rgba(69, 161, 255, 0.2);
+}
+
+.content-blocking-warning-title,
+.content-blocking-category .radio-label-box {
+  font-weight: bold;
+}
+
+.content-blocking-extra-information {
+  visibility: collapse;
+}
+
+.extra-information-label {
+  display: inline-grid;
+  grid-template-columns: auto max-content 2fr;
+  row-gap: 10px;
+  align-items: center;
+  margin-block: 18px;
+}
+
+/* Apply display: block to the containers of all the category information, as
+ * without this the flex-wrapped blocks inside don't stretch vertically to
+ * enclose their content. */
+.content-blocking-category > .indent {
+  display: block;
+}
+
+.content-blocking-category.expanded .content-blocking-extra-information {
+  visibility: visible;
+  display: flex;
+  flex-direction: column;
+  align-content: stretch;
+  margin-bottom: 10px;
+}
+
+.content-blocking-extra-information > .custom-option {
+  margin: 10px 0;
+}
+
+.content-blocking-warning {
+  background-color: rgba(69, 161, 255, 0.2);
+  border-radius: 4px;
+  padding: 10px 0;
+  margin: 10px 0;
+}
+
+.content-blocking-warning:not([hidden]) + .content-blocking-warning {
+  margin-top: 0;
+}
+
+.content-blocking-category-description {
+  font-size: 90%;
+  opacity: 0.6;
+}
+
+.expiration-date-icon {
+  display: none;
+  vertical-align: text-top;
+  -moz-context-properties: fill;
+  margin-inline-end: 4px;
+  fill: currentColor;
+  width: 16px;
+}
+
+.expiration-date-container.key-is-expiring .expiration-date-icon {
+  display: inline-block;
+  list-style-image: url("chrome://messenger/skin/icons/info.svg");
+  opacity: 0.9;
+}
+
+.expiration-date-container.key-expired description {
+  color: #D70022;
+  font-weight: 600;
+}
+
+.expiration-date-container.key-expired .expiration-date-icon {
+  display: inline-block;
+  list-style-image: url("chrome://global/skin/icons/warning.svg");
+  fill: #FF9400;
+}
+
+.expiration-date-button {
+  margin-inline-start: 8px;
+  font-size: 0.9em;
+  min-height: 28px;
+}
+
+.extra-information-label-type {
+  font-weight: 600;
+  margin-inline-end: 4px;
+}
+
+/* Key info icons */
+.extra-information-label image {
+  margin-inline-end: 5px;
+}
+
+.content-blocking-openpgp-type {
+  list-style-image: url("chrome://messenger/skin/icons/mark.svg");
+}
+
+.content-blocking-openpgp-fingerprint {
+  list-style-image: url("chrome://messenger/skin/icons/fingerprint.svg");
+}
+
+.content-blocking-openpgp-created {
+  list-style-image: url("chrome://messenger/skin/shared/preferences/calendar.svg");
+}
+
+.openpgp-key-details {
+  margin-bottom: 18px;
+  border: 1px solid var(--in-content-box-border-color);
+  border-radius: 4px;
+  overflow: hidden;
+  background-color: var(--in-content-page-background);
+}
+
+.openpgp-key-details tabs {
+  border-top: none;
+}
+
+.openpgp-key-details tabpanels {
+  padding: 0 10px 18px;
+}
new file mode 100644
--- /dev/null
+++ b/mail/themes/shared/mail/icons/fingerprint.svg
@@ -0,0 +1,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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="context-fill" fill-opacity="context-fill-opacity"><path d="M8 0a7 7 0 0 0-7 7 1 1 0 0 0 2 0 5 5 0 0 1 10 0 8.24 8.24 0 0 1-1.14 4.28A5.75 5.75 0 0 0 11 14a1 1 0 0 0 2 0 3.91 3.91 0 0 1 .63-1.79A10.18 10.18 0 0 0 15 7a7 7 0 0 0-7-7z"></path><path d="M8 3a4 4 0 0 0-4 4 2 2 0 0 1-2 2 1 1 0 0 0 0 2 4 4 0 0 0 4-4 2 2 0 0 1 4 0 6.88 6.88 0 0 1-1.74 4.8 11 11 0 0 0-1.15 1.75 1 1 0 0 0 .44 1.34A.93.93 0 0 0 8 15a1 1 0 0 0 .89-.55 9.74 9.74 0 0 1 1-1.44A8.84 8.84 0 0 0 12 7a4 4 0 0 0-4-4z"></path><path d="M8 6a1 1 0 0 0-1 1c0 4.21-5.26 6-5.32 6.05a1 1 0 0 0-.63 1.27A1 1 0 0 0 2 15a1.25 1.25 0 0 0 .32 0C2.59 14.86 9 12.66 9 7a1 1 0 0 0-1-1z"></path></svg>
\ No newline at end of file
--- a/mail/themes/shared/mail/icons/login.svg
+++ b/mail/themes/shared/mail/icons/login.svg
@@ -1,4 +1,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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="context-fill" d="M10.992 1a4.009 4.009 0 0 0-4.009 4.008c0 .1.022.187.028.282-.059.05-.119.087-.178.143L5.667 6.6a.366.366 0 0 0 0 .467A1.878 1.878 0 0 0 6 7.5.353.353 0 0 1 6 8l-5 5v1.767a.229.229 0 0 0 .233.233H3.77a.229.229 0 0 0 .23-.233v-.778h.75a.227.227 0 0 0 .233-.228v-.768H5.2s.28 0 .28-.235V12.5h.779s.233-.1.233-.244v-1.271h.855l1.12-1.118H8.7l.467.467c.233.233.233.233.365.233a.437.437 0 0 0 .275-.127l.993-1.273c.034-.053.054-.107.084-.161.036 0 .07.011.107.011a4.008 4.008 0 1 0 0-8.017zM12.5 4.489a1 1 0 1 1 1-1 1 1 0 0 1-1 1z"></path></svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16"><path fill="context-fill" fill-opacity="context-fill-opacity" d="M10.992 1a4.009 4.009 0 0 0-4.009 4.008c0 .1.022.187.028.282-.059.05-.119.087-.178.143L5.667 6.6a.366.366 0 0 0 0 .467A1.878 1.878 0 0 0 6 7.5.353.353 0 0 1 6 8l-5 5v1.767a.229.229 0 0 0 .233.233H3.77a.229.229 0 0 0 .23-.233v-.778h.75a.227.227 0 0 0 .233-.228v-.768H5.2s.28 0 .28-.235V12.5h.779s.233-.1.233-.244v-1.271h.855l1.12-1.118H8.7l.467.467c.233.233.233.233.365.233a.437.437 0 0 0 .275-.127l.993-1.273c.034-.053.054-.107.084-.161.036 0 .07.011.107.011a4.008 4.008 0 1 0 0-8.017zM12.5 4.489a1 1 0 1 1 1-1 1 1 0 0 1-1 1z"></path></svg>
new file mode 100644
--- /dev/null
+++ b/mail/themes/shared/openpgp/inlineNotification.css
@@ -0,0 +1,66 @@
+/* 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/. */
+
+.inline-notification-container:not([collapsed="true"]) {
+  display: block;
+  border-radius: 4px;
+  padding: 10px;
+  margin: 10px 0;
+  color: inherit;
+}
+
+.inline-notification-wrapper {
+  display: flex;
+  align-items: flex-start;
+}
+
+.inline-notification-wrapper.align-center {
+  align-items: center;
+}
+
+.inline-notification-wrapper description {
+  flex: 1;
+}
+
+.notification-image {
+  -moz-context-properties: fill;
+  fill: currentColor;
+  margin-inline: 4px 8px;
+  margin-top: 4px;
+  width: 16px;
+}
+
+.inline-notification-wrapper.align-center {
+  margin-top: 0;
+}
+
+/* Info variation */
+
+.inline-notification-container.info-container {
+  background-color: rgba(69, 161, 255, 0.2);
+}
+
+.notification-image-info {
+  list-style-image: url("chrome://messenger/skin/icons/information.svg");
+}
+
+/* error variation */
+
+.inline-notification-container.error-container {
+  background-color: rgba(255, 69, 69, 0.2);
+}
+
+.notification-image-error {
+  list-style-image: url("chrome://global/skin/icons/warning.svg");
+}
+
+/* success variation */
+
+.inline-notification-container.success-container {
+  background-color: rgba(69, 255, 100, 0.2);
+}
+
+.notification-image-success {
+  list-style-image: url("chrome://global/skin/icons/check.svg");
+}
new file mode 100644
--- /dev/null
+++ b/mail/themes/shared/openpgp/keyWizard.css
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+@import url("chrome://messenger/skin/messenger.css");
+
+/* ::::: Warnings and inline notifications ::::: */
+
+.dialogheader-title {
+  margin-block: 0 8px;
+  margin-inline-start: 0;
+  font-size: 1.46em;
+  font-weight: 300;
+  line-height: 1.3em;
+  color: var(--in-content-text-color);
+}
+
+.wizard-section {
+  transition: transform 230ms ease, opacity 230ms ease;
+}
+
+.wizard-section.hide {
+  transform: translateY(-100%);
+  opacity: 0;
+}
+
+.wizard-section.hide-reverse {
+  transform: translateY(100%);
+  opacity: 0;
+}
+
+.grid-size {
+  display: grid;
+  grid-template-columns: auto 1fr 1fr;
+}
+
+.overlay {
+  display: flex;
+  position: fixed;
+  flex-direction: column;
+  justify-content: center;
+  background-color: var(--in-content-page-background);
+  inset: 0;
+  z-index: 1;
+}
+
+.self-center {
+  align-self: center;
+}
+
+.loading-status {
+  -moz-context-properties: fill;
+  fill: currentColor;
+  width: 16px;
+  height: 16px;
+  background: url("chrome://global/skin/icons/loading.png") no-repeat;
+  background-size: 16px 16px;
+}