Bug 1636290 - Initial user assistance for OpenPGP problems in message composer. r=PatrickBrunschwig,mkmelin a=wsmwk
authorKai Engert <kaie@kuix.de>
Sun, 03 May 2020 21:46:07 +0200
changeset 38146 a4c6822ef10da51179aea767abb0b4487e7b86c4
parent 38145 70da4ae3946486e8723aab361efaa4afe3e1d6c0
child 38147 eb57edd569a7ea66dffcaf87c37ec810dff1ba90
push id2602
push userkaie@kuix.de
push dateSun, 17 May 2020 18:03:23 +0000
treeherdercomm-beta@f78f85f503c0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersPatrickBrunschwig, mkmelin, wsmwk
bugs1636290
Bug 1636290 - Initial user assistance for OpenPGP problems in message composer. r=PatrickBrunschwig,mkmelin a=wsmwk Differential Revision: https://phabricator.services.mozilla.com/D74330
mail/components/compose/content/MsgComposeCommands.js
mail/extensions/am-e2e/prefs/e2e-prefs.js
mail/extensions/openpgp/content/modules/RNP.jsm
mail/extensions/openpgp/content/modules/keyRing.jsm
mail/extensions/openpgp/content/modules/keyserver.jsm
mail/extensions/openpgp/content/modules/keyserverUris.jsm
mail/extensions/openpgp/content/modules/sqliteDb.jsm
mail/extensions/openpgp/content/modules/uidHelper.jsm
mail/extensions/openpgp/content/strings/composeKeyStatus.dtd
mail/extensions/openpgp/content/strings/composeKeyStatus.properties
mail/extensions/openpgp/content/strings/enigmail.properties
mail/extensions/openpgp/content/strings/oneRecipientStatus.dtd
mail/extensions/openpgp/content/strings/oneRecipientStatus.properties
mail/extensions/openpgp/content/ui/composeKeyStatus.js
mail/extensions/openpgp/content/ui/composeKeyStatus.xhtml
mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
mail/extensions/openpgp/content/ui/enigmailMsgComposeHelper.js
mail/extensions/openpgp/content/ui/enigmailMsgComposeOverlay.js
mail/extensions/openpgp/content/ui/keyDetailsDlg.js
mail/extensions/openpgp/content/ui/keyDetailsDlg.xhtml
mail/extensions/openpgp/content/ui/oneRecipientStatus.js
mail/extensions/openpgp/content/ui/oneRecipientStatus.xhtml
mail/extensions/openpgp/jar.mn
mail/installer/allowed-dupes.mn
mail/themes/shared/jar.inc.mn
mail/themes/shared/openpgp/composeKeyStatus.css
mail/themes/shared/openpgp/oneRecipientStatus.css
--- a/mail/components/compose/content/MsgComposeCommands.js
+++ b/mail/components/compose/content/MsgComposeCommands.js
@@ -1729,31 +1729,47 @@ function setSecuritySettings(menu_id) {
       }
     }
   }
 }
 
 function showMessageComposeSecurityStatus() {
   Recipients2CompFields(gMsgCompose.compFields);
 
-  window.openDialog(
-    "chrome://messenger-smime/content/msgCompSecurityInfo.xhtml",
-    "",
-    "chrome,modal,resizable,centerscreen",
-    {
-      compFields: gMsgCompose.compFields,
-      subject: document.getElementById("msgSubject").value,
-      smFields: gSMFields,
-      isSigningCertAvailable:
-        gCurrentIdentity.getUnicharAttribute("signing_cert_name") != "",
-      isEncryptionCertAvailable:
-        gCurrentIdentity.getUnicharAttribute("encryption_cert_name") != "",
-      currentIdentity: gCurrentIdentity,
-    }
-  );
+  if (
+    MailConstants.MOZ_OPENPGP &&
+    BondOpenPGP.allDependenciesLoaded() &&
+    gSelectedTechnologyIsPGP
+  ) {
+    window.openDialog(
+      "chrome://openpgp/content/ui/composeKeyStatus.xhtml",
+      "",
+      "chrome,modal,resizable,centerscreen",
+      {
+        compFields: gMsgCompose.compFields,
+        currentIdentity: gCurrentIdentity,
+      }
+    );
+  } else {
+    window.openDialog(
+      "chrome://messenger-smime/content/msgCompSecurityInfo.xhtml",
+      "",
+      "chrome,modal,resizable,centerscreen",
+      {
+        compFields: gMsgCompose.compFields,
+        subject: document.getElementById("msgSubject").value,
+        smFields: gSMFields,
+        isSigningCertAvailable:
+          gCurrentIdentity.getUnicharAttribute("signing_cert_name") != "",
+        isEncryptionCertAvailable:
+          gCurrentIdentity.getUnicharAttribute("encryption_cert_name") != "",
+        currentIdentity: gCurrentIdentity,
+      }
+    );
+  }
 }
 
 function openEditorContextMenu(popup) {
   gSpellChecker.clearSuggestionsFromMenu();
   gSpellChecker.initFromEvent(
     document.popupRangeParent,
     document.popupRangeOffset
   );
--- a/mail/extensions/am-e2e/prefs/e2e-prefs.js
+++ b/mail/extensions/am-e2e/prefs/e2e-prefs.js
@@ -83,18 +83,18 @@ pref("temp.openpgp.inlineSigAttachExt", 
 
 // debug log directory (if set, also enabled debugging)
 pref("temp.openpgp.logDirectory", "");
 
 // display all or no keys by default in the key manager
 pref("temp.openpgp.keyManShowAllKeys", true);
 
 
-// list of keyservers to use
-pref("temp.openpgp.keyserver", "vks://keys.openpgp.org, hkps://hkps.pool.sks-keyservers.net, hkps://pgp.mit.edu");
+// list of keyservers to use (comma separated list)
+pref("temp.openpgp.keyserver", "vks://keys.openpgp.org");
 
 // auto select the first keyserver in the key server list
 pref("temp.openpgp.autoKeyServerSelection", true);
 
 // keep passphrase for ... minutes
 pref("temp.openpgp.maxIdleMinutes", 5);
 
 // maximum number of parallel decrypt processes that Enigmaik will handle
--- a/mail/extensions/openpgp/content/modules/RNP.jsm
+++ b/mail/extensions/openpgp/content/modules/RNP.jsm
@@ -323,16 +323,18 @@ var RNP = {
       throw new Error("rnp_key_is_revoked failed");
     }
 
     if (key_revoked.value) {
       keyObj.keyTrust = "r";
       if (forListing) {
         keyObj.revoke = true;
       }
+    } else if (this.isExpiredTime(keyObj.expiryTime)) {
+      keyObj.keyTrust = "e";
     } else if (keyObj.secretAvailable) {
       keyObj.keyTrust = "u";
     } else {
       keyObj.keyTrust = "o";
     }
 
     /* The remaining actions are done for primary keys, only. */
     if (!is_subkey.value) {
@@ -678,17 +680,25 @@ var RNP = {
     if (
       result.exitCode &&
       !("alreadyUsedGPGME" in options) &&
       GPGME.allDependenciesLoaded()
     ) {
       // failure processing with RNP, attempt decryption with GPGME
       let r2 = await GPGME.decrypt(encrypted, RNP.enArmor);
       if (!r2.exitCode && r2.decryptedData) {
+        // TODO: obtain info which key ID was used for decryption
+        //       and set result.decryptKey*
+        //       It isn't obvious how to do that with GPGME, because
+        //       gpgme_op_decrypt_result provides the list of all the
+        //       encryption keys, only.
         options.alreadyUsedGPGME = true;
+        // The result may still contain wrapping like compression,
+        // and optional signature data. Recursively call ourselves
+        // to perform the remaining processing.
         return RNP.decrypt(r2.decryptedData, options);
       }
     }
 
     return result;
   },
 
   async getVerifyDetails(ffi, fromAddr, verify_op, result) {
@@ -1752,32 +1762,42 @@ var RNP = {
 
     if (args.sign) {
       resultStatus.statusFlags |= EnigmailConstants.SIG_CREATED;
     }
 
     return result;
   },
 
+  /**
+   * @param {number} expiryTime - Time to check, in seconds from the epoch.
+   * @return {Boolean} - true if the given time is after now.
+   */
+  isExpiredTime(expiryTime) {
+    if (!expiryTime) {
+      return false;
+    }
+    let nowSeconds = Math.floor(Date.now() / 1000);
+    return nowSeconds > expiryTime;
+  },
+
   isKeyExpired(handle) {
     let expiration = new ctypes.uint32_t();
     if (RNPLib.rnp_key_get_expiration(handle, expiration.address())) {
       throw new Error("rnp_key_get_expiration failed");
     }
     if (!expiration.value) {
       return false;
     }
-    let nowSeconds = Math.floor(Date.now() / 1000);
     let creation = new ctypes.uint32_t();
     if (RNPLib.rnp_key_get_creation(handle, creation.address())) {
       throw new Error("rnp_key_get_creation failed");
     }
     let expirationSeconds = creation.value + expiration.value;
-    let isExpired = nowSeconds > expirationSeconds;
-    return isExpired;
+    return this.isExpiredTime(expirationSeconds);
   },
 
   async findKeyByEmail(id, onlyAcceptableAsPublic = false) {
     if (!id.startsWith("<") || !id.endsWith(">") || id.includes(" ")) {
       throw new Error("invalid parameter given to findKeyByEmail");
     }
 
     let emailWithoutBrackets = id.substring(1, id.length - 1);
--- a/mail/extensions/openpgp/content/modules/keyRing.jsm
+++ b/mail/extensions/openpgp/content/modules/keyRing.jsm
@@ -28,16 +28,22 @@ const { EnigmailLazy } = ChromeUtils.imp
 );
 const { newEnigmailKeyObj } = ChromeUtils.import(
   "chrome://openpgp/content/modules/keyObj.jsm"
 );
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const { EnigmailCryptoAPI } = ChromeUtils.import(
   "chrome://openpgp/content/modules/cryptoAPI.jsm"
 );
+const { PgpSqliteDb2 } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/sqliteDb.jsm"
+);
+const { uidHelper } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/uidHelper.jsm"
+);
 
 const getDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
 const getWindows = EnigmailLazy.loader(
   "enigmail/windows.jsm",
   "EnigmailWindows"
 );
 
 const DEFAULT_FILE_PERMS = 0o600;
@@ -61,18 +67,16 @@ let gLoadingKeys = false;
             - p: pgp/classical
             - t: always trust
             - a: auto (:0) (default, currently pgp/classical)
             - T: TOFU
             - TP: TOFU+PGP
 
 */
 
-const TRUSTLEVELS_SORTED = EnigmailTrust.trustLevelsSorted();
-
 var EnigmailKeyRing = {
   /**
    * Get the complete list of all public keys, optionally sorted by a column
    *
    * @param  win           - optional |object| holding the parent window for displaying error messages
    * @param  sortColumn    - optional |string| containing the column name for sorting. One of:
    *                            userid, keyid, keyidshort, fpr, keytype, validity, trust, expiry
    * @param  sortDirection - |number| 1 = ascending / -1 = descending
@@ -447,17 +451,19 @@ var EnigmailKeyRing = {
    *                                     key ID, fingerprint, or userId
    * @param outputFile        String or nsIFile - output file name or Object - or NULL
    * @param exitCodeObj       Object   - o.value will contain exit code
    * @param errorMsgObj       Object   - o.value will contain error message from GnuPG
    *
    * @return String - if outputFile is NULL, the key block data; "" if a file is written
    */
   extractKey(includeSecretKey, idArray, outputFile, exitCodeObj, errorMsgObj) {
-    EnigmailLog.DEBUG("keyRing.jsm: EnigmailKeyRing.extractKey: %o\n", idArray);
+    EnigmailLog.DEBUG(
+      "keyRing.jsm: EnigmailKeyRing.extractKey: " + idArray + "\n"
+    );
     exitCodeObj.value = -1;
 
     if (includeSecretKey) {
       throw new Error("extractKey with secret key not implemented");
     }
 
     if (!Array.isArray(idArray) || !idArray.length) {
       throw new Error("invalid parameter given to EnigmailKeyRing.extractKey");
@@ -636,16 +642,73 @@ var EnigmailKeyRing = {
 
     let exitCode = 0;
 
     EnigmailKeyRing.clearCache();
 
     return exitCode;
   },
 
+  importKeyDataWithConfirmation(window, preview, keyData, isBinary) {
+    let somethingWasImported = false;
+    if (preview.length > 0) {
+      let exitStatus;
+      if (preview.length == 1) {
+        exitStatus = getDialog().confirmDlg(
+          window,
+          EnigmailLocale.getString("doImportOne", [
+            preview[0].name,
+            preview[0].id,
+          ])
+        );
+      } else {
+        exitStatus = getDialog().confirmDlg(
+          window,
+          EnigmailLocale.getString("doImportMultiple", [
+            preview
+              .map(function(a) {
+                return "\t" + a.name + " (" + a.id + ")";
+              })
+              .join("\n"),
+          ])
+        );
+      }
+
+      if (exitStatus) {
+        let errorMsgObj = {};
+        try {
+          exitStatus = EnigmailKeyRing.importKey(
+            window,
+            false,
+            keyData,
+            isBinary,
+            "",
+            errorMsgObj
+          );
+        } catch (ex) {
+          console.debug(ex);
+        }
+
+        if (exitStatus === 0) {
+          let keyList = preview.map(a => a.id);
+          getDialog().keyImportDlg(window, keyList);
+          somethingWasImported = true;
+        } else {
+          getDialog().alert(
+            window,
+            EnigmailLocale.getString("failKeyImport") + "\n" + errorMsgObj.value
+          );
+        }
+      }
+    } else {
+      getDialog().alert(window, EnigmailLocale.getString("noKeyFound"));
+    }
+    return somethingWasImported;
+  },
+
   /**
    * Generate a new key pair with GnuPG
    *
    * @name:       String     - name part of UID
    * @comment:    String     - comment part of UID (brackets are added)
    * @comment:    String     - email part of UID (<> will be added)
    * @expiryDate: Number     - Unix timestamp of key expiry date; 0 if no expiry
    * @keyLength:  Number     - size of key in bytes (e.g 4096)
@@ -674,284 +737,212 @@ var EnigmailKeyRing = {
 
   /**
    * try to find valid key for encryption to passed email address
    *
    * @param details if not null returns error in details.msg
    *
    * @return: found key ID (without leading "0x") or null
    */
-  getValidKeyForRecipient(emailAddr, minTrustLevelIndex, details) {
+  async getValidKeyForRecipient(emailAddr, details) {
     EnigmailLog.DEBUG(
       'keyRing.jsm: getValidKeyForRecipient(): emailAddr="' + emailAddr + '"\n'
     );
-    const TRUSTLEVELS_SORTED = EnigmailTrust.trustLevelsSorted();
-    const fullTrustIndex = TRUSTLEVELS_SORTED.indexOf("f");
+    const FULLTRUSTLEVEL = 2;
 
     emailAddr = emailAddr.toLowerCase();
-    var embeddedEmailAddr = "<" + emailAddr + ">";
 
-    // note: we can't take just the first matched because we might have faked keys as duplicates
     var foundKeyId = null;
-    var foundKeyTrustIndex = null;
+    var foundAcceptanceLevel = null;
 
-    let k = this.getAllKeys(null, "validity", -1);
+    let k = this.getAllKeys(null, null);
     let keyList = k.keyList;
-    let keySortList = k.keySortList;
 
-    // **** LOOP to check against each key
-    // - note: we have sorted the keys according to validity
-    //         to abort the loop as soon as we reach keys that are not valid enough
-    for (var idx = 0; idx < keySortList.length; idx++) {
-      var keyObj = keyList[keySortList[idx].keyNum];
-      var keyTrust = keyObj.keyTrust;
-      var keyTrustIndex = TRUSTLEVELS_SORTED.indexOf(keyTrust);
-      //EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient():  check key " + keyObj.keyId + "\n");
+    for (var idx = 0; idx < keyList.length; idx++) {
+      var keyObj = keyList[idx];
 
-      // key trust (our sort criterion) too low?
-      // => *** regular END of the loop
-      if (keyTrustIndex < minTrustLevelIndex) {
-        if (!foundKeyId) {
-          if (details) {
-            details.msg = "ProblemNoKey";
-          }
-          let msg =
-            "no key with enough trust level for '" + emailAddr + "' found";
-          EnigmailLog.DEBUG(
-            "keyRing.jsm: getValidKeyForRecipient():  " + msg + "\n"
-          );
-        }
-        return foundKeyId; // **** regular END OF LOOP (return NULL or found single key)
+      switch (keyObj.keyTrust) {
+        case "e":
+        case "r":
+          continue;
       }
 
+      let uidMatch = false;
+      for (let uid of keyObj.userIds) {
+        if (uid.type !== "uid") {
+          continue;
+        }
+        let split = {};
+        if (uidHelper.getPartsFromUidStr(uid.userId, split)) {
+          let uidEmail = split.email.toLowerCase();
+          if (uidEmail === emailAddr) {
+            uidMatch = true;
+            break;
+          }
+        }
+      }
+      if (!uidMatch) {
+        continue;
+      }
       // key valid for encryption?
       if (!keyObj.keyUseFor.includes("E")) {
         //EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient():  skip key " + keyObj.keyId + " (not provided for encryption)\n");
         continue; // not valid for encryption => **** CONTINUE the LOOP
       }
-      // key disabled?
-      if (keyObj.keyUseFor.includes("D")) {
-        //EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient():  skip key " + keyObj.keyId + " (disabled)\n");
-        continue; // disabled => **** CONTINUE the LOOP
+
+      let acceptanceLevel;
+      if (keyObj.secretAvailable) {
+        acceptanceLevel = 3;
+      } else {
+        acceptanceLevel = await this.getKeyAcceptanceLevelForEmail(
+          keyObj,
+          emailAddr
+        );
       }
 
-      // check against the user ID
-      var userId = keyObj.userId.toLowerCase();
-      if (
-        userId &&
-        (userId == emailAddr || userId.includes(embeddedEmailAddr))
-      ) {
-        if (keyTrustIndex < minTrustLevelIndex) {
-          EnigmailLog.DEBUG(
-            "keyRing.jsm: getValidKeyForRecipient():  matching key=" +
-              keyObj.keyId +
-              " found but not enough trust\n"
-          );
-        } else {
-          // key with enough trust level found
-          EnigmailLog.DEBUG(
-            "keyRing.jsm: getValidKeyForRecipient():  key=" +
-              keyObj.keyId +
-              ' keyTrust="' +
-              keyTrust +
-              '" found\n'
-          );
-
-          // immediately return if a fully or ultimately trusted key is found
-          // (faked keys should not be an issue here, so we don't have to check other keys)
-          if (keyTrustIndex >= fullTrustIndex) {
-            return keyObj.keyId;
-          }
+      if (acceptanceLevel < 1) {
+        continue;
+      }
 
-          if (foundKeyId != keyObj.keyId) {
-            // new matching key found (note: might find same key via subkeys)
-            if (foundKeyId) {
-              // different matching keys found
-              if (foundKeyTrustIndex > keyTrustIndex) {
-                return foundKeyId; // OK, previously found key has higher trust level
-              }
-              // error because we have two keys with same trust level
-              // => let the user decide (to prevent from using faked keys with default trust level)
-              if (details) {
-                details.msg = "ProblemMultipleKeys";
-              }
-              let msg =
-                "multiple matching keys with same trust level found for '" +
-                emailAddr +
-                "' ";
-              EnigmailLog.DEBUG(
-                "keyRing.jsm: getValidKeyForRecipient():  " +
-                  msg +
-                  ' trustLevel="' +
-                  keyTrust +
-                  '" (0x' +
-                  foundKeyId +
-                  " and 0x" +
-                  keyObj.keyId +
-                  ")\n"
-              );
-              return null;
-            }
-            // save found key to compare with other matching keys (handling of faked keys)
-            foundKeyId = keyObj.keyId;
-            foundKeyTrustIndex = keyTrustIndex;
-          }
-          continue; // matching key found (again) => **** CONTINUE the LOOP (don't check Sub-UserIDs)
-        }
+      // immediately return if a fully or ultimately trusted key is found
+      if (acceptanceLevel >= FULLTRUSTLEVEL) {
+        return keyObj.keyId;
       }
 
-      // check against the sub user ID
-      // (if we are here, the primary user ID didn't match)
-      // - Note: sub user IDs have NO owner trust
-      for (var subUidIdx = 1; subUidIdx < keyObj.userIds.length; subUidIdx++) {
-        var subUidObj = keyObj.userIds[subUidIdx];
-        var subUserId = subUidObj.userId.toLowerCase();
-        var subUidTrust = subUidObj.keyTrust;
-        var subUidTrustIndex = TRUSTLEVELS_SORTED.indexOf(subUidTrust);
-        //EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient():  check subUid " + subUidObj.keyId + "\n");
-
+      if (foundKeyId != keyObj.keyId) {
+        // different matching key found
         if (
-          subUserId &&
-          (subUserId == emailAddr || subUserId.includes(embeddedEmailAddr))
+          !foundKeyId ||
+          (foundKeyId && acceptanceLevel > foundAcceptanceLevel)
         ) {
-          if (subUidTrustIndex < minTrustLevelIndex) {
-            EnigmailLog.DEBUG(
-              "keyRing.jsm: getValidKeyForRecipient():  matching subUid=" +
-                keyObj.keyId +
-                " found but not enough trust\n"
-            );
-          } else {
-            // subkey with enough trust level found
-            EnigmailLog.DEBUG(
-              "keyRing.jsm: getValidKeyForRecipient():  matching subUid in key=" +
-                keyObj.keyId +
-                ' keyTrust="' +
-                keyTrust +
-                '" found\n'
-            );
-
-            if (keyTrustIndex >= fullTrustIndex) {
-              // immediately return if a fully or ultimately trusted key is found
-              // (faked keys should not be an issue here, so we don't have to check other keys)
-              return keyObj.keyId;
-            }
-
-            if (foundKeyId != keyObj.keyId) {
-              // new matching key found (note: might find same key via different subkeys)
-              if (foundKeyId) {
-                // different matching keys found
-                if (foundKeyTrustIndex > subUidTrustIndex) {
-                  return foundKeyId; // OK, previously found key has higher trust level
-                }
-                // error because we have two keys with same trust level
-                // => let the user decide (to prevent from using faked keys with default trust level)
-                if (details) {
-                  details.msg = "ProblemMultipleKeys";
-                }
-                let msg =
-                  "multiple matching keys with same trust level found for '" +
-                  emailAddr +
-                  "' ";
-                EnigmailLog.DEBUG(
-                  "keyRing.jsm: getValidKeyForRecipient():  " +
-                    msg +
-                    ' trustLevel="' +
-                    keyTrust +
-                    '" (0x' +
-                    foundKeyId +
-                    " and 0x" +
-                    keyObj.keyId +
-                    ")\n"
-                );
-                return null;
-              }
-              // save found key to compare with other matching keys (handling of faked keys)
-              foundKeyId = keyObj.keyId;
-              foundKeyTrustIndex = subUidTrustIndex;
-            }
-          }
+          foundKeyId = keyObj.keyId;
+          foundAcceptanceLevel = acceptanceLevel;
         }
       }
-    } // **** LOOP to check against each key
+    }
 
     if (!foundKeyId) {
+      if (details) {
+        details.msg = "ProblemNoKey";
+      }
+      let msg = "no key with enough trust level for '" + emailAddr + "' found";
       EnigmailLog.DEBUG(
-        "keyRing.jsm: getValidKeyForRecipient():  no key for '" +
-          emailAddr +
-          "' found\n"
+        "keyRing.jsm: getValidKeyForRecipient():  " + msg + "\n"
+      );
+    } else {
+      EnigmailLog.DEBUG(
+        "keyRing.jsm: getValidKeyForRecipient():  key=" +
+          keyObj.keyId +
+          '" found\n'
       );
     }
     return foundKeyId;
   },
 
+  async getKeyAcceptanceLevelForEmail(keyObj, email) {
+    let acceptanceLevel = 0;
+
+    let acceptanceResult = {};
+    try {
+      await PgpSqliteDb2.getAcceptance(keyObj.fpr, email, acceptanceResult);
+    } catch (ex) {
+      console.debug("getAcceptance failed: " + ex);
+      return null;
+    }
+
+    if (acceptanceResult.emailDecided) {
+      switch (acceptanceResult.fingerprintAcceptance) {
+        case "verified":
+          acceptanceLevel = 2;
+          break;
+        case "unverified":
+          acceptanceLevel = 1;
+          break;
+        case "rejected":
+          acceptanceLevel = -1;
+          break;
+        default:
+        case "undecided":
+          acceptanceLevel = 0;
+          break;
+      }
+    }
+    return acceptanceLevel;
+  },
+
+  async getKeyAcceptanceForEmail(keyObj, email) {
+    let acceptanceResult = {};
+    try {
+      await PgpSqliteDb2.getAcceptance(keyObj.fpr, email, acceptanceResult);
+    } catch (ex) {
+      console.debug("getAcceptance failed: " + ex);
+      return null;
+    }
+
+    if (acceptanceResult.emailDecided) {
+      switch (acceptanceResult.fingerprintAcceptance) {
+        case "verified":
+        case "unverified":
+        case "rejected":
+        case "undecided":
+          return acceptanceResult.fingerprintAcceptance;
+      }
+    }
+
+    return "undecided";
+  },
+
   /**
    *  Determine the key ID for a set of given addresses
    *
    * @param {Array<String>} addresses: email addresses
-   * @param {String} minTrustLevel:    f for Fully trusted keys / ? for any valid key
    * @param {Object} details:          holds details for invalid keys:
    *                                   - errArray: {
    *                                       * addr {String}: email addresses
    *                                       * msg {String}:  related error
    *                                       }
    *                                   - keyMap {Object<String>}: map of email addr -> keyID
    * @param {Array<String>} resultingArray: list of found key IDs
    *
    * @return {Boolean}: true if at least one key missing; false otherwise
    */
-  getValidKeysForAllRecipients(
-    addresses,
-    minTrustLevel,
-    details,
-    resultingArray
-  ) {
-    let minTrustLevelIndex = TRUSTLEVELS_SORTED.indexOf(minTrustLevel);
-
+  async getValidKeysForAllRecipients(addresses, details, resultingArray) {
+    if (!addresses) {
+      return null;
+    }
     // check whether each address is or has a key:
     let keyMissing = false;
     if (details) {
       details.errArray = [];
       details.keyMap = {};
     }
     for (let i = 0; i < addresses.length; i++) {
       let addr = addresses[i];
+      if (!addr) {
+        continue;
+      }
       // try to find current address in key list:
       let keyId = null;
       var errMsg = null;
-      if (addr.includes("@")) {
-        // try email match:
-        var addrErrDetails = {};
-        let foundKeyId = this.getValidKeyForRecipient(
-          addr,
-          minTrustLevelIndex,
-          addrErrDetails
+      if (!addr.includes("@")) {
+        throw new Error(
+          "getValidKeysForAllRecipients unexpected lookup for non-email addr: " +
+            addr
         );
-        if (details && addrErrDetails.msg) {
-          errMsg = addrErrDetails.msg;
-        }
-        if (foundKeyId) {
-          keyId = "0x" + foundKeyId.toUpperCase();
-          resultingArray.push(keyId);
-        }
-      } else {
-        // try key match:
-        var keyObj = this.getKeyById(addr);
-
-        if (keyObj) {
-          // if found, check whether the trust level is enough
-          if (
-            TRUSTLEVELS_SORTED.indexOf(keyObj.keyTrust) >= minTrustLevelIndex
-          ) {
-            keyId = "0x" + keyObj.keyId.toUpperCase();
-            resultingArray.push(keyId);
-          }
-        }
       }
 
-      if (keyId) {
+      // try email match:
+      var addrErrDetails = {};
+      let foundKeyId = await this.getValidKeyForRecipient(addr, addrErrDetails);
+      if (details && addrErrDetails.msg) {
+        errMsg = addrErrDetails.msg;
+      }
+      if (foundKeyId) {
+        keyId = "0x" + foundKeyId.toUpperCase();
+        resultingArray.push(keyId);
         if (details) {
           details.keyMap[addr.toLowerCase()] = keyId;
         }
       } else {
         // no key for this address found
         keyMissing = true;
         if (details) {
           if (!errMsg) {
@@ -960,25 +951,115 @@ var EnigmailKeyRing = {
           var detailsElem = {};
           detailsElem.addr = addr;
           detailsElem.msg = errMsg;
           details.errArray.push(detailsElem);
         }
         EnigmailLog.DEBUG(
           'keyRing.jsm: doValidKeysForAllRecipients(): return null (no single valid key found for="' +
             addr +
-            '" with minTrustLevel="' +
-            minTrustLevel +
             '")\n'
         );
       }
     }
     return keyMissing;
   },
 
+  async getMultValidKeysForOneRecipient(emailAddr) {
+    EnigmailLog.DEBUG(
+      'keyRing.jsm: getMultValidKeysForOneRecipient(): emailAddr="' +
+        emailAddr +
+        '"\n'
+    );
+    emailAddr = emailAddr.toLowerCase();
+    if (emailAddr.startsWith("<") && emailAddr.endsWith(">")) {
+      emailAddr = emailAddr.substr(1, emailAddr.length - 2);
+    }
+
+    let found = [];
+
+    let k = this.getAllKeys(null, null);
+    let keyList = k.keyList;
+
+    for (var idx = 0; idx < keyList.length; idx++) {
+      var keyObj = keyList[idx];
+
+      switch (keyObj.keyTrust) {
+        case "e":
+        case "r":
+          continue;
+        default:
+          break;
+      }
+
+      let uidMatch = false;
+      for (let uid of keyObj.userIds) {
+        if (uid.type !== "uid") {
+          continue;
+        }
+        let split = {};
+        if (uidHelper.getPartsFromUidStr(uid.userId, split)) {
+          let uidEmail = split.email.toLowerCase();
+          if (uidEmail === emailAddr) {
+            uidMatch = true;
+            break;
+          }
+        }
+      }
+      if (!uidMatch) {
+        continue;
+      }
+      // key valid for encryption?
+      if (!keyObj.keyUseFor.includes("E")) {
+        //EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient():  skip key " + keyObj.keyId + " (not provided for encryption)\n");
+        continue; // not valid for encryption => **** CONTINUE the LOOP
+      }
+      if (!keyObj.secretAvailable) {
+        keyObj.acceptance = await this.getKeyAcceptanceForEmail(
+          keyObj,
+          emailAddr
+        );
+      }
+      found.push(keyObj);
+    }
+    return found;
+  },
+
+  /**
+   *  Determine the key ID for a set of given addresses
+   *
+   * @param {string[]} addresses - Email addresses to get key id for.
+   *
+   * @return {Map<string,keyObj[]>}: map of email addr -> keyObj[]
+   */
+  async getMultValidKeysForMultRecipients(addresses) {
+    if (!addresses) {
+      return null;
+    }
+    let allKeysMap = new Map();
+    for (let i = 0; i < addresses.length; i++) {
+      let addr = addresses[i].toLowerCase();
+      if (!addr) {
+        continue;
+      }
+
+      if (!addr.includes("@")) {
+        throw new Error(
+          "getAllRecipientKeys unexpected lookup for non-email addr: " + addr
+        );
+      }
+
+      let found = await this.getMultValidKeysForOneRecipient(addr);
+      if (found) {
+        allKeysMap.set(addr, found);
+      }
+    }
+    return allKeysMap;
+  },
+
   /**
    * Rebuild the quick access search indexes after the key list was loaded
    */
   rebuildKeyIndex() {
     gKeyIndex = [];
     gSubkeyIndex = [];
 
     for (let i in gKeyListObj.keyList) {
--- a/mail/extensions/openpgp/content/modules/keyserver.jsm
+++ b/mail/extensions/openpgp/content/modules/keyserver.jsm
@@ -15,19 +15,16 @@ const { EnigmailLog } = ChromeUtils.impo
   "chrome://openpgp/content/modules/log.jsm"
 );
 const { EnigmailLocale } = ChromeUtils.import(
   "chrome://openpgp/content/modules/locale.jsm"
 );
 const { EnigmailKeyRing } = ChromeUtils.import(
   "chrome://openpgp/content/modules/keyRing.jsm"
 );
-const { EnigmailKeyserverURIs } = ChromeUtils.import(
-  "chrome://openpgp/content/modules/keyserverUris.jsm"
-);
 const { EnigmailData } = ChromeUtils.import(
   "chrome://openpgp/content/modules/data.jsm"
 );
 const { EnigmailConstants } = ChromeUtils.import(
   "chrome://openpgp/content/modules/constants.jsm"
 );
 const { EnigmailGpg } = ChromeUtils.import(
   "chrome://openpgp/content/modules/gpg.jsm"
@@ -239,18 +236,18 @@ const accessHkpInternal = {
    * @param listener:    optional Object implementing the KeySrvListener API (above)
    *
    * @return:   Promise<Number (Status-ID)>
    */
   accessKeyServer(actionFlag, keyserver, keyId, listener) {
     EnigmailLog.DEBUG(
       `keyserver.jsm: accessHkpInternal.accessKeyServer(${keyserver})\n`
     );
-    if (keyserver === null) {
-      keyserver = EnigmailKeyserverURIs.getDefaultKeyServer();
+    if (!keyserver) {
+      throw new Error("accessKeyServer requires explicit keyserver parameter");
     }
 
     return new Promise((resolve, reject) => {
       let xmlReq = null;
       if (listener && typeof listener === "object") {
         listener.onCancel = function() {
           EnigmailLog.DEBUG(
             `keyserver.jsm: accessHkpInternal.accessKeyServer - onCancel() called\n`
@@ -950,20 +947,16 @@ const accessGnuPG = {
    */
   accessKeyServer(actionFlag, keyserver, keyId, listener) {
     EnigmailLog.DEBUG(
       `keyserver.jsm: accessGnuPG: accessKeyServer(${keyserver})\n`
     );
     throw new Error("Not implemented");
 
     /*
-    if (keyserver === null) {
-      keyserver = EnigmailKeyserverURIs.getDefaultKeyServer();
-    }
-
     let proxyHost = EnigmailHttpProxy.getHttpProxy();
 
     switch (actionFlag) {
       case EnigmailConstants.SEARCH_KEY:
         break;
       case EnigmailConstants.DOWNLOAD_KEY:
       case EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT:
         break;
@@ -1216,18 +1209,18 @@ const accessGnuPG = {
       retObj.pubKeys.push(key);
     }
 
     return retObj;
   },
 };
 
 function getAccessType(keyserver) {
-  if (keyserver === null) {
-    keyserver = EnigmailKeyserverURIs.getDefaultKeyServer();
+  if (!keyserver) {
+    throw new Error("getAccessType requires explicit keyserver parameter");
   }
 
   let srv = parseKeyserverUrl(keyserver);
   switch (srv.protocol) {
     case "keybase":
       return accessKeyBase;
     case "ldap":
     case "ldaps":
--- a/mail/extensions/openpgp/content/modules/keyserverUris.jsm
+++ b/mail/extensions/openpgp/content/modules/keyserverUris.jsm
@@ -62,17 +62,26 @@ function buildUriOptionsFor(keyserver) {
     uris.push(buildUriFor("hkp", keyserver));
   }
 
   return uris;
 }
 
 function getDefaultKeyServer() {
   let keyservers = EnigmailPrefs.getPref(KEYSERVER_PREF).split(/\s*[,;]\s*/g);
-  return keyservers[0];
+  let defKs = keyservers[0];
+  // We don't have great code yet to handle multiple results,
+  // or poisoned results. So avoid SKS.
+  // Let's start with verifying keyservers, only, which return only
+  // one result.
+  if (!defKs.startsWith("vks://")) {
+    console.debug("Not using " + defKs + " in getDefaultKeyServer");
+    return null;
+  }
+  return defKs;
 }
 
 function getUserDefinedKeyserverURIs() {
   const keyservers = EnigmailPrefs.getPref(KEYSERVER_PREF).split(/\s*[,;]\s*/g);
   return EnigmailPrefs.getPref(AUTO_KEYSERVER_SELECTION_PREF)
     ? [keyservers[0]]
     : keyservers;
 }
--- a/mail/extensions/openpgp/content/modules/sqliteDb.jsm
+++ b/mail/extensions/openpgp/content/modules/sqliteDb.jsm
@@ -33,26 +33,26 @@ var PgpSqliteDb2 = {
   },
 
   async checkDatabaseStructure() {
     EnigmailLog.DEBUG(`sqliteDb.jsm: PgpSqliteDb2 checkDatabaseStructure()\n`);
     let conn;
     try {
       conn = await this.openDatabase();
       await checkAcceptanceTable(conn);
-      conn.close();
+      await conn.close();
       EnigmailLog.DEBUG(
         `sqliteDb.jsm: PgpSqliteDb2 checkDatabaseStructure - success\n`
       );
     } catch (ex) {
       EnigmailLog.ERROR(
         `sqliteDb.jsm: PgpSqliteDb2 checkDatabaseStructure: ERROR: ${ex}\n`
       );
       if (conn) {
-        conn.close();
+        await conn.close();
       }
     }
   },
 
   async getFingerprintAcceptance(conn, fingerprint, rv) {
     let myConn = false;
 
     try {
@@ -72,17 +72,17 @@ var PgpSqliteDb2 = {
             rv.fingerprintAcceptance = result[0].getResultByName("decision");
           }
         });
     } catch (ex) {
       console.debug(ex);
     }
 
     if (myConn && conn) {
-      conn.close();
+      await conn.close();
     }
   },
 
   async getAcceptance(fingerprint, email, rv) {
     rv.emailDecided = false;
     rv.fingerprintAcceptance = "";
 
     let conn;
@@ -103,70 +103,71 @@ var PgpSqliteDb2 = {
           )
           .then(result => {
             if (result.length) {
               let count = result[0].getResultByName("count(*)");
               rv.emailDecided = count > 0;
             }
           });
       }
-      conn.close();
+      await conn.close();
     } catch (ex) {
       console.debug(ex);
       if (conn) {
-        conn.close();
+        await conn.close();
       }
     }
   },
 
   async updateAcceptance(fingerprint, emailArray, decision) {
     let conn;
     try {
       conn = await this.openDatabase();
 
-      await conn.executeTransaction(async function() {
-        fingerprint = fingerprint.toLowerCase();
+      await conn.execute("begin transaction");
+
+      fingerprint = fingerprint.toLowerCase();
 
-        let delObj = { fpr: fingerprint };
+      let delObj = { fpr: fingerprint };
+      await conn.execute(
+        "delete from acceptance_decision where fpr = :fpr",
+        delObj
+      );
+      await conn.execute(
+        "delete from acceptance_email where fpr = :fpr",
+        delObj
+      );
+
+      if (decision !== "undecided") {
+        let decisionObj = {
+          fpr: fingerprint,
+          decision,
+        };
         await conn.execute(
-          "delete from acceptance_decision where fpr = :fpr",
-          delObj
-        );
-        await conn.execute(
-          "delete from acceptance_email where fpr = :fpr",
-          delObj
+          "insert into acceptance_decision values (:fpr, :decision)",
+          decisionObj
         );
 
-        if (decision !== "undecided") {
-          let decisionObj = {
-            fpr: fingerprint,
-            decision,
-          };
+        let insertObj = {
+          fpr: fingerprint,
+        };
+        for (let email of emailArray) {
+          insertObj.email = email.toLowerCase();
           await conn.execute(
-            "insert into acceptance_decision values (:fpr, :decision)",
-            decisionObj
+            "insert into acceptance_email values (:fpr, :email)",
+            insertObj
           );
-
-          let insertObj = {
-            fpr: fingerprint,
-          };
-          for (let email of emailArray) {
-            insertObj.email = email.toLowerCase();
-            await conn.execute(
-              "insert into acceptance_email values (:fpr, :email)",
-              insertObj
-            );
-          }
         }
-      });
-      conn.close();
+      }
+      await conn.execute("commit transaction");
+      await conn.close();
     } catch (ex) {
       console.debug(ex);
       if (conn) {
-        conn.close();
+        await conn.close();
       }
     }
   },
 };
 
 var EnigmailSqliteDb = {
   /**
    * Provide an sqlite conection object asynchronously, retrying if needed
@@ -189,22 +190,22 @@ var EnigmailSqliteDb = {
 
   async checkDatabaseStructure() {
     EnigmailLog.DEBUG(`sqliteDb.jsm: checkDatabaseStructure()\n`);
     let conn;
     try {
       conn = await this.openDatabase();
       //await checkAutocryptTable(conn);
       await checkWkdTable(conn);
-      conn.close();
+      await conn.close();
       EnigmailLog.DEBUG(`sqliteDb.jsm: checkDatabaseStructure - success\n`);
     } catch (ex) {
       EnigmailLog.ERROR(`sqliteDb.jsm: checkDatabaseStructure: ERROR: ${ex}\n`);
       if (conn) {
-        conn.close();
+        await conn.close();
       }
     }
   },
 };
 
 /**
  * use a promise to open the Enigmail database.
  *
--- a/mail/extensions/openpgp/content/modules/uidHelper.jsm
+++ b/mail/extensions/openpgp/content/modules/uidHelper.jsm
@@ -11,39 +11,55 @@ var EXPORTED_SYMBOLS = ["uidHelper"];
 /* Parse a OpenPGP user ID string and split it into its parts.
  * The expected syntax is:
  *    Name (comment) <email>
  * Each part is allowed to be empty.
  */
 
 var uidHelper = {
   getPartsFromUidStr(uid, resultObj) {
+    resultObj.name = "";
+    resultObj.comment = "";
+    resultObj.email = "";
+
+    if (!uid) {
+      return false;
+    }
+
     // RegExp strategy:
     // Search until the first ( or < character, use that as Name.
     // Then search for the () characters, allow any characters until ).
     // Do the same for <>.
     // No characters are allowed between a closing ) and opening <.
     // All characters after a trailing > are ignored.
-
-    if (!uid) {
-      return false;
-    }
-
     let result = uid.match(/^ *([^(<]*)? *(\([^)]*\))? *(<[^>]*>)?/);
     if (result.length != 4) {
       return false;
     }
 
-    resultObj.name = result[1].trim();
-
-    resultObj.comment = "";
-    if (result[2]) {
-      resultObj.comment = result[2].substring(1, result[2].length - 1).trim();
+    if (result[1]) {
+      resultObj.name = result[1].trim();
     }
 
-    resultObj.email = "";
-    if (result[3]) {
-      resultObj.email = result[3].substring(1, result[3].length - 1).trim();
+    if (result[1] && !result[2] && !result[3]) {
+      // Does the whole name look roughly like an email address?
+      // Domain part after @ must not contain space.
+      // Local part in front of @ must either be quoted (allows space),
+      // or must not contain space.
+      // If that condition is true, then conclude it's probably an
+      // email address that wasn't enclosed in <>.
+      let looksLikeEmail = resultObj.name.match(/^(".+"|[^ ]+)@[^ @]+$/);
+      if (looksLikeEmail) {
+        resultObj.email = resultObj.name;
+        resultObj.name = "";
+      }
+    } else {
+      if (result[2]) {
+        resultObj.comment = result[2].substring(1, result[2].length - 1).trim();
+      }
+      if (result[3]) {
+        resultObj.email = result[3].substring(1, result[3].length - 1).trim();
+      }
     }
 
     return true;
   },
 };
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/strings/composeKeyStatus.dtd
@@ -0,0 +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/. -->
+
+<!--LOCALIZATION NOTE composeKeyStatus.dtd UI for viewing security status when composing a message -->
+
+<!ENTITY composeKeyStatus.introNeedKeys "To send an end-to-end encrypted message, you must obtain and accept a public key for each recipient.">
+<!ENTITY composeKeyStatus.keysHeading "Availability of OpenPGP keys:">
+<!ENTITY composeKeyStatus.title "OpenPGP Message Security">
+<!ENTITY composeKeyStatus.recipient "Recipient">
+<!ENTITY composeKeyStatus.status "Status">
+<!ENTITY composeKeyStatus.openDetails "Manage keys for selected recipient…">
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/strings/composeKeyStatus.properties
@@ -0,0 +1,7 @@
+# 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/.
+
+RecipGood=ok
+RecipMissing=no key available
+RecipNoneAccepted=no accepted key
--- a/mail/extensions/openpgp/content/strings/enigmail.properties
+++ b/mail/extensions/openpgp/content/strings/enigmail.properties
@@ -538,16 +538,17 @@ keyUsageEncrypt=Encrypt
 keyUsageSign=Sign
 keyUsageCertify=Certify
 keyUsageAuthentication=Authentication
 keyDoesNotExpire=Key does not expire
 keyExpired=Key expired on %S
 keyRevoked=Key was revoked
 keyAutoAcceptPersonal=You accept this key for all uses, because it is one of your personal keys. (You have the secret key.)
 keyDoYouAccept=Do you accept this key for verifying digital signatures and for encrypting messages?
+KeyAcceptWarning=Avoid accepting a rogue key. Use a communication channel other than email to verify the fingerprint of your correspondent's key.
 
 # Strings in enigmailGenCardKey.xhtml
 keygen.started=Please wait while the key is being generated ....
 keygen.completed=Key Generated. The new Key ID is: 0x%S
 keygen.keyBackup=The key is backed up as %S
 keygen.passRequired=Please specify a passphrase if you want to create a backup copy of your key outside your SmartCard.
 
 # Strings in enigmailSetCardPin.xhtml
@@ -712,8 +713,11 @@ mimeDecrypt.encryptedPart.concealedData=
 
 #strings in gnupg-key.jsm
 import.secretKeyImportError=An error has occurred in GnuPG while importing secret keys. The import was not successful.
 
 #strings in importSettings.js
 importSettings.errorNoFile=The file you specified is not a regular file!
 importSettings.cancelWhileInProgress=Restoring is in progress. Do you really want to abort the process?
 importSettings.button.abortImport=&Abort process
+
+cannotUseOwnKeyBecause=Unable to send the message, because there is a problem with your personal key. %S
+cannotEncryptBecauseMissing=Unable to send this message with end-to-end encryption, because there are problems with the keys of the following recipients: %S
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/strings/oneRecipientStatus.dtd
@@ -0,0 +1,18 @@
+<!-- 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/. -->
+
+<!--LOCALIZATION NOTE oneRecipientStatus.dtd UI for viewing security status when composing a message -->
+
+<!ENTITY oneRecipientStatus.keysHeading "Available OpenPGP keys:">
+<!ENTITY oneRecipientStatus.title "OpenPGP Message Security">
+<!ENTITY oneRecipientStatus.status "Status">
+<!ENTITY oneRecipientStatus.keyId "Key ID">
+<!ENTITY oneRecipientStatus.createdDate "Created">
+<!ENTITY oneRecipientStatus.expiresDate "Expires">
+<!ENTITY oneRecipientStatus.openDetails "Open details and edit acceptance…">
+<!ENTITY oneRecipientStatus.discover "Discover new or updated key">
+
+<!ENTITY oneRecipientStatus.instruction1 "To send an end-to-end encrypted message to a recipient, you need to obtain their OpenPGP public key and mark it as accepted.">
+
+<!ENTITY oneRecipientStatus.instruction2 "To obtain their public key, import them from email they have sent to you and that includes it. Alternatively, you can try to discover their public key on a directory.">
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/strings/oneRecipientStatus.properties
@@ -0,0 +1,11 @@
+# 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/.
+
+KeyOwn=accepted (personal key)
+KeyVerified=accepted (verified)
+KeyUnverified=accepted (unverifed)
+KeyUndecided=not accepted (undecided)
+KeyRejected=not accepted (rejected)
+
+Intro=Available public keys for %S:
copy from mailnews/extensions/smime/content/msgCompSecurityInfo.js
copy to mail/extensions/openpgp/content/ui/composeKeyStatus.js
--- a/mailnews/extensions/smime/content/msgCompSecurityInfo.js
+++ b/mail/extensions/openpgp/content/ui/composeKeyStatus.js
@@ -1,302 +1,160 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { EnigmailFuncs } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/funcs.jsm"
+);
+var EnigmailKeyRing = ChromeUtils.import(
+  "chrome://openpgp/content/modules/keyRing.jsm"
+).EnigmailKeyRing;
+var { EnigmailWindows } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/windows.jsm"
+);
+var { EnigmailKey } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/key.jsm"
+);
 
 var gListBox;
 var gViewButton;
 var gBundle;
 
-var gCerts;
+var gEmailAddresses = [];
+var gRowToEmail = [];
+var gMapAddressToKeyObjs = null;
+
+function addRecipients(toAddrList, recList) {
+  for (var i = 0; i < recList.length; i++) {
+    try {
+      let entry = EnigmailFuncs.stripEmail(recList[i].replace(/[",]/g, ""));
+      toAddrList.push(entry);
+    } catch (ex) {
+      console.debug(ex);
+    }
+  }
+}
+
+async function setListEntries() {
+  gMapAddressToKeyObjs = await EnigmailKeyRing.getMultValidKeysForMultRecipients(
+    gEmailAddresses
+  );
+  if (!gMapAddressToKeyObjs) {
+    throw new Error("getMultValidKeysForMultRecipients failed");
+  }
+
+  for (let addr of gEmailAddresses) {
+    let emailStatus = null;
 
-function onLoad() {
+    let foundKeys = gMapAddressToKeyObjs.get(addr);
+    if (!foundKeys || !foundKeys.length) {
+      emailStatus = "RecipMissing";
+    } else {
+      for (let keyObj of foundKeys) {
+        if (
+          keyObj.secretAvailable ||
+          keyObj.acceptance == "verified" ||
+          keyObj.acceptance == "unverified"
+        ) {
+          emailStatus = "RecipGood";
+          break;
+        }
+      }
+      if (!emailStatus) {
+        emailStatus = "RecipNoneAccepted";
+      }
+    }
+
+    let listitem = document.createXULElement("richlistitem");
+
+    let emailItem = document.createXULElement("label");
+    emailItem.setAttribute("value", addr);
+    emailItem.setAttribute("crop", "end");
+    emailItem.setAttribute("style", "width: var(--recipientWidth)");
+    listitem.appendChild(emailItem);
+
+    let status = document.createXULElement("label");
+    status.setAttribute("value", gBundle.getString(emailStatus));
+    status.setAttribute("crop", "end");
+    status.setAttribute("style", "width: var(--statusWidth)");
+    listitem.appendChild(status);
+
+    gListBox.appendChild(listitem);
+
+    gRowToEmail.push(addr);
+  }
+}
+
+async function onLoad() {
   let params = window.arguments[0];
   if (!params) {
     return;
   }
 
-  let helper = Cc[
-    "@mozilla.org/messenger-smime/smimejshelper;1"
-  ].createInstance(Ci.nsISMimeJSHelper);
-
   gListBox = document.getElementById("infolist");
-  gViewButton = document.getElementById("viewCertButton");
-  gBundle = document.getElementById("bundle_smime_comp_info");
-
-  let allow_ldap_cert_fetching = params.smFields.requireEncryptMessage;
-
-  let emailAddresses = [];
-  let certIssuedInfos = [];
-  let certExpiresInfos = [];
-  let certs = [];
-  let canEncrypt = false;
+  gViewButton = document.getElementById("detailsButton");
+  gBundle = document.getElementById("bundle_openpgp_comp_info");
 
-  while (true) {
-    try {
-      // Out parameters - must be objects.
-      let outEmailAddresses = {};
-      let outCertIssuedInfos = {};
-      let outCertExpiresInfos = {};
-      let outCerts = {};
-      let outCanEncrypt = {};
-      helper.getRecipientCertsInfo(
-        params.compFields,
-        outEmailAddresses,
-        outCertIssuedInfos,
-        outCertExpiresInfos,
-        outCerts,
-        outCanEncrypt
-      );
-      // Unwrap to the actual values.
-      emailAddresses = outEmailAddresses.value;
-      certIssuedInfos = outCertIssuedInfos.value;
-      certExpiresInfos = outCertExpiresInfos.value;
-      gCerts = certs = outCerts.value;
-      canEncrypt = outCanEncrypt.value;
-    } catch (e) {
-      dump(e);
-      return;
-    }
-
-    if (!allow_ldap_cert_fetching) {
-      break;
-    }
-    allow_ldap_cert_fetching = false;
+  var arrLen = {};
+  var recList;
 
-    let missing = [];
-    for (let i = 0; i < emailAddresses.length; i++) {
-      if (!certs[i]) {
-        missing.push(emailAddresses[i]);
-      }
-    }
-
-    if (missing.length > 0) {
-      var autocompleteLdap = Services.prefs.getBoolPref(
-        "ldap_2.autoComplete.useDirectory"
-      );
-
-      if (autocompleteLdap) {
-        var autocompleteDirectory = null;
-        if (params.currentIdentity.overrideGlobalPref) {
-          autocompleteDirectory = params.currentIdentity.directoryServer;
-        } else {
-          autocompleteDirectory = Services.prefs.getCharPref(
-            "ldap_2.autoComplete.directoryServer"
-          );
-        }
-
-        if (autocompleteDirectory) {
-          window.openDialog(
-            "chrome://messenger-smime/content/certFetchingStatus.xhtml",
-            "",
-            "chrome,resizable=1,modal=1,dialog=1",
-            autocompleteDirectory,
-            missing
-          );
-        }
-      }
-    }
+  if (params.compFields.to) {
+    recList = params.compFields.splitRecipients(
+      params.compFields.to,
+      true,
+      arrLen
+    );
+    addRecipients(gEmailAddresses, recList);
   }
-
-  let signedElement = document.getElementById("signed");
-  let encryptedElement = document.getElementById("encrypted");
-  if (params.smFields.requireEncryptMessage) {
-    if (params.isEncryptionCertAvailable && canEncrypt) {
-      encryptedElement.value = gBundle.getString("StatusYes");
-    } else {
-      encryptedElement.value = gBundle.getString("StatusNotPossible");
-    }
-  } else {
-    encryptedElement.value = gBundle.getString("StatusNo");
+  if (params.compFields.cc) {
+    recList = params.compFields.splitRecipients(
+      params.compFields.cc,
+      true,
+      arrLen
+    );
+    addRecipients(gEmailAddresses, recList);
   }
-
-  if (params.smFields.signMessage) {
-    if (params.isSigningCertAvailable) {
-      signedElement.value = gBundle.getString("StatusYes");
-    } else {
-      signedElement.value = gBundle.getString("StatusNotPossible");
-    }
-  } else {
-    signedElement.value = gBundle.getString("StatusNo");
+  if (params.compFields.bcc) {
+    recList = params.compFields.splitRecipients(
+      params.compFields.bcc,
+      true,
+      arrLen
+    );
+    addRecipients(gEmailAddresses, recList);
   }
 
-  for (let i = 0; i < emailAddresses.length; ++i) {
-    let email = document.createXULElement("label");
-    email.setAttribute("value", emailAddresses[i]);
-    email.setAttribute("crop", "end");
-    email.setAttribute("style", "width: var(--recipientWidth)");
-
-    let listitem = document.createXULElement("richlistitem");
-    listitem.appendChild(email);
-
-    if (!certs[i]) {
-      let notFound = document.createXULElement("label");
-      notFound.setAttribute("value", gBundle.getString("StatusNotFound"));
-      notFound.setAttribute("style", "width: var(--statusWidth)");
-      listitem.appendChild(notFound);
-    } else {
-      let status = document.createXULElement("label");
-      status.setAttribute("value", "?"); // temporary placeholder
-      status.setAttribute("crop", "end");
-      status.setAttribute("style", "width: var(--statusWidth)");
-      listitem.appendChild(status);
-
-      let issued = document.createXULElement("label");
-      issued.setAttribute("value", certIssuedInfos[i]);
-      issued.setAttribute("crop", "end");
-      issued.setAttribute("style", "width: var(--issuedWidth)");
-      listitem.appendChild(issued);
-
-      let expire = document.createXULElement("label");
-      expire.setAttribute("value", certExpiresInfos[i]);
-      expire.setAttribute("crop", "end");
-      expire.setAttribute("style", "width: var(--expireWidth)");
-      listitem.appendChild(expire);
-
-      asyncDetermineUsages(certs[i]).then(results => {
-        let someError = results.some(
-          result => result.errorCode !== PRErrorCodeSuccess
-        );
-        if (!someError) {
-          status.setAttribute("value", gBundle.getString("StatusValid"));
-          return;
-        }
+  await setListEntries();
+}
 
-        // Keep in sync with certViewer.js.
-        const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
-        const SEC_ERROR_EXPIRED_CERTIFICATE = SEC_ERROR_BASE + 11;
-        const SEC_ERROR_REVOKED_CERTIFICATE = SEC_ERROR_BASE + 12;
-        const SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13;
-        const SEC_ERROR_UNTRUSTED_ISSUER = SEC_ERROR_BASE + 20;
-        const SEC_ERROR_UNTRUSTED_CERT = SEC_ERROR_BASE + 21;
-        const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30;
-        const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED =
-          SEC_ERROR_BASE + 176;
-
-        const errorRankings = [
-          {
-            error: SEC_ERROR_REVOKED_CERTIFICATE,
-            bundleString: "StatusRevoked",
-          },
-          { error: SEC_ERROR_UNTRUSTED_CERT, bundleString: "StatusUntrusted" },
-          {
-            error: SEC_ERROR_UNTRUSTED_ISSUER,
-            bundleString: "StatusUntrusted",
-          },
-          {
-            error: SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
-            bundleString: "StatusInvalid",
-          },
-          {
-            error: SEC_ERROR_EXPIRED_CERTIFICATE,
-            bundleString: "StatusExpired",
-          },
-          {
-            error: SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE,
-            bundleString: "StatusExpired",
-          },
-          { error: SEC_ERROR_UNKNOWN_ISSUER, bundleString: "StatusUntrusted" },
-        ];
-
-        let bs = "StatusInvalid";
-        for (let errorRanking of errorRankings) {
-          let errorPresent = results.some(
-            result => result.errorCode == errorRanking.error
-          );
-          if (errorPresent) {
-            bs = errorRanking.bundleString;
-            break;
-          }
-        }
-
-        status.setAttribute("value", gBundle.getString(bs));
-      });
+async function reload() {
+  while (true) {
+    let child = gListBox.firstChild;
+    if (!child) {
+      break;
     }
-
-    gListBox.appendChild(listitem);
+    gListBox.removeChild(child);
   }
+  gRowToEmail = [];
+  setListEntries();
 }
 
-// --- borrowed from pippki.js ---
-const PRErrorCodeSuccess = 0;
-
-const certificateUsageEmailSigner = 0x0010;
-const certificateUsageEmailRecipient = 0x0020;
-
-// A map from the name of a certificate usage to the value of the usage.
-const certificateUsages = {
-  certificateUsageEmailRecipient,
-};
-
-function asyncDetermineUsages(cert) {
-  let promises = [];
-  let now = Date.now() / 1000;
-  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
-    Ci.nsIX509CertDB
-  );
-  Object.keys(certificateUsages).forEach(usageString => {
-    promises.push(
-      new Promise((resolve, reject) => {
-        let usage = certificateUsages[usageString];
-        certdb.asyncVerifyCertAtTime(
-          cert,
-          usage,
-          0,
-          null,
-          now,
-          (aPRErrorCode, aVerifiedChain, aHasEVPolicy) => {
-            resolve({
-              usageString,
-              errorCode: aPRErrorCode,
-              chain: aVerifiedChain,
-            });
-          }
-        );
-      })
-    );
-  });
-  return Promise.all(promises);
-}
-// --- /borrowed from pippki.js ---
-
 function onSelectionChange(event) {
-  gViewButton.disabled = !(
-    gListBox.selectedItems.length == 1 && certForRow(gListBox.selectedIndex)
-  );
+  gViewButton.disabled = !gListBox.selectedItems.length;
 }
 
-function viewCertHelper(parent, cert) {
-  Services.ww.openWindow(
-    parent,
-    "chrome://pippki/content/certViewer.xhtml",
-    "_blank",
-    "centerscreen,chrome,titlebar",
-    cert
+function viewSelectedEmail() {
+  if (gViewButton.disabled) {
+    return;
+  }
+  let email = gRowToEmail[gListBox.selectedIndex];
+  window.openDialog(
+    "chrome://openpgp/content/ui/oneRecipientStatus.xhtml",
+    "",
+    "chrome,modal,resizable,centerscreen",
+    {
+      email,
+      keys: gMapAddressToKeyObjs.get(email),
+    }
   );
-}
-
-function certForRow(aRowIndex) {
-  return gCerts[aRowIndex];
+  reload();
 }
-
-function viewSelectedCert() {
-  if (!gViewButton.disabled) {
-    viewCertHelper(window, certForRow(gListBox.selectedIndex));
-  }
-}
-
-/* globals openHelp */
-// Suite only.
-function doHelpButton() {
-  openHelp(
-    "compose_security",
-    "chrome://communicator/locale/help/suitehelp.rdf"
-  );
-}
-
-function createCell(label) {
-  var cell = document.createXULElement("listcell");
-  cell.setAttribute("label", label);
-  return cell;
-}
copy from mailnews/extensions/smime/content/msgCompSecurityInfo.xhtml
copy to mail/extensions/openpgp/content/ui/composeKeyStatus.xhtml
--- a/mailnews/extensions/smime/content/msgCompSecurityInfo.xhtml
+++ b/mail/extensions/openpgp/content/ui/composeKeyStatus.xhtml
@@ -1,68 +1,52 @@
 <?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
-<?xml-stylesheet href="chrome://messenger/skin/smime/msgCompSecurityInfo.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/openpgp/composeKeyStatus.css" type="text/css"?>
 
-<!DOCTYPE window SYSTEM "chrome://messenger-smime/locale/msgCompSecurityInfo.dtd">
+<!DOCTYPE window SYSTEM "chrome://openpgp/content/strings/composeKeyStatus.dtd">
 
-<window title="&title.label;"
+<window title="&composeKeyStatus.title;"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
-        style="width: 50em;"
+        style="width: 40em; height: 25em"
         persist="width height"
         onload="onLoad();">
-<dialog id="msgCompSecurityInfo"
+<dialog id="composeKeyStatus"
         buttons="accept">
-  <script src="chrome://messenger-smime/content/msgCompSecurityInfo.js"/>
+  <script src="chrome://openpgp/content/ui/composeKeyStatus.js"/>
   <script><![CDATA[
       function resizeColumns() {
         let list = document.getElementById("infolist");
         let cols = list.getElementsByTagName("treecol");
         list.style.setProperty("--recipientWidth", cols[0].getBoundingClientRect().width + "px");
         list.style.setProperty("--statusWidth", cols[1].getBoundingClientRect().width + "px");
-        list.style.setProperty("--issuedWidth", cols[2].getBoundingClientRect().width + "px");
-        list.style.setProperty("--expireWidth", cols[3].getBoundingClientRect().width - 5 + "px");
       }
       addEventListener("load", resizeColumns, { once: true });
       addEventListener("resize", resizeColumns);
   ]]></script>
 
-  <stringbundle id="bundle_smime_comp_info" src="chrome://messenger-smime/locale/msgCompSecurityInfo.properties"/>
+  <stringbundle id="bundle_openpgp_comp_info" src="chrome://openpgp/content/strings/composeKeyStatus.properties"/>
 
-  <description>&subject.plaintextWarning;</description>
-  <separator class="thin"/>
-  <description>&status.heading;</description>
-  <hbox>
-    <vbox>
-      <label value="&status.signed;"/>
-      <label value="&status.encrypted;"/>
-    </vbox>
-    <vbox>
-      <label id="signed"/>
-      <label id="encrypted"/>
-    </vbox>
-  </hbox>
+  <description>&composeKeyStatus.introNeedKeys;</description>
 
   <separator class="thin"/>
-  <label value="&status.certificates;" control="infolist"/>
+  <label value="&composeKeyStatus.keysHeading;" control="infolist"/>
 
   <richlistbox id="infolist"
                class="theme-listbox"
                flex="1"
                onselect="onSelectionChange(event);">
     <treecols>
-      <treecol flex="3" label="&tree.recipient;"/>
-      <treecol flex="1" label="&tree.status;"/>
-      <treecol flex="2" label="&tree.issuedDate;"/>
-      <treecol flex="2" label="&tree.expiresDate;"/>
+      <treecol flex="3" label="&composeKeyStatus.recipient;"/>
+      <treecol flex="2" label="&composeKeyStatus.status;"/>
     </treecols>
   </richlistbox>
   <hbox pack="start">
-    <button id="viewCertButton" disabled="true"
-            label="&view.label;" accesskey="&view.accesskey;"
-            oncommand="viewSelectedCert();"/>
+    <button id="detailsButton" disabled="true"
+            label="&composeKeyStatus.openDetails;"
+            oncommand="viewSelectedEmail();"/>
   </hbox>
 </dialog>
 </window>
--- a/mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
+++ b/mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
@@ -1794,17 +1794,22 @@ Enigmail.msg = {
       .getAttribute("keydata");
     if (!keyDataB64) {
       return;
     }
     let keyData = EnigmailData.decodeBase64(keyDataB64);
     let errorMsgObj = {};
     let preview = EnigmailKey.getKeyListFromKeyBlock(keyData, errorMsgObj);
     if (errorMsgObj.value === "") {
-      this.importKeyDataWithConfirmation(preview, keyData, true);
+      EnigmailKeyRing.importKeyDataWithConfirmation(
+        window,
+        preview,
+        keyData,
+        true
+      );
     } else {
       EnigmailDialog.alert(
         window,
         EnigmailLocale.getString("previewFailed") + "\n" + errorMsgObj.value
       );
     }
   },
 
@@ -1812,35 +1817,35 @@ Enigmail.msg = {
     let keyId = document
       .getElementById("signatureKeyBox")
       .getAttribute("keyid");
     if (!keyId) {
       return;
     }
 
     let defKs = EnigmailKeyserverURIs.getDefaultKeyServer();
-    // We don't have great code yet to handle multiple results,
-    // or poisoned results. So avoid SKS.
-    // Let's start with verifying keyservers, only, which return only
-    // one result.
-    if (!defKs.startsWith("vks://")) {
-      console.debug("Not using " + defKs + " in searchSignatureKey");
+    if (!defKs) {
       return;
     }
 
     let vks = await EnigmailKeyServer.downloadNoImport("0x" + keyId, defKs);
     if ("keyData" in vks) {
       let keyList = EnigmailKey.getKeyListFromKeyBlock(
         vks.keyData,
         {},
         false,
         true,
         false
       );
-      this.importKeyDataWithConfirmation(keyList, vks.keyData, true);
+      EnigmailKeyRing.importKeyDataWithConfirmation(
+        window,
+        keyList,
+        vks.keyData,
+        true
+      );
     } else {
       console.debug("searchKeysOnInternet no data in keys.openpgp.org");
     }
   },
 
   importKeyFromMsgBody(msgData) {
     let beginIndexObj = {};
     let endIndexObj = {};
@@ -1857,79 +1862,30 @@ Enigmail.msg = {
       return;
     }
 
     let keyData = msgData.substring(beginIndexObj.value, endIndexObj.value);
 
     let errorMsgObj = {};
     let preview = EnigmailKey.getKeyListFromKeyBlock(keyData, errorMsgObj);
     if (errorMsgObj.value === "") {
-      this.importKeyDataWithConfirmation(preview, keyData, false);
+      EnigmailKeyRing.importKeyDataWithConfirmation(
+        window,
+        preview,
+        keyData,
+        false
+      );
     } else {
       EnigmailDialog.alert(
         window,
         EnigmailLocale.getString("previewFailed") + "\n" + errorMsgObj.value
       );
     }
   },
 
-  importKeyDataWithConfirmation(preview, keyData, isBinary) {
-    let exitStatus = -1,
-      errorMsgObj = {};
-    if (preview.length > 0) {
-      if (preview.length == 1) {
-        exitStatus = EnigmailDialog.confirmDlg(
-          window,
-          EnigmailLocale.getString("doImportOne", [
-            preview[0].name,
-            preview[0].id,
-          ])
-        );
-      } else {
-        exitStatus = EnigmailDialog.confirmDlg(
-          window,
-          EnigmailLocale.getString("doImportMultiple", [
-            preview
-              .map(function(a) {
-                return "\t" + a.name + " (" + a.id + ")";
-              })
-              .join("\n"),
-          ])
-        );
-      }
-
-      if (exitStatus) {
-        try {
-          exitStatus = EnigmailKeyRing.importKey(
-            window,
-            false,
-            keyData,
-            isBinary,
-            "",
-            errorMsgObj
-          );
-        } catch (ex) {}
-
-        if (exitStatus === 0) {
-          var keyList = preview.map(function(a) {
-            return a.id;
-          });
-          EnigmailDialog.keyImportDlg(window, keyList);
-        } else {
-          EnigmailDialog.alert(
-            window,
-            EnigmailLocale.getString("failKeyImport") + "\n" + errorMsgObj.value
-          );
-        }
-      }
-    } else {
-      EnigmailDialog.alert(window, EnigmailLocale.getString("noKeyFound"));
-    }
-  },
-
   /**
    * Extract the subject from the 1st content line and move it to the subject line
    */
   movePEPsubject() {
     EnigmailLog.DEBUG("enigmailMessengerOverlay.js: movePEPsubject:\n");
 
     let bodyElement = this.getBodyElement();
 
@@ -2900,17 +2856,22 @@ Enigmail.msg = {
           preview = EnigmailKey.getKeyListFromKeyBlock(
             callbackArg.data,
             errorMsgObj
           );
         }
       }
 
       if (errorMsgObj.value === "") {
-        this.importKeyDataWithConfirmation(preview, callbackArg.data, false);
+        EnigmailKeyRing.importKeyDataWithConfirmation(
+          window,
+          preview,
+          callbackArg.data,
+          false
+        );
       } else {
         EnigmailDialog.alert(
           window,
           EnigmailLocale.getString("previewFailed") + "\n" + errorMsgObj.value
         );
       }
       outFile.remove(true);
       return;
@@ -3116,43 +3077,48 @@ Enigmail.msg = {
     if (!foundKeys) {
       console.debug("searchKeysOnInternet no wkd data");
     } else {
       let keyList = EnigmailKey.getKeyListFromKeyBlock(
         foundKeys.keyData,
         {},
         false
       );
-      this.importKeyDataWithConfirmation(keyList, foundKeys.keyData, true);
+      EnigmailKeyRing.importKeyDataWithConfirmation(
+        window,
+        keyList,
+        foundKeys.keyData,
+        true
+      );
     }
 
     if (foundKeys) {
       return;
     }
 
     let defKs = EnigmailKeyserverURIs.getDefaultKeyServer();
-    // We don't have great code yet to handle multiple results,
-    // or poisoned results. So avoid SKS.
-    // Let's start with verifying keyservers, only, which return only
-    // one result.
-    if (!defKs.startsWith("vks://")) {
-      console.debug("Not using " + defKs + " in searchKeysOnInternet");
+    if (!defKs) {
       return;
     }
 
     let vks = await EnigmailKeyServer.downloadNoImport(address, defKs);
     if ("keyData" in vks) {
       let keyList = EnigmailKey.getKeyListFromKeyBlock(
         vks.keyData,
         {},
         false,
         true,
         false
       );
-      this.importKeyDataWithConfirmation(keyList, vks.keyData, true);
+      EnigmailKeyRing.importKeyDataWithConfirmation(
+        window,
+        keyList,
+        vks.keyData,
+        true
+      );
     } else {
       console.debug("searchKeysOnInternet no data in keys.openpgp.org");
     }
   },
 
   importKeyFromKeyserver() {
     var pubKeyId = "0x" + Enigmail.msg.securityInfo.keyId;
     var inputObj = {
--- a/mail/extensions/openpgp/content/ui/enigmailMsgComposeHelper.js
+++ b/mail/extensions/openpgp/content/ui/enigmailMsgComposeHelper.js
@@ -45,85 +45,59 @@ if (!Enigmail) {
   var Enigmail = {};
 }
 
 Enigmail.hlp = {
   /* try to find valid key to passed email addresses (or keys)
    * @return: list of all found key (with leading "0x") or null
    *          details in details parameter
    */
-  validKeysForAllRecipients(emailsOrKeys, details) {
+  async validKeysForAllRecipients(emailsOrKeys, details) {
     EnigmailLog.DEBUG("=====> validKeysForAllRecipients()\n");
     EnigmailLog.DEBUG(
       "enigmailMsgComposeHelper.js: validKeysForAllRecipients(): emailsOrKeys='" +
         emailsOrKeys +
         "'\n"
     );
 
     // use helper to see when we enter and leave this function
-    let resultingArray = this.doValidKeysForAllRecipients(
+    let resultingArray = await this.doValidKeysForAllRecipients(
       emailsOrKeys,
       details
     );
 
     EnigmailLog.DEBUG(
       "enigmailMsgComposeHelper.js: validKeysForAllRecipients(): return '" +
         resultingArray +
         "'\n"
     );
     EnigmailLog.DEBUG("  <=== validKeysForAllRecipients()\n");
     return resultingArray;
   },
 
   // helper for validKeysForAllRecipients()
-  doValidKeysForAllRecipients(emailsOrKeys, details) {
+  async doValidKeysForAllRecipients(emailsOrKeys, details) {
     EnigmailLog.DEBUG(
       "enigmailMsgComposeHelper.js: doValidKeysForAllRecipients(): emailsOrKeys='" +
         emailsOrKeys +
         "'\n"
     );
 
-    // check which keys are accepted
-    let minTrustLevel;
-    let acceptedKeys = EnigmailPrefs.getPref("acceptedKeys");
-    switch (acceptedKeys) {
-      case 0: // accept valid/authenticated keys only
-        minTrustLevel = "f"; // first value for trusted keys
-        break;
-      case 1: // accept all but revoked/disabled/expired keys
-        minTrustLevel = "?"; // value between invalid and unknown keys
-        break;
-      default:
-        EnigmailLog.DEBUG(
-          'enigmailMsgComposeOverlay.js: doValidKeysForAllRecipients(): return null (INVALID VALUE for acceptedKeys: "' +
-            acceptedKeys +
-            '")\n'
-        );
-        return null;
-    }
-
-    EnigmailLog.DEBUG(
-      'enigmailMsgComposeHelper.js: doValidKeysForAllRecipients(): find keys with minTrustLevel="' +
-        minTrustLevel +
-        '"\n'
-    );
-
     let keyMissing;
     let resultingArray = []; // resulting key list (if all valid)
     try {
       // create array of address elements (email or key)
       let addresses = [];
       try {
         addresses = EnigmailFuncs.stripEmail(emailsOrKeys).split(",");
       } catch (ex) {}
 
       // resolve all the email addresses if possible:
-      keyMissing = EnigmailKeyRing.getValidKeysForAllRecipients(
+      keyMissing = await EnigmailKeyRing.getValidKeysForAllRecipients(
         addresses,
-        minTrustLevel,
         details,
         resultingArray
       );
     } catch (ex) {
       EnigmailLog.DEBUG(
         "enigmailMsgComposeHelper.js: doValidKeysForAllRecipients(): return null (exception: " +
           ex.message +
           "\n" +
@@ -140,65 +114,9 @@ Enigmail.hlp = {
     }
     EnigmailLog.DEBUG(
       'enigmailMsgComposeHelper.js: doValidKeysForAllRecipients(): return "' +
         resultingArray +
         '"\n'
     );
     return resultingArray;
   },
-
-  /**
-   * processConflicts
-   * - handle sign/encrypt/pgpMime conflicts if any
-   * - NOTE: conflicts result into disabling the feature (0/never)
-   * Input parameters:
-   *  @encrypt: email would currently get encrypted
-   *  @sign:    email would currently get signed
-   * @return:  false if error occurred or processing was canceled
-   */
-  processConflicts(encrypt, sign) {
-    // process message about whether we still sign/encrypt
-    let msg = "";
-    msg +=
-      "\n- " + EnigmailLocale.getString(encrypt ? "encryptYes" : "encryptNo");
-    msg += "\n- " + EnigmailLocale.getString(sign ? "signYes" : "signNo");
-    if (EnigmailPrefs.getPref("warnOnRulesConflict") == 2) {
-      EnigmailPrefs.setPref("warnOnRulesConflict", 0);
-    }
-    if (
-      !EnigmailDialog.confirmPref(
-        window,
-        EnigmailLocale.getString("rulesConflict", [msg]),
-        "warnOnRulesConflict"
-      )
-    ) {
-      return false;
-    }
-    return true;
-  },
-
-  /**
-   * determine invalid recipients as returned from GnuPG
-   *
-   * @gpgMsg: output from GnuPG
-   *
-   * @return: space separated list of invalid addresses
-   */
-  getInvalidAddress(gpgMsg) {
-    EnigmailLog.DEBUG(
-      'enigmailMsgComposeHelper.js: getInvalidAddress(): gpgMsg="' +
-        gpgMsg +
-        '"\n\n'
-    );
-    var invalidAddr = [];
-    var lines = gpgMsg.split(/[\n\r]+/);
-    for (var i = 0; i < lines.length; i++) {
-      var m = lines[i].match(/^(INV_RECP \d+ )(.*)$/);
-      if (m && m.length == 3) {
-        try {
-          invalidAddr.push(EnigmailFuncs.stripEmail(m[2].toLowerCase()));
-        } catch (ex) {}
-      }
-    }
-    return invalidAddr.join(" ");
-  },
 };
--- a/mail/extensions/openpgp/content/ui/enigmailMsgComposeOverlay.js
+++ b/mail/extensions/openpgp/content/ui/enigmailMsgComposeOverlay.js
@@ -7,17 +7,17 @@
 "use strict";
 
 /*globally available Thunderbird variables/object/functions: */
 /*global gMsgCompose: false, getCurrentIdentity: false, gNotification: false */
 /*global UpdateAttachmentBucket: false, gContentChanged: true */
 /*global AddAttachments: false, AddAttachment: false, ChangeAttachmentBucketVisibility: false, GetResourceFromUri: false */
 /*global Recipients2CompFields: false, Attachments2CompFields: false, DetermineConvertibility: false, gWindowLocked: false */
 /*global CommandUpdate_MsgCompose: false, gSMFields: false, setSecuritySettings: false, getCurrentAccountKey: false */
-/*global Sendlater3Composing: false, gCurrentIdentity: false */
+/*global Sendlater3Composing: false, gCurrentIdentity: false, showMessageComposeSecurityStatus: false */
 /*global gSendEncrypted: true, gOptionalEncryption: true, gSendSigned: true, gSelectedTechnologyIsPGP: true */
 /*global gIsRelatedToEncryptedOriginal: true, gIsRelatedToSignedOriginal: true, gAttachMyPublicPGPKey: true */
 
 /* import-globals-from ../BondOpenPGP.jsm */
 
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var { MailServices } = ChromeUtils.import(
   "resource:///modules/MailServices.jsm"
@@ -90,16 +90,19 @@ var EnigmailMime = ChromeUtils.import(
   "chrome://openpgp/content/modules/mime.jsm"
 ).EnigmailMime;
 var EnigmailMsgRead = ChromeUtils.import(
   "chrome://openpgp/content/modules/msgRead.jsm"
 ).EnigmailMsgRead;
 var EnigmailMimeEncrypt = ChromeUtils.import(
   "chrome://openpgp/content/modules/mimeEncrypt.jsm"
 ).EnigmailMimeEncrypt;
+const { EnigmailCryptoAPI } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/cryptoAPI.jsm"
+);
 var { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm");
 
 // Account encryption policy values:
 // const kEncryptionPolicy_Never = 0;
 // 'IfPossible' was used by ns4.
 // const kEncryptionPolicy_IfPossible = 1;
 var kEncryptionPolicy_Always = 2;
 
@@ -143,17 +146,17 @@ Enigmail.msg = {
   },
 
   keyLookupDone: [],
 
   saveDraftError: 0,
   addrOnChangeTimeout: 250,
   /* timeout when entering something into the address field */
 
-  composeStartup() {
+  async composeStartup() {
     if (!BondOpenPGP.allDependenciesLoaded()) {
       return;
     }
 
     EnigmailLog.DEBUG(
       "enigmailMsgComposeOverlay.js: Enigmail.msg.composeStartup\n"
     );
 
@@ -221,17 +224,17 @@ Enigmail.msg = {
     // possible values to check:
     // - Enigmail.msg.wasEnigmailAddOnInstalled()
     // - Enigmail.msg.wasEnigmailEnabledForIdentity
     // - this.identity.getIntAttribute("mimePreferOpenPGP") > 0
 
     Enigmail.msg.composeOpen();
     //Enigmail.msg.processFinalState();
     Enigmail.msg.updateStatusBar();
-    Enigmail.msg.initialSendFlags();
+    await Enigmail.msg.initialSendFlags();
 
     //Enigmail.msg.setFinalSendMode('final-pgpmimeYes');
   },
 
   // TODO: call this from global compose when options change
   enigmailComposeProcessFinalState() {
     //Enigmail.msg.processFinalState();
     Enigmail.msg.updateStatusBar();
@@ -840,29 +843,29 @@ Enigmail.msg = {
       if (relatedNode.attachment.name.toLowerCase() + ".sig" == findFile) {
         baseAttachment = relatedNode.attachment;
       }
       relatedNode = relatedNode.nextSibling;
     }
     return baseAttachment;
   },
 
-  initialSendFlags() {
+  async initialSendFlags() {
     EnigmailLog.DEBUG(
       "enigmailMsgComposeOverlay.js: Enigmail.msg.initialSendFlags\n"
     );
     this.fireSendFlags();
 
     EnigmailTimer.setTimeout(
-      function() {
+      async function() {
         EnigmailLog.DEBUG(
           "enigmailMsgComposeOverlay: re-determine send flags\n"
         );
         try {
-          this.determineSendFlags();
+          await this.determineSendFlags();
           //this.processFinalState();
           this.updateStatusBar();
         } catch (ex) {
           EnigmailLog.DEBUG(
             "enigmailMsgComposeOverlay: re-determine send flags - ERROR: " +
               ex.toString() +
               "\n"
           );
@@ -1628,17 +1631,17 @@ Enigmail.msg = {
         if (e) e.removeAttribute("collapsed");
         if (s) s.setAttribute("collapsed", "true");
         if (e) e.setAttribute("collapsed", "true");
   },
   */
 
   /* check if encryption is possible (have keys for everyone or not)
    */
-  determineSendFlags() {
+  async determineSendFlags() {
     EnigmailLog.DEBUG(
       "enigmailMsgComposeOverlay.js: Enigmail.msg.focusChange: Enigmail.msg.determineSendFlags\n"
     );
 
     let detailsObj = {};
 
     if (!this.identity) {
       this.identity = getCurrentIdentity();
@@ -1652,31 +1655,39 @@ Enigmail.msg = {
       ].createInstance(Ci.nsIMsgCompFields);
     }
     Recipients2CompFields(compFields);
 
     // disabled, see bug 1625135
     // gMsgCompose.expandMailingLists();
 
     if (Enigmail.msg.isEnigmailEnabledForIdentity()) {
-      // process list of to/cc email addresses
-      // - bcc email addresses are ignored, when processing whether to sign/encrypt
+      // TODO: use a mechanism that hides the bcc recipients in the
+      //       OpenPGG recipient info.
       var toAddrList = [];
       var arrLen = {};
       var recList;
-      if (compFields.to.length > 0) {
+      if (compFields.to) {
         recList = compFields.splitRecipients(compFields.to, true, arrLen);
         this.addRecipients(toAddrList, recList);
       }
-      if (compFields.cc.length > 0) {
+      if (compFields.cc) {
         recList = compFields.splitRecipients(compFields.cc, true, arrLen);
         this.addRecipients(toAddrList, recList);
       }
-
-      Enigmail.hlp.validKeysForAllRecipients(toAddrList.join(", "), detailsObj);
+      if (compFields.bcc) {
+        recList = compFields.splitRecipients(compFields.bcc, true, arrLen);
+        this.addRecipients(toAddrList, recList);
+      }
+
+      // We don't need the returned array of valid keys
+      await Enigmail.hlp.validKeysForAllRecipients(
+        toAddrList.join(", "),
+        detailsObj
+      );
       //this.autoPgpEncryption = (validKeyList !== null);
     }
 
     // process and signal new resulting state
     //this.processFinalState();
     this.updateStatusBar();
 
     return detailsObj;
@@ -1751,96 +1762,16 @@ Enigmail.msg = {
     */
   },
 
   getSenderUserId() {
     let keyId = this.identity.getUnicharAttribute("openpgp_key_id");
     return "0x" + keyId;
   },
 
-  /* process rules and find keys for passed email addresses
-   * This is THE core method to prepare sending encryptes emails.
-   * - it processes the recipient rules (if not disabled)
-   * - it
-   *
-   * @sendFlags:    Longint - all current combined/processed send flags (incl. optSendFlags)
-   * @optSendFlags: Longint - may only be SEND_ALWAYS_TRUST or SEND_ENCRYPT_TO_SELF
-   * @fromAddr:     String - from email
-   * @toAddrList:   Array  - both to and cc receivers
-   * @bccAddrList:  Array  - bcc receivers
-   * @return:       Object:
-   *                - sendFlags (Longint)
-   *                - toAddrStr  comma separated string of unprocessed to/cc emails
-   *                - bccAddrStr comma separated string of unprocessed to/cc emails
-   *                or null (cancel sending the email)
-   */
-  keySelection(
-    enigmailSvc,
-    sendFlags,
-    optSendFlags,
-    fromAddr,
-    toAddrList,
-    bccAddrList
-  ) {
-    EnigmailLog.DEBUG("=====> keySelection()\n");
-    EnigmailLog.DEBUG(
-      "enigmailMsgComposeOverlay.js: Enigmail.msg.keySelection()\n"
-    );
-
-    let toAddrStr = toAddrList.join(", ");
-    let bccAddrStr = bccAddrList.join(", ");
-    let keyMap = {};
-
-    // NOTE: If we only have bcc addresses, we currently do NOT process rules and select keys at all
-    //       This is GOOD because sending keys for bcc addresses makes bcc addresses visible
-    //       (thus compromising the concept of bcc)
-    //       THUS, we disable encryption even though all bcc receivers might want to have it encrypted.
-    if (toAddrStr.length === 0) {
-      EnigmailLog.DEBUG(
-        'enigmailMsgComposeOverlay.js: Enigmail.msg.keySelection(): skip key selection because we neither have "to" nor "cc" addresses\n'
-      );
-
-      //sendFlags |= EnigmailConstants.SEND_PGP_MIME;
-      //sendFlags &= ~EnigmailConstants.SEND_PGP_MIME;
-
-      return {
-        sendFlags,
-        toAddrStr,
-        bccAddrStr,
-        keyMap,
-      };
-    }
-
-    EnigmailLog.DEBUG(
-      'enigmailMsgComposeOverlay.js: Enigmail.msg.keySelection(): toAddrStr="' +
-        toAddrStr +
-        '" bccAddrStr="' +
-        bccAddrStr +
-        '"\n'
-    );
-
-    // REPEAT 1 or 2 times:
-    // NOTE: The only way to call this loop twice is to come to the "continue;" statement below,
-    //       which forces a second iteration (with forceRecipientSettings==true)
-    EnigmailLog.DEBUG(
-      'enigmailMsgComposeOverlay.js: Enigmail.msg.keySelection(): return toAddrStr="' +
-        toAddrStr +
-        '" bccAddrStr="' +
-        bccAddrStr +
-        '"\n'
-    );
-    EnigmailLog.DEBUG("  <=== keySelection()\n");
-    return {
-      sendFlags,
-      toAddrStr,
-      bccAddrStr,
-      keyMap,
-    };
-  },
-
   /**
    * Determine if S/MIME or OpenPGP should be used
    *
    * @param sendFlags: Number - input send flags.
    *
    * @return: Boolean:
    *   1: use OpenPGP
    *   0: use S/MIME
@@ -2491,17 +2422,17 @@ Enigmail.msg = {
     EnigmailLog.DEBUG(
       "enigmailMsgComposeOverlay.js: Enigmail.msg.prepareSecurityInfo: securityInfo = " +
         newSecurityInfo +
         "\n"
     );
     return newSecurityInfo;
   },
 
-  encryptMsg(msgSendType) {
+  async encryptMsg(msgSendType) {
     // msgSendType: value from nsIMsgCompDeliverMode
     EnigmailLog.DEBUG(
       "enigmailMsgComposeOverlay.js: Enigmail.msg.encryptMsg: msgSendType=" +
         msgSendType +
         ", gSendSigned=" +
         gSendSigned +
         ", gSendEncrypted=" +
         gSendEncrypted +
@@ -2544,78 +2475,85 @@ Enigmail.msg = {
     ) {
       // don't attempt to send message if no recipient specified
       var bundle = document.getElementById("bundle_composeMsgs");
       EnigmailDialog.alert(window, bundle.getString("12511"));
       return false;
     }
 
     this.identity = getCurrentIdentity();
+    let senderKeyId = this.identity.getUnicharAttribute("openpgp_key_id");
+
+    let senderKeyUsable = EnigmailEncryption.determineOwnKeyUsability(
+      sendFlags,
+      senderKeyId
+    );
+    if (senderKeyUsable.errorMsg) {
+      let fullAlert = EnigmailLocale.getString("cannotUseOwnKeyBecause", [
+        senderKeyUsable.errorMsg,
+      ]);
+
+      EnigmailDialog.alert(window, fullAlert);
+      return false;
+    }
+
+    if (gSendEncrypted) {
+      let canEncryptDetails = await this.determineSendFlags();
+      if (canEncryptDetails.errArray.length != 0) {
+        let isFirst = true;
+        let allProblems = "";
+        for (let obj of canEncryptDetails.errArray) {
+          if (isFirst) {
+            isFirst = false;
+          } else {
+            allProblems += ", ";
+          }
+          allProblems += obj.addr;
+        }
+
+        let fullAlert = EnigmailLocale.getString(
+          "cannotEncryptBecauseMissing",
+          [allProblems]
+        );
+
+        EnigmailDialog.alert(window, fullAlert);
+        showMessageComposeSecurityStatus();
+        return false;
+      }
+    }
 
     if (gWindowLocked) {
       EnigmailDialog.alert(window, EnigmailLocale.getString("windowLocked"));
       return false;
     }
 
     let newSecurityInfo = this.resetDirty();
     this.dirty = 1;
 
-    let enigmailSvc = EnigmailCore.getService(window);
-    if (!enigmailSvc) {
-      var msg = EnigmailLocale.getString("sendUnencrypted");
-      if (
-        EnigmailCore.getEnigmailService() &&
-        EnigmailCore.getEnigmailService().initializationError
-      ) {
-        msg =
-          EnigmailCore.getEnigmailService().initializationError + "\n\n" + msg;
-      }
-
-      return EnigmailDialog.confirmDlg(
-        window,
-        msg,
-        EnigmailLocale.getString("msgCompose.button.send")
-      );
-    }
-
     try {
       this.modifiedAttach = null;
 
       // fill fromAddr, toAddrList, bcc etc
       let rcpt = this.determineMsgRecipients(sendFlags);
       if (typeof rcpt === "boolean") {
         return rcpt;
       }
       sendFlags = rcpt.sendFlags;
 
       if (this.sendPgpMime) {
         // Use PGP/MIME
         sendFlags |= EnigmailConstants.SEND_PGP_MIME;
       }
 
-      let result = this.keySelection(
-        enigmailSvc,
-        sendFlags, // all current combined/processed send flags (incl. optSendFlags)
-        rcpt.optSendFlags, // may only be SEND_ALWAYS_TRUST or SEND_ENCRYPT_TO_SELF
-        rcpt.fromAddr,
-        rcpt.toAddrList,
-        rcpt.bccAddrList
-      );
-      if (!result) {
-        return false;
-      }
-
-      sendFlags = result.sendFlags;
-      let toAddrStr = result.toAddrStr;
-      let bccAddrStr = result.bccAddrStr;
-      let keyMap = result.keyMap;
+      let toAddrStr = rcpt.toAddrList.join(", ");
+      let bccAddrStr = rcpt.bccAddrList.join(", ");
+      let keyMap = {};
 
       if (gAttachMyPublicPGPKey) {
-        let keyId = this.identity.getUnicharAttribute("openpgp_key_id");
-        this.attachOwnKey(keyId);
+        this.attachOwnKey(senderKeyId);
       }
 
       /*
       if (this.preferPgpOverSmime(sendFlags) === 0) {
         // use S/MIME
         Attachments2CompFields(gMsgCompose.compFields); // update list of attachments
         sendFlags = 0;
         return true;
@@ -2720,28 +2658,17 @@ Enigmail.msg = {
           }
         }
       }
     } catch (ex) {
       EnigmailLog.writeException(
         "enigmailMsgComposeOverlay.js: Enigmail.msg.encryptMsg",
         ex
       );
-      let msg = EnigmailLocale.getString("signFailed");
-      if (
-        EnigmailCore.getEnigmailService() &&
-        EnigmailCore.getEnigmailService().initializationError
-      ) {
-        msg += "\n" + EnigmailCore.getEnigmailService().initializationError;
-      }
-      return EnigmailDialog.confirmDlg(
-        window,
-        msg,
-        EnigmailLocale.getString("msgCompose.button.sendUnencrypted")
-      );
+      return false;
     }
 
     // The encryption process for PGP/MIME messages follows "here". It's
     // called automatically from nsMsgCompose->sendMsg().
     // registration for this is done in core.jsm: startup()
 
     return true;
   },
@@ -3286,17 +3213,20 @@ Enigmail.msg = {
       sendMsgType != Ci.nsIMsgCompDeliverMode.AutoSaveAsDraft
     ) {
       this.sendProcess = true;
       //let bc = document.getElementById("enigmail-bc-sendprocess");
 
       try {
         this.modifyCompFields();
         //bc.setAttribute("disabled", "true");
-        if (!this.encryptMsg(sendMsgType)) {
+
+        const cApi = EnigmailCryptoAPI();
+        let encryptResult = cApi.sync(this.encryptMsg(sendMsgType));
+        if (!encryptResult) {
           this.resetUpdatedFields();
           event.preventDefault();
           event.stopPropagation();
         }
       } catch (ex) {
         console.debug(ex);
       }
       //bc.removeAttribute("disabled");
@@ -3844,54 +3774,52 @@ Enigmail.msg = {
 
   addrOnChangeTimer: null,
 
   addressOnChange() {
     EnigmailLog.DEBUG(
       "enigmailMsgComposeOverlay.js: Enigmail.msg.addressOnChange\n"
     );
     if (!this.addrOnChangeTimer) {
-      var self = this;
-      this.addrOnChangeTimer = EnigmailTimer.setTimeout(function() {
-        self.fireSendFlags();
-        self.addrOnChangeTimer = null;
+      this.addrOnChangeTimer = EnigmailTimer.setTimeout(async () => {
+        await this.fireSendFlags();
+        this.addrOnChangeTimer = null;
       }, Enigmail.msg.addrOnChangeTimeout);
     }
   },
 
-  focusChange() {
+  async focusChange() {
     // call original TB function
     CommandUpdate_MsgCompose();
 
     var focusedWindow = top.document.commandDispatcher.focusedWindow;
 
     // we're just setting focus to where it was before
     if (focusedWindow == Enigmail.msg.lastFocusedWindow) {
       // skip
       return;
     }
 
     Enigmail.msg.lastFocusedWindow = focusedWindow;
 
-    Enigmail.msg.fireSendFlags();
+    await Enigmail.msg.fireSendFlags();
   },
 
   fireSendFlags() {
     try {
       EnigmailLog.DEBUG(
         "enigmailMsgComposeOverlay.js: Enigmail.msg.fireSendFlags\n"
       );
       if (!this.determineSendFlagId) {
-        let self = this;
-        this.determineSendFlagId = EnigmailTimer.setTimeout(function() {
+        this.determineSendFlagId = EnigmailTimer.setTimeout(async () => {
           try {
-            self.determineSendFlags();
-            self.fireSearchKeys();
+            await this.determineSendFlags();
+            this.fireSearchKeys();
           } catch (x) {}
-          self.determineSendFlagId = null;
+          this.determineSendFlagId = null;
         }, 0);
       }
     } catch (ex) {}
   },
 
   /**
    * Merge multiple  Re: Re: into one Re: in message subject
    */
--- a/mail/extensions/openpgp/content/ui/keyDetailsDlg.js
+++ b/mail/extensions/openpgp/content/ui/keyDetailsDlg.js
@@ -5,17 +5,17 @@
  */
 
 /* global EnigmailLog: false, EnigmailLocale: false, EnigmailKey: false, EnigmailKeyRing: false */
 
 // from enigmailCommon.js:
 /* global GetEnigmailSvc: false, EnigAlert: false, EnigConvertGpgToUnicode: false */
 /* global EnigCleanGuiList: false, EnigGetTrustLabel: false, EnigShowPhoto: false, EnigSignKey: false */
 /* global EnigEditKeyExpiry: false, EnigEditKeyTrust: false, EnigChangeKeyPwd: false, EnigRevokeKey: false */
-/* global EnigCreateRevokeCert: false, EnigmailTimer: false */
+/* global EnigCreateRevokeCert: false, EnigmailTimer: false, EnigmailCryptoAPI: false */
 
 // from enigmailKeyManager.js:
 /* global keyMgrAddPhoto: false, EnigmailCompat: false */
 
 "use strict";
 
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
@@ -85,16 +85,17 @@ async function reloadData() {
   EnigCleanGuiList(uidList);
 
   let keyObj = EnigmailKeyRing.getKeyById(gKeyId);
   if (keyObj) {
     let keyIsExpired =
       keyObj.expiryTime && keyObj.expiryTime < Math.floor(Date.now() / 1000);
 
     let acceptanceIntroText = "";
+    let acceptanceWarningText = "";
     if (keyObj.secretAvailable) {
       setLabel("keyType", EnigmailLocale.getString("keyTypePair2"));
       document.getElementById("ownKeyCommands").removeAttribute("hidden");
       acceptanceIntroText = EnigmailLocale.getString("keyAutoAcceptPersonal");
     } else {
       document.getElementById("ownKeyCommands").setAttribute("hidden", "true");
       setLabel("keyType", EnigmailLocale.getString("keyTypePublic"));
 
@@ -103,16 +104,17 @@ async function reloadData() {
         keyObj.keyTrust == "e" ||
         keyIsExpired
       );
       if (isStillValid) {
         document
           .getElementById("acceptanceRadio")
           .setAttribute("hidden", "false");
         acceptanceIntroText = EnigmailLocale.getString("keyDoYouAccept");
+        acceptanceWarningText = EnigmailLocale.getString("KeyAcceptWarning");
         gUpdateAllowed = true;
 
         let acceptanceResult = {};
         await PgpSqliteDb2.getFingerprintAcceptance(
           null,
           keyObj.fpr,
           acceptanceResult
         );
@@ -166,16 +168,17 @@ async function reloadData() {
       acceptanceIntroText = expiryInfo;
     } else if (keyObj.expiry.length === 0) {
       expiryInfo = EnigmailLocale.getString("keyDoesNotExpire");
     } else {
       expiryInfo = keyObj.expiry;
     }
 
     setText("acceptanceIntro", acceptanceIntroText);
+    setText("acceptanceExplanation", acceptanceWarningText);
     setLabel("keyExpiry", expiryInfo);
     if (keyObj.fpr) {
       gFingerprint = keyObj.fpr;
       setLabel("fingerprint", EnigmailKey.formatFpr(keyObj.fpr));
     }
   }
 }
 
@@ -608,24 +611,30 @@ SubkeyListView.prototype = {
     return true;
   },
 
   toggleOpenState(row) {},
 };
 
 function sigHandleDblClick(event) {}
 
-function onAccept() {
+async function onAccept() {
   if (gUpdateAllowed && gAcceptanceRadio.value != gOriginalAcceptance) {
-    PgpSqliteDb2.updateAcceptance(
-      gFingerprint,
-      gAllEmails,
-      gAcceptanceRadio.value
+    enableRefresh();
+
+    const cApi = EnigmailCryptoAPI();
+    cApi.sync(
+      PgpSqliteDb2.updateAcceptance(
+        gFingerprint,
+        gAllEmails,
+        gAcceptanceRadio.value
+      )
     );
   }
   return true;
 }
 
-document.addEventListener("dialogaccept", function(event) {
-  if (!onAccept()) {
+document.addEventListener("dialogaccept", async function(event) {
+  let result = await onAccept();
+  if (!result) {
     event.preventDefault();
   } // Prevent the dialog closing.
 });
--- a/mail/extensions/openpgp/content/ui/keyDetailsDlg.xhtml
+++ b/mail/extensions/openpgp/content/ui/keyDetailsDlg.xhtml
@@ -13,17 +13,17 @@
 %brandDTD;
 <!ENTITY % enigMailDTD SYSTEM "chrome://openpgp/content/strings/enigmail.dtd" >
 %enigMailDTD;
 ]>
 
 <window title="&enigmail.keyDetails.title;"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml"
-        minwidth="450px"
+        minwidth="60em" minheight="30em"
         persist="width height"
         onload="onLoad();">
 <dialog id="enigmailKeyDetailsDlg"
         buttons="accept"
         buttonlabelaccept="&enigmail.cardDetails.closeWindow.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"/>
@@ -85,16 +85,19 @@
       <tab id="signaturesTab"  label="&enigmail.keyDetails.signaturesTab;"/>
       <tab id="structureTab"   label="&enigmail.keyDetails.structureTab;"/>
     </tabs>
 
     <tabpanels flex="1" id="mainTabPanel">
        <!-- Acceptance Tab -->
       <vbox id="acceptancePanel">
         <description id="acceptanceIntro"/>
+        <separator class="thin"/>
+        <description id="acceptanceExplanation"/>
+        <separator class="thin"/>
         <radiogroup id="acceptanceRadio" hidden="true">
           <radio id="acceptRejected" value="rejected"
                  label="&enigmail.acceptance.rejected.label;"/>
 
           <radio id="acceptUndecided" value="undecided"
                  label="&enigmail.acceptance.undecided.label;"/>
 
           <radio id="acceptUnverified" value="unverified"
copy from mailnews/extensions/smime/content/msgCompSecurityInfo.js
copy to mail/extensions/openpgp/content/ui/oneRecipientStatus.js
--- a/mailnews/extensions/smime/content/msgCompSecurityInfo.js
+++ b/mail/extensions/openpgp/content/ui/oneRecipientStatus.js
@@ -1,302 +1,202 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
+var { EnigmailFuncs } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/funcs.jsm"
+);
+var EnigmailKeyRing = ChromeUtils.import(
+  "chrome://openpgp/content/modules/keyRing.jsm"
+).EnigmailKeyRing;
+var { EnigmailWindows } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/windows.jsm"
+);
+var EnigmailWkdLookup = ChromeUtils.import(
+  "chrome://openpgp/content/modules/wkdLookup.jsm"
+).EnigmailWkdLookup;
+var { EnigmailKeyserverURIs } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/keyserverUris.jsm"
+);
+var EnigmailKeyServer = ChromeUtils.import(
+  "chrome://openpgp/content/modules/keyserver.jsm"
+).EnigmailKeyServer;
+var { EnigmailKey } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/key.jsm"
+);
 
 var gListBox;
 var gViewButton;
 var gBundle;
 
-var gCerts;
+var gAddr;
+var gRowToKey = [];
+
+async function setListEntries(keys = null) {
+  let index = 0;
+
+  if (!keys) {
+    keys = await EnigmailKeyRing.getMultValidKeysForOneRecipient(gAddr);
+  }
+
+  for (let keyObj of keys) {
+    let listitem = document.createXULElement("richlistitem");
+
+    let keyId = document.createXULElement("label");
+    keyId.setAttribute("value", "0x" + keyObj.keyId);
+    keyId.setAttribute("crop", "end");
+    keyId.setAttribute("style", "width: var(--keyWidth)");
+    listitem.appendChild(keyId);
 
-function onLoad() {
+    let acceptanceText;
+    if (keyObj.secretAvailable) {
+      acceptanceText = gBundle.getString("KeyOwn");
+    } else {
+      if (!("acceptance" in keyObj)) {
+        throw new Error(
+          "expected getMultValidKeysForMultRecipients to set acceptance"
+        );
+      }
+      let stringId;
+      switch (keyObj.acceptance) {
+        case "rejected":
+          stringId = "KeyRejected";
+          break;
+        case "unverified":
+          stringId = "KeyUnverified";
+          break;
+        case "verified":
+          stringId = "KeyVerified";
+          break;
+        case "undecided":
+          stringId = "KeyUndecided";
+          break;
+        default:
+          throw new Error("unexpected acceptance value: " + keyObj.acceptance);
+      }
+      acceptanceText = gBundle.getString(stringId);
+    }
+
+    let status = document.createXULElement("label");
+    status.setAttribute("value", acceptanceText);
+    status.setAttribute("crop", "end");
+    status.setAttribute("style", "width: var(--statusWidth)");
+    listitem.appendChild(status);
+
+    let issued = document.createXULElement("label");
+    issued.setAttribute("value", keyObj.created);
+    issued.setAttribute("crop", "end");
+    issued.setAttribute("style", "width: var(--issuedWidth)");
+    listitem.appendChild(issued);
+
+    let expire = document.createXULElement("label");
+    expire.setAttribute("value", keyObj.expiry);
+    expire.setAttribute("crop", "end");
+    expire.setAttribute("style", "width: var(--expireWidth)");
+    listitem.appendChild(expire);
+
+    gListBox.appendChild(listitem);
+
+    gRowToKey[index] = keyObj.keyId;
+    index++;
+  }
+}
+
+async function onLoad() {
   let params = window.arguments[0];
   if (!params) {
     return;
   }
 
-  let helper = Cc[
-    "@mozilla.org/messenger-smime/smimejshelper;1"
-  ].createInstance(Ci.nsISMimeJSHelper);
+  gListBox = document.getElementById("infolist");
+  gViewButton = document.getElementById("detailsButton");
+  gBundle = document.getElementById("bundle_one_recip_info");
 
-  gListBox = document.getElementById("infolist");
-  gViewButton = document.getElementById("viewCertButton");
-  gBundle = document.getElementById("bundle_smime_comp_info");
+  gAddr = params.email;
 
-  let allow_ldap_cert_fetching = params.smFields.requireEncryptMessage;
+  document.getElementById("intro").value = gBundle.getFormattedString("Intro", [
+    gAddr,
+  ]);
 
-  let emailAddresses = [];
-  let certIssuedInfos = [];
-  let certExpiresInfos = [];
-  let certs = [];
-  let canEncrypt = false;
+  await setListEntries(params.keys);
+}
 
+async function reload() {
   while (true) {
-    try {
-      // Out parameters - must be objects.
-      let outEmailAddresses = {};
-      let outCertIssuedInfos = {};
-      let outCertExpiresInfos = {};
-      let outCerts = {};
-      let outCanEncrypt = {};
-      helper.getRecipientCertsInfo(
-        params.compFields,
-        outEmailAddresses,
-        outCertIssuedInfos,
-        outCertExpiresInfos,
-        outCerts,
-        outCanEncrypt
-      );
-      // Unwrap to the actual values.
-      emailAddresses = outEmailAddresses.value;
-      certIssuedInfos = outCertIssuedInfos.value;
-      certExpiresInfos = outCertExpiresInfos.value;
-      gCerts = certs = outCerts.value;
-      canEncrypt = outCanEncrypt.value;
-    } catch (e) {
-      dump(e);
-      return;
-    }
-
-    if (!allow_ldap_cert_fetching) {
+    let child = gListBox.firstChild;
+    if (!child) {
       break;
     }
-    allow_ldap_cert_fetching = false;
-
-    let missing = [];
-    for (let i = 0; i < emailAddresses.length; i++) {
-      if (!certs[i]) {
-        missing.push(emailAddresses[i]);
-      }
-    }
+    gListBox.removeChild(child);
+  }
+  gRowToKey = [];
+  setListEntries();
+}
 
-    if (missing.length > 0) {
-      var autocompleteLdap = Services.prefs.getBoolPref(
-        "ldap_2.autoComplete.useDirectory"
-      );
-
-      if (autocompleteLdap) {
-        var autocompleteDirectory = null;
-        if (params.currentIdentity.overrideGlobalPref) {
-          autocompleteDirectory = params.currentIdentity.directoryServer;
-        } else {
-          autocompleteDirectory = Services.prefs.getCharPref(
-            "ldap_2.autoComplete.directoryServer"
-          );
-        }
+function onSelectionChange(event) {
+  let haveSelection = gListBox.selectedItems.length;
+  gViewButton.disabled = !haveSelection;
+}
 
-        if (autocompleteDirectory) {
-          window.openDialog(
-            "chrome://messenger-smime/content/certFetchingStatus.xhtml",
-            "",
-            "chrome,resizable=1,modal=1,dialog=1",
-            autocompleteDirectory,
-            missing
-          );
-        }
-      }
-    }
+function viewSelectedKey() {
+  if (gViewButton.disabled) {
+    return;
   }
+  EnigmailWindows.openKeyDetails(
+    window,
+    gRowToKey[gListBox.selectedIndex],
+    false
+  );
+  reload();
+}
 
-  let signedElement = document.getElementById("signed");
-  let encryptedElement = document.getElementById("encrypted");
-  if (params.smFields.requireEncryptMessage) {
-    if (params.isEncryptionCertAvailable && canEncrypt) {
-      encryptedElement.value = gBundle.getString("StatusYes");
-    } else {
-      encryptedElement.value = gBundle.getString("StatusNotPossible");
-    }
+async function discoverKey() {
+  let foundKeys = null;
+  foundKeys = await EnigmailWkdLookup.downloadKey(gAddr);
+  if (!foundKeys) {
+    console.debug("searchKeysOnInternet no wkd data");
   } else {
-    encryptedElement.value = gBundle.getString("StatusNo");
-  }
-
-  if (params.smFields.signMessage) {
-    if (params.isSigningCertAvailable) {
-      signedElement.value = gBundle.getString("StatusYes");
-    } else {
-      signedElement.value = gBundle.getString("StatusNotPossible");
+    let keyList = EnigmailKey.getKeyListFromKeyBlock(
+      foundKeys.keyData,
+      {},
+      false
+    );
+    let somethingWasImported = EnigmailKeyRing.importKeyDataWithConfirmation(
+      window,
+      keyList,
+      foundKeys.keyData,
+      true
+    );
+    if (somethingWasImported) {
+      reload();
     }
-  } else {
-    signedElement.value = gBundle.getString("StatusNo");
+    return;
   }
 
-  for (let i = 0; i < emailAddresses.length; ++i) {
-    let email = document.createXULElement("label");
-    email.setAttribute("value", emailAddresses[i]);
-    email.setAttribute("crop", "end");
-    email.setAttribute("style", "width: var(--recipientWidth)");
-
-    let listitem = document.createXULElement("richlistitem");
-    listitem.appendChild(email);
-
-    if (!certs[i]) {
-      let notFound = document.createXULElement("label");
-      notFound.setAttribute("value", gBundle.getString("StatusNotFound"));
-      notFound.setAttribute("style", "width: var(--statusWidth)");
-      listitem.appendChild(notFound);
-    } else {
-      let status = document.createXULElement("label");
-      status.setAttribute("value", "?"); // temporary placeholder
-      status.setAttribute("crop", "end");
-      status.setAttribute("style", "width: var(--statusWidth)");
-      listitem.appendChild(status);
-
-      let issued = document.createXULElement("label");
-      issued.setAttribute("value", certIssuedInfos[i]);
-      issued.setAttribute("crop", "end");
-      issued.setAttribute("style", "width: var(--issuedWidth)");
-      listitem.appendChild(issued);
-
-      let expire = document.createXULElement("label");
-      expire.setAttribute("value", certExpiresInfos[i]);
-      expire.setAttribute("crop", "end");
-      expire.setAttribute("style", "width: var(--expireWidth)");
-      listitem.appendChild(expire);
-
-      asyncDetermineUsages(certs[i]).then(results => {
-        let someError = results.some(
-          result => result.errorCode !== PRErrorCodeSuccess
-        );
-        if (!someError) {
-          status.setAttribute("value", gBundle.getString("StatusValid"));
-          return;
-        }
+  let defKs = EnigmailKeyserverURIs.getDefaultKeyServer();
+  if (!defKs) {
+    return;
+  }
 
-        // Keep in sync with certViewer.js.
-        const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE;
-        const SEC_ERROR_EXPIRED_CERTIFICATE = SEC_ERROR_BASE + 11;
-        const SEC_ERROR_REVOKED_CERTIFICATE = SEC_ERROR_BASE + 12;
-        const SEC_ERROR_UNKNOWN_ISSUER = SEC_ERROR_BASE + 13;
-        const SEC_ERROR_UNTRUSTED_ISSUER = SEC_ERROR_BASE + 20;
-        const SEC_ERROR_UNTRUSTED_CERT = SEC_ERROR_BASE + 21;
-        const SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE = SEC_ERROR_BASE + 30;
-        const SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED =
-          SEC_ERROR_BASE + 176;
-
-        const errorRankings = [
-          {
-            error: SEC_ERROR_REVOKED_CERTIFICATE,
-            bundleString: "StatusRevoked",
-          },
-          { error: SEC_ERROR_UNTRUSTED_CERT, bundleString: "StatusUntrusted" },
-          {
-            error: SEC_ERROR_UNTRUSTED_ISSUER,
-            bundleString: "StatusUntrusted",
-          },
-          {
-            error: SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED,
-            bundleString: "StatusInvalid",
-          },
-          {
-            error: SEC_ERROR_EXPIRED_CERTIFICATE,
-            bundleString: "StatusExpired",
-          },
-          {
-            error: SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE,
-            bundleString: "StatusExpired",
-          },
-          { error: SEC_ERROR_UNKNOWN_ISSUER, bundleString: "StatusUntrusted" },
-        ];
-
-        let bs = "StatusInvalid";
-        for (let errorRanking of errorRankings) {
-          let errorPresent = results.some(
-            result => result.errorCode == errorRanking.error
-          );
-          if (errorPresent) {
-            bs = errorRanking.bundleString;
-            break;
-          }
-        }
-
-        status.setAttribute("value", gBundle.getString(bs));
-      });
+  let vks = await EnigmailKeyServer.downloadNoImport(gAddr, defKs);
+  if ("keyData" in vks) {
+    let keyList = EnigmailKey.getKeyListFromKeyBlock(
+      vks.keyData,
+      {},
+      false,
+      true,
+      false
+    );
+    let somethingWasImported = EnigmailKeyRing.importKeyDataWithConfirmation(
+      window,
+      keyList,
+      vks.keyData,
+      true
+    );
+    if (somethingWasImported) {
+      reload();
     }
-
-    gListBox.appendChild(listitem);
+  } else {
+    console.debug("searchKeysOnInternet no data in keys.openpgp.org");
   }
 }
-
-// --- borrowed from pippki.js ---
-const PRErrorCodeSuccess = 0;
-
-const certificateUsageEmailSigner = 0x0010;
-const certificateUsageEmailRecipient = 0x0020;
-
-// A map from the name of a certificate usage to the value of the usage.
-const certificateUsages = {
-  certificateUsageEmailRecipient,
-};
-
-function asyncDetermineUsages(cert) {
-  let promises = [];
-  let now = Date.now() / 1000;
-  let certdb = Cc["@mozilla.org/security/x509certdb;1"].getService(
-    Ci.nsIX509CertDB
-  );
-  Object.keys(certificateUsages).forEach(usageString => {
-    promises.push(
-      new Promise((resolve, reject) => {
-        let usage = certificateUsages[usageString];
-        certdb.asyncVerifyCertAtTime(
-          cert,
-          usage,
-          0,
-          null,
-          now,
-          (aPRErrorCode, aVerifiedChain, aHasEVPolicy) => {
-            resolve({
-              usageString,
-              errorCode: aPRErrorCode,
-              chain: aVerifiedChain,
-            });
-          }
-        );
-      })
-    );
-  });
-  return Promise.all(promises);
-}
-// --- /borrowed from pippki.js ---
-
-function onSelectionChange(event) {
-  gViewButton.disabled = !(
-    gListBox.selectedItems.length == 1 && certForRow(gListBox.selectedIndex)
-  );
-}
-
-function viewCertHelper(parent, cert) {
-  Services.ww.openWindow(
-    parent,
-    "chrome://pippki/content/certViewer.xhtml",
-    "_blank",
-    "centerscreen,chrome,titlebar",
-    cert
-  );
-}
-
-function certForRow(aRowIndex) {
-  return gCerts[aRowIndex];
-}
-
-function viewSelectedCert() {
-  if (!gViewButton.disabled) {
-    viewCertHelper(window, certForRow(gListBox.selectedIndex));
-  }
-}
-
-/* globals openHelp */
-// Suite only.
-function doHelpButton() {
-  openHelp(
-    "compose_security",
-    "chrome://communicator/locale/help/suitehelp.rdf"
-  );
-}
-
-function createCell(label) {
-  var cell = document.createXULElement("listcell");
-  cell.setAttribute("label", label);
-  return cell;
-}
copy from mailnews/extensions/smime/content/msgCompSecurityInfo.xhtml
copy to mail/extensions/openpgp/content/ui/oneRecipientStatus.xhtml
--- a/mailnews/extensions/smime/content/msgCompSecurityInfo.xhtml
+++ b/mail/extensions/openpgp/content/ui/oneRecipientStatus.xhtml
@@ -1,68 +1,60 @@
 <?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <?xml-stylesheet href="chrome://global/skin/global.css" type="text/css"?>
-<?xml-stylesheet href="chrome://messenger/skin/smime/msgCompSecurityInfo.css" type="text/css"?>
+<?xml-stylesheet href="chrome://messenger/skin/openpgp/oneRecipientStatus.css" type="text/css"?>
 
-<!DOCTYPE window SYSTEM "chrome://messenger-smime/locale/msgCompSecurityInfo.dtd">
+<!DOCTYPE window SYSTEM "chrome://openpgp/content/strings/oneRecipientStatus.dtd">
 
-<window title="&title.label;"
+<window title="&oneRecipientStatus.title;"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" xmlns:html="http://www.w3.org/1999/xhtml"
-        style="width: 50em;"
+        style="width: 50em; height: 22em"
         persist="width height"
         onload="onLoad();">
-<dialog id="msgCompSecurityInfo"
+<dialog id="oneRecipientStatus"
         buttons="accept">
-  <script src="chrome://messenger-smime/content/msgCompSecurityInfo.js"/>
+  <script src="chrome://openpgp/content/ui/oneRecipientStatus.js"/>
   <script><![CDATA[
       function resizeColumns() {
         let list = document.getElementById("infolist");
         let cols = list.getElementsByTagName("treecol");
-        list.style.setProperty("--recipientWidth", cols[0].getBoundingClientRect().width + "px");
+        list.style.setProperty("--keyWidth", cols[0].getBoundingClientRect().width + "px");
         list.style.setProperty("--statusWidth", cols[1].getBoundingClientRect().width + "px");
         list.style.setProperty("--issuedWidth", cols[2].getBoundingClientRect().width + "px");
         list.style.setProperty("--expireWidth", cols[3].getBoundingClientRect().width - 5 + "px");
       }
       addEventListener("load", resizeColumns, { once: true });
       addEventListener("resize", resizeColumns);
   ]]></script>
 
-  <stringbundle id="bundle_smime_comp_info" src="chrome://messenger-smime/locale/msgCompSecurityInfo.properties"/>
+  <stringbundle id="bundle_one_recip_info" src="chrome://openpgp/content/strings/oneRecipientStatus.properties"/>
 
-  <description>&subject.plaintextWarning;</description>
+  <description>&oneRecipientStatus.instruction1;</description>
   <separator class="thin"/>
-  <description>&status.heading;</description>
-  <hbox>
-    <vbox>
-      <label value="&status.signed;"/>
-      <label value="&status.encrypted;"/>
-    </vbox>
-    <vbox>
-      <label id="signed"/>
-      <label id="encrypted"/>
-    </vbox>
-  </hbox>
-
+  <description>&oneRecipientStatus.instruction2;</description>
   <separator class="thin"/>
-  <label value="&status.certificates;" control="infolist"/>
+  <label id="intro" control="infolist"/>
 
   <richlistbox id="infolist"
                class="theme-listbox"
                flex="1"
                onselect="onSelectionChange(event);">
     <treecols>
-      <treecol flex="3" label="&tree.recipient;"/>
-      <treecol flex="1" label="&tree.status;"/>
-      <treecol flex="2" label="&tree.issuedDate;"/>
-      <treecol flex="2" label="&tree.expiresDate;"/>
+      <treecol flex="3" label="&oneRecipientStatus.keyId;"/>
+      <treecol flex="4" label="&oneRecipientStatus.status;"/>
+      <treecol flex="1" label="&oneRecipientStatus.createdDate;"/>
+      <treecol flex="1" label="&oneRecipientStatus.expiresDate;"/>
     </treecols>
   </richlistbox>
   <hbox pack="start">
-    <button id="viewCertButton" disabled="true"
-            label="&view.label;" accesskey="&view.accesskey;"
-            oncommand="viewSelectedCert();"/>
+    <button id="detailsButton" disabled="true"
+            label="&oneRecipientStatus.openDetails;"
+            oncommand="viewSelectedKey();"/>
+    <button id="discoverButton"
+            label="&oneRecipientStatus.discover;"
+            oncommand="discoverKey();"/>
   </hbox>
 </dialog>
 </window>
--- a/mail/extensions/openpgp/jar.mn
+++ b/mail/extensions/openpgp/jar.mn
@@ -6,15 +6,19 @@
 openpgp.jar:
 % content openpgp %content/openpgp/
   content/openpgp/BondOpenPGP.jsm              (content/BondOpenPGP.jsm)
   content/openpgp/modules                      (content/modules/*.js*)
   content/openpgp/modules/stdlib               (content/modules/stdlib/*.js*)
   content/openpgp/modules/cryptoAPI            (content/modules/cryptoAPI/*.js*)
   content/openpgp/strings/enigmail.properties  (content/strings/enigmail.properties)
   content/openpgp/strings/enigmail.dtd         (content/strings/enigmail.dtd)
+  content/openpgp/strings/oneRecipientStatus.properties (content/strings/oneRecipientStatus.properties)
+  content/openpgp/strings/oneRecipientStatus.dtd        (content/strings/oneRecipientStatus.dtd)
+  content/openpgp/strings/composeKeyStatus.properties   (content/strings/composeKeyStatus.properties)
+  content/openpgp/strings/composeKeyStatus.dtd          (content/strings/composeKeyStatus.dtd)
   content/openpgp/strings/bond.dtd             (content/strings/bond.dtd)
   content/openpgp/ui                           (content/ui/*.js)
   content/openpgp/ui                           (content/ui/*.xhtml)
   content/openpgp/ui                           (content/ui/*.css)
 
 [localization] @AB_CD@.jar:
   openpgp/content/ui/keyPicker.ftl             (content/ui/keyPicker.ftl)
--- a/mail/installer/allowed-dupes.mn
+++ b/mail/installer/allowed-dupes.mn
@@ -10,16 +10,18 @@ defaults/settings/pinning/pins.json
 defaults/settings/main/example.json
 
 # mail
 chrome/chat/content/chat/conv.html
 chrome/classic/skin/classic/messenger/icons/accounts.svg
 chrome/classic/skin/classic/messenger/icons/addon-install-confirm.svg
 chrome/classic/skin/classic/messenger/icons/new-mail-alert.png
 chrome/classic/skin/classic/messenger/icons/spelling.svg
+chrome/classic/skin/classic/messenger/openpgp/composeKeyStatus.css
+chrome/classic/skin/classic/messenger/openpgp/oneRecipientStatus.css
 chrome/messenger/content/branding/icon48.png
 chrome/messenger/content/branding/icon64.png
 chrome/messenger/content/branding/icon128.png
 chrome/messenger/content/messenger/extension.svg
 chrome/messenger/skin/classic/messenger/messages/simple/Variants/Normal.css
 chrome/messenger/skin/classic/messenger/messages/simple/Incoming/Context.html
 chrome/messenger/skin/classic/messenger/messages/simple/Incoming/NextContext.html
 chrome/messenger/skin/classic/messenger/messages/mail/Incoming/buddy_icon.png
--- a/mail/themes/shared/jar.inc.mn
+++ b/mail/themes/shared/jar.inc.mn
@@ -243,8 +243,10 @@
   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)
   skin/classic/messenger/openpgp/warning-16.png               (../shared/openpgp/warning-16.png)
+  skin/classic/messenger/openpgp/composeKeyStatus.css         (../shared/openpgp/composeKeyStatus.css)
+  skin/classic/messenger/openpgp/oneRecipientStatus.css       (../shared/openpgp/oneRecipientStatus.css)
new file mode 100644
--- /dev/null
+++ b/mail/themes/shared/openpgp/composeKeyStatus.css
@@ -0,0 +1,11 @@
+/* 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");
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+treecolpicker {
+  display: none;
+}
new file mode 100644
--- /dev/null
+++ b/mail/themes/shared/openpgp/oneRecipientStatus.css
@@ -0,0 +1,11 @@
+/* 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");
+
+@namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
+
+treecolpicker {
+  display: none;
+}