Bug 1631247 - Support additional mechanisms to obtain sender's public OpenPGP key. r=PatrickBrunschwig
authorKai Engert <kaie@kuix.de>
Sun, 19 Apr 2020 12:02:26 +0200
changeset 38908 1265c97794fd1ceeccc482e571a6cf3d918507cf
parent 38907 804e09a2b5711c710984702bfca2551ac7d4e24d
child 38909 a432e1a57c2805be8bbd3ab16a641a39bca7a17c
push id401
push userclokep@gmail.com
push dateMon, 01 Jun 2020 20:41:59 +0000
reviewersPatrickBrunschwig
bugs1631247
Bug 1631247 - Support additional mechanisms to obtain sender's public OpenPGP key. r=PatrickBrunschwig Differential Revision: https://phabricator.services.mozilla.com/D71464
mail/base/content/mainPopupSet.inc.xhtml
mail/base/content/msgHdrView.inc.xhtml
mail/base/content/msgOpenPGPKey.inc.xhtml
mail/extensions/openpgp/content/modules/constants.jsm
mail/extensions/openpgp/content/modules/core.jsm
mail/extensions/openpgp/content/modules/keyRing.jsm
mail/extensions/openpgp/content/modules/keyserver.jsm
mail/extensions/openpgp/content/modules/rnp.jsm
mail/extensions/openpgp/content/modules/rnpLib.jsm
mail/extensions/openpgp/content/modules/sqliteDb.jsm
mail/extensions/openpgp/content/modules/uidHelper.jsm
mail/extensions/openpgp/content/modules/wkdLookup.jsm
mail/extensions/openpgp/content/strings/bond.dtd
mail/extensions/openpgp/content/strings/enigmail.properties
mail/extensions/openpgp/content/ui/enigmailKeyImportInfo.js
mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
mail/extensions/openpgp/content/ui/enigmailMsgHdrViewOverlay.js
--- a/mail/base/content/mainPopupSet.inc.xhtml
+++ b/mail/base/content/mainPopupSet.inc.xhtml
@@ -22,16 +22,21 @@
               accesskey="&SendMessageTo.accesskey;"
               oncommand="SendMailToNode(document.popupNode, event)"/>
     <menuitem id="copyEmailAddressItem" label="&CopyEmailAddress.label;"
               accesskey="&CopyEmailAddress.accesskey;"
               oncommand="CopyEmailNewsAddress(document.popupNode)"/>
     <menuitem id="copyNameAndEmailAddressItem" label="&CopyNameAndEmailAddress.label;"
               accesskey="&CopyNameAndEmailAddress.accesskey;"
               oncommand="CopyEmailNewsAddress(document.popupNode, true)"/>
+#ifdef MOZ_OPENPGP
+    <menuseparator/>
+    <menuitem id="searchKeysOpenPGP" label="&searchKeysOpenPGP.label;"
+              oncommand="Enigmail.msg.searchKeysOnInternet(document.popupNode)"/>
+#endif
     <menuseparator/>
     <menuitem id="createFilterFromItem" label="&CreateFilterFrom.label;"
               accesskey="&CreateFilterFrom.accesskey;"
               oncommand="CreateFilter(document.popupNode, gMessageDisplay.displayedMessage)"
               observes="cmd_createFilterFromPopup"/>
   </menupopup>
 
   <menupopup id="copyPopup">
--- a/mail/base/content/msgHdrView.inc.xhtml
+++ b/mail/base/content/msgHdrView.inc.xhtml
@@ -3,26 +3,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
                         <deck id="msgHeaderViewDeck" selectedIndex="0"
                               persist="selectedIndex" flex="1">
 
                           <!-- the message pane consists of 3 'boxes'. Box #2 is the expanded headers view (the default view) -->
                           <vbox id="expandedHeaderView" flex="1">
 
-#ifdef MOZ_OPENPGP
-                            <vbox flex="1" pack="start" hidden="true">
-                              <hbox flex="1" align="center">
-                                <label id="enigmailStatusText" flex="1"
-                                       class="enigmailHeaderValue">...
-                                </label>
-                              </hbox>
-                            </vbox>
-#endif
-
                             <!-- a convenience box for ease of extension overlaying -->
                             <vbox id="expandedHeadersBox" flex="1">
 
                               <!-- This hbox has display:block set to imbue it with the HTML layout
                                    model. This lets us float the message header toolbar to the right
                                    so we don't waste space when the From field and the toolbar could
                                    fit alongside each other. -->
                               <hbox id="expandedHeadersTopBox">
--- a/mail/base/content/msgOpenPGPKey.inc.xhtml
+++ b/mail/base/content/msgOpenPGPKey.inc.xhtml
@@ -3,9 +3,14 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
                         <hbox id="openpgpKeyBox" hidden="true">
                           <label id="hasKeyOpenPGP" value="&enigmail.hasSenderKey.label;"/>
                            <toolbarbutton id="openpgpImportButton"
                                          label="&enigmail.importSenderKey.label;"
                                          oncommand="Enigmail.msg.importAutocryptKeydata();"/>
                         </hbox>
-
+                        <hbox id="signatureKeyBox" hidden="true">
+                          <label id="missingSignatureKey" value="&enigmail.missingSignatureKey.label;"/>
+                          <toolbarbutton id="searchSignatureKeyButton"
+                                         label="&enigmail.searchSignatureKey.label;"
+                                         oncommand="Enigmail.msg.searchSignatureKey();"/>
+                        </hbox>
--- a/mail/extensions/openpgp/content/modules/constants.jsm
+++ b/mail/extensions/openpgp/content/modules/constants.jsm
@@ -122,16 +122,17 @@ var EnigmailConstants = {
   /* Keyserver Action Flags */
   SEARCH_KEY: 1,
   DOWNLOAD_KEY: 2,
   UPLOAD_KEY: 3,
   REFRESH_KEY: 4,
   GET_SKS_CACERT: 5,
   UPLOAD_WKD: 6,
   GET_CONFIRMATION_LINK: 7,
+  DOWNLOAD_KEY_NO_IMPORT: 8,
 
   /* attachment handling */
 
   /* per-recipient rules */
   AC_RULE_PREFIX: "autocrypt://",
 
   CARD_PIN_CHANGE: 1,
   CARD_PIN_UNBLOCK: 2,
--- a/mail/extensions/openpgp/content/modules/core.jsm
+++ b/mail/extensions/openpgp/content/modules/core.jsm
@@ -64,22 +64,20 @@ const getEnigmailApp = EnigmailLazy.load
 //  "enigmail/wksMimeHandler.jsm",
 //  "EnigmailWksMimeHandler"
 //);
 const getEnigmailPgpmimeHander = EnigmailLazy.loader(
   "enigmail/pgpmimeHandler.jsm",
   "EnigmailPgpmimeHander"
 );
 //const getEnigmailOverlays = EnigmailLazy.loader("enigmail/enigmailOverlays.jsm", "EnigmailOverlays");
-/*
 const getEnigmailSqlite = EnigmailLazy.loader(
   "enigmail/sqliteDb.jsm",
   "EnigmailSqliteDb"
 );
-*/
 const getPgpSqlite2 = EnigmailLazy.loader(
   "enigmail/sqliteDb.jsm",
   "PgpSqliteDb2"
 );
 const getOpenPGPMasterpass = EnigmailLazy.loader(
   "enigmail/masterpass.jsm",
   "OpenPGPMasterpass"
 );
@@ -113,17 +111,17 @@ var EnigmailCore = {
     let env = getEnvironment();
     initializeLogDirectory();
     initializeLogging(env);
 
     const logger = getEnigmailLog();
 
     logger.DEBUG("core.jsm: startup()\n");
 
-    //getEnigmailSqlite().checkDatabaseStructure();
+    getEnigmailSqlite().checkDatabaseStructure();
     getPgpSqlite2().checkDatabaseStructure();
     getEnigmailPrefs().startup(reason);
 
     this.factories = [];
 
     function continueStartup(type) {
       logger.DEBUG(`core.jsm: startup.continueStartup(${type})\n`);
 
--- a/mail/extensions/openpgp/content/modules/keyRing.jsm
+++ b/mail/extensions/openpgp/content/modules/keyRing.jsm
@@ -610,17 +610,19 @@ var EnigmailKeyRing = {
         )
       ) {
         errorMsgObj.value = EnigmailLocale.getString("failCancel");
         return -1;
       }
     }
 
     if (limitedUids.length > 0) {
-      throw new Error("importKeyAsync with limitedUids: not implemented");
+      throw new Error(
+        "importKeyAsync with limitedUids: not implemented " + limitedUids
+      );
     }
 
     if (minimizeKey) {
       throw new Error("importKeyAsync with minimizeKey: not implemented");
     }
 
     const cApi = EnigmailCryptoAPI();
     if (isBinary) {
--- a/mail/extensions/openpgp/content/modules/keyserver.jsm
+++ b/mail/extensions/openpgp/content/modules/keyserver.jsm
@@ -167,16 +167,17 @@ const accessHkpInternal = {
         if (keyData.length === 0) {
           return null;
         }
 
         payLoad = "keytext=" + encodeURIComponent(keyData);
         return payLoad;
 
       case EnigmailConstants.DOWNLOAD_KEY:
+      case EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT:
       case EnigmailConstants.SEARCH_KEY:
       case EnigmailConstants.GET_SKS_CACERT:
         return "";
     }
 
     // other actions are not yet implemented
     return null;
   },
@@ -201,17 +202,20 @@ const accessHkpInternal = {
         protocol = "https";
     }
 
     let url = protocol + "://" + keySrv.host + ":" + keySrv.port;
 
     if (actionFlag === EnigmailConstants.UPLOAD_KEY) {
       url += "/pks/add";
       method = "POST";
-    } else if (actionFlag === EnigmailConstants.DOWNLOAD_KEY) {
+    } else if (
+      actionFlag === EnigmailConstants.DOWNLOAD_KEY ||
+      actionFlag === EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT
+    ) {
       if (searchTerm.indexOf("0x") !== 0) {
         searchTerm = "0x" + searchTerm;
       }
       url += "/pks/lookup?search=" + searchTerm + "&op=get&options=mr";
     } else if (actionFlag === EnigmailConstants.SEARCH_KEY) {
       url +=
         "/pks/lookup?search=" +
         escape(searchTerm) +
@@ -298,16 +302,17 @@ const accessHkpInternal = {
             } else if (xmlReq.status >= 400) {
               reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR));
             } else {
               resolve(xmlReq.responseText);
             }
             return;
 
           case EnigmailConstants.DOWNLOAD_KEY:
+          case EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT:
             if (xmlReq.status >= 400 && xmlReq.status < 500) {
               // key not found
               resolve(1);
             } else if (xmlReq.status >= 500) {
               EnigmailLog.DEBUG(
                 "keyserver.jsm: accessHkpInternal: onload: " +
                   xmlReq.responseText +
                   "\n"
@@ -452,29 +457,31 @@ const accessHkpInternal = {
   /**
    * Download keys from a keyserver
    * @param keyIDs:      String  - space-separated list of search terms or key IDs
    * @param keyserver:   String  - keyserver URL (optionally incl. protocol)
    * @param listener:    optional Object implementing the KeySrvListener API (above)
    *
    * @return:   Promise<...>
    */
-  async download(keyIDs, keyserver, listener = null) {
+  async download(autoImport, keyIDs, keyserver, listener = null) {
     EnigmailLog.DEBUG(`keyserver.jsm: accessHkpInternal.download(${keyIDs})\n`);
     let keyIdArr = keyIDs.split(/ +/);
     let retObj = {
       result: 0,
       errorDetails: "",
       keyList: [],
     };
 
     for (let i = 0; i < keyIdArr.length; i++) {
       try {
         let r = await this.accessKeyServer(
-          EnigmailConstants.DOWNLOAD_KEY,
+          autoImport
+            ? EnigmailConstants.DOWNLOAD_KEY
+            : EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT,
           keyserver,
           keyIdArr[i],
           listener
         );
         if (Array.isArray(r)) {
           retObj.keyList = retObj.keyList.concat(r);
         }
       } catch (ex) {
@@ -493,17 +500,17 @@ const accessHkpInternal = {
 
   refresh(keyServer, listener = null) {
     let keyList = EnigmailKeyRing.getAllKeys()
       .keyList.map(keyObj => {
         return "0x" + keyObj.fpr;
       })
       .join(" ");
 
-    return this.download(keyList, keyServer, listener);
+    return this.download(true, keyList, keyServer, listener);
   },
 
   /**
    * Upload keys to a keyserver
    * @param keyIDs: String  - space-separated list of search terms or key IDs
    * @param keyserver:   String  - keyserver URL (optionally incl. protocol)
    * @param listener:    optional Object implementing the KeySrvListener API (above)
    *
@@ -646,17 +653,20 @@ const accessKeyBase = {
    * return the URL and the HTTP access method for a given action
    */
   createRequestUrl(actionFlag, searchTerm) {
     let url = "https://keybase.io/_/api/1.0/user/";
 
     if (actionFlag === EnigmailConstants.UPLOAD_KEY) {
       // not supported
       throw Cr.NS_ERROR_FAILURE;
-    } else if (actionFlag === EnigmailConstants.DOWNLOAD_KEY) {
+    } else if (
+      actionFlag === EnigmailConstants.DOWNLOAD_KEY ||
+      actionFlag === EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT
+    ) {
       if (searchTerm.indexOf("0x") === 0) {
         searchTerm = searchTerm.substr(0, 40);
       }
       url +=
         "lookup.json?key_fingerprint=" +
         escape(searchTerm) +
         "&fields=public_keys";
     } else if (actionFlag === EnigmailConstants.SEARCH_KEY) {
@@ -709,16 +719,17 @@ const accessKeyBase = {
             if (xmlReq.status >= 400) {
               reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR));
             } else {
               resolve(xmlReq.responseText);
             }
             return;
 
           case EnigmailConstants.DOWNLOAD_KEY:
+          case EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT:
             if (xmlReq.status >= 400 && xmlReq.status < 500) {
               // key not found
               resolve([]);
             } else if (xmlReq.status >= 500) {
               EnigmailLog.DEBUG(
                 "keyserver.jsm: onload: " + xmlReq.responseText + "\n"
               );
               reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR));
@@ -797,29 +808,31 @@ const accessKeyBase = {
   /**
    * Download keys from a KeyBase
    * @param keyIDs:      String  - space-separated list of search terms or key IDs
    * @param keyserver:   (not used for keybase)
    * @param listener:    optional Object implementing the KeySrvListener API (above)
    *
    * @return:   Promise<...>
    */
-  async download(keyIDs, keyserver, listener = null) {
+  async download(autoImport, keyIDs, keyserver, listener = null) {
     EnigmailLog.DEBUG(`keyserver.jsm: accessKeyBase: download()\n`);
     let keyIdArr = keyIDs.split(/ +/);
     let retObj = {
       result: 0,
       errorDetails: "",
       keyList: [],
     };
 
     for (let i = 0; i < keyIdArr.length; i++) {
       try {
         let r = await this.accessKeyServer(
-          EnigmailConstants.DOWNLOAD_KEY,
+          autoImport
+            ? EnigmailConstants.DOWNLOAD_KEY
+            : EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT,
           keyIdArr[i],
           listener
         );
         if (r.length > 0) {
           retObj.keyList = retObj.keyList.concat(r);
         }
       } catch (ex) {
         retObj.result = ex.result;
@@ -913,17 +926,17 @@ const accessKeyBase = {
   refresh(keyServer, listener = null) {
     EnigmailLog.DEBUG(`keyserver.jsm: accessKeyBase: refresh()\n`);
     let keyList = EnigmailKeyRing.getAllKeys()
       .keyList.map(keyObj => {
         return "0x" + keyObj.fpr;
       })
       .join(" ");
 
-    return this.download(keyList, keyServer, listener);
+    return this.download(true, keyList, keyServer, listener);
   },
 };
 
 /**
  Object to handle HKP/HKPS and LDAP/LDAPS requests via GnuPG
  */
 const accessGnuPG = {
   /**
@@ -947,16 +960,17 @@ const accessGnuPG = {
     }
 
     let proxyHost = EnigmailHttpProxy.getHttpProxy();
 
     switch (actionFlag) {
       case EnigmailConstants.SEARCH_KEY:
         break;
       case EnigmailConstants.DOWNLOAD_KEY:
+      case EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT:
         break;
       case EnigmailConstants.UPLOAD_KEY:
         break;
     }
     */
   },
 
   parseStatusMsg(execResult) {
@@ -1012,28 +1026,30 @@ const accessGnuPG = {
   /**
    * Download keys from a keyserver
    * @param keyIDs:      String  - space-separated list of search terms or key IDs
    * @param keyserver:   String  - keyserver URL (optionally incl. protocol)
    * @param listener:    optional Object implementing the KeySrvListener API (above)
    *
    * @return:   Promise<...>
    */
-  async download(keyIDs, keyserver, listener = null) {
+  async download(autoImport, keyIDs, keyserver, listener = null) {
     EnigmailLog.DEBUG(`keyserver.jsm: accessGnuPG.download(${keyIDs})\n`);
     let retObj = {
       result: 0,
       errorDetails: "",
       keyList: [],
     };
     let keyIdArr = keyIDs.split(/ +/);
 
     for (let i = 0; i < keyIdArr.length; i++) {
       let r = await this.accessKeyServer(
-        EnigmailConstants.DOWNLOAD_KEY,
+        autoImport
+          ? EnigmailConstants.DOWNLOAD_KEY
+          : EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT,
         keyserver,
         keyIdArr[i],
         listener
       );
 
       let exitValue = this.parseStatusMsg(r);
       if (exitValue) {
         exitValue.keyList = [];
@@ -1060,17 +1076,17 @@ const accessGnuPG = {
 
   refresh(keyServer, listener = null) {
     let keyList = EnigmailKeyRing.getAllKeys()
       .keyList.map(keyObj => {
         return "0x" + keyObj.fpr;
       })
       .join(" ");
 
-    return this.download(keyList, keyServer, listener);
+    return this.download(true, keyList, keyServer, listener);
   },
 
   /**
    * Upload keys to a keyserver
    * @param keyIDs: String  - space-separated list of search terms or key IDs
    * @param keyserver:   String  - keyserver URL (optionally incl. protocol)
    * @param listener:    optional Object implementing the KeySrvListener API (above)
    *
@@ -1259,16 +1275,17 @@ const accessVksServer = {
         payLoad = JSON.stringify({
           token: searchTerms.token,
           addresses: searchTerms.addresses,
           locale: [locale],
         });
         return payLoad;
 
       case EnigmailConstants.DOWNLOAD_KEY:
+      case EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT:
       case EnigmailConstants.SEARCH_KEY:
       case EnigmailConstants.GET_SKS_CACERT:
         return "";
     }
 
     // other actions are not yet implemented
     return null;
   },
@@ -1289,16 +1306,17 @@ const accessVksServer = {
       method = "POST";
       contentType = "application/json";
     } else if (actionFlag === EnigmailConstants.GET_CONFIRMATION_LINK) {
       url += "/vks/v1/request-verify";
       method = "POST";
       contentType = "application/json";
     } else if (
       actionFlag === EnigmailConstants.DOWNLOAD_KEY ||
+      actionFlag === EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT ||
       actionFlag === EnigmailConstants.SEARCH_KEY
     ) {
       if (searchTerm) {
         let lookup = "/vks/";
         if (searchTerm.indexOf("0x") === 0) {
           searchTerm = searchTerm.substr(2);
           if (
             searchTerm.length == 16 &&
@@ -1400,44 +1418,50 @@ const accessVksServer = {
             } else if (xmlReq.status >= 400) {
               reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR));
             } else {
               resolve(xmlReq.responseText);
             }
             return;
 
           case EnigmailConstants.DOWNLOAD_KEY:
+          case EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT:
             if (xmlReq.status >= 400 && xmlReq.status < 500) {
               // key not found
               resolve(1);
             } else if (xmlReq.status >= 500) {
               EnigmailLog.DEBUG(
                 "keyserver.jsm: accessVksServer.onload: " +
                   xmlReq.responseText +
                   "\n"
               );
               reject(createError(EnigmailConstants.KEYSERVER_ERR_SERVER_ERROR));
             } else {
               let errorMsgObj = {},
                 importedKeysObj = {};
-              let r = EnigmailKeyRing.importKey(
-                null,
-                false,
-                xmlReq.responseText,
-                false,
-                "",
-                errorMsgObj,
-                importedKeysObj
-              );
-              if (r === 0) {
-                resolve(importedKeysObj.value);
+              if (actionFlag === EnigmailConstants.DOWNLOAD_KEY) {
+                let r = EnigmailKeyRing.importKey(
+                  null,
+                  false,
+                  xmlReq.responseText,
+                  false,
+                  "",
+                  errorMsgObj,
+                  importedKeysObj
+                );
+                if (r === 0) {
+                  resolve(importedKeysObj.value);
+                } else {
+                  reject(
+                    createError(EnigmailConstants.KEYSERVER_ERR_IMPORT_ERROR)
+                  );
+                }
               } else {
-                reject(
-                  createError(EnigmailConstants.KEYSERVER_ERR_IMPORT_ERROR)
-                );
+                // DOWNLOAD_KEY_NO_IMPORT
+                resolve(xmlReq.responseText);
               }
             }
             return;
         }
         resolve(-1);
       };
 
       xmlReq.onerror = function(e) {
@@ -1487,35 +1511,43 @@ const accessVksServer = {
   /**
    * Download keys from a keyserver
    * @param keyIDs:      String  - space-separated list of search terms or key IDs
    * @param keyserver:   String  - keyserver URL (optionally incl. protocol)
    * @param listener:    optional Object implementing the KeySrvListener API (above)
    *
    * @return:   Promise<...>
    */
-  async download(keyIDs, keyserver, listener = null) {
+  async download(autoImport, keyIDs, keyserver, listener = null) {
     EnigmailLog.DEBUG(`keyserver.jsm: accessVksServer.download(${keyIDs})\n`);
     let keyIdArr = keyIDs.split(/ +/);
     let retObj = {
       result: 0,
       errorDetails: "",
       keyList: [],
     };
 
     for (let i = 0; i < keyIdArr.length; i++) {
       try {
         let r = await this.accessKeyServer(
-          EnigmailConstants.DOWNLOAD_KEY,
+          autoImport
+            ? EnigmailConstants.DOWNLOAD_KEY
+            : EnigmailConstants.DOWNLOAD_KEY_NO_IMPORT,
           keyserver,
           keyIdArr[i],
           listener
         );
-        if (Array.isArray(r)) {
-          retObj.keyList = retObj.keyList.concat(r);
+        if (autoImport) {
+          if (Array.isArray(r)) {
+            retObj.keyList = retObj.keyList.concat(r);
+          }
+        } else if (typeof r == "string") {
+          retObj.keyData = r;
+        } else {
+          retObj.result = r;
         }
       } catch (ex) {
         retObj.result = ex.result;
         retObj.errorDetails = ex.errorDetails;
         throw retObj;
       }
 
       if (listener && "onProgress" in listener) {
@@ -1528,17 +1560,17 @@ const accessVksServer = {
 
   refresh(keyServer, listener = null) {
     let keyList = EnigmailKeyRing.getAllKeys()
       .keyList.map(keyObj => {
         return "0x" + keyObj.fpr;
       })
       .join(" ");
 
-    return this.download(keyList, keyServer, listener);
+    return this.download(true, keyList, keyServer, listener);
   },
 
   async requestConfirmationLink(keyserver, jsonFragment) {
     EnigmailLog.DEBUG(
       `keyserver.jsm: accessVksServer.requestConfirmationLink()\n`
     );
 
     let response = JSON.parse(jsonFragment);
@@ -1704,17 +1736,22 @@ var EnigmailKeyServer = {
    * @param listener:    optional Object implementing the KeySrvListener API (above)
    *
    * @return:   Promise<Object>
    *     Object: - result: Number           - result Code (0 = OK),
    *             - keyList: Array of String - imported key FPR
    */
   download(keyIDs, keyserver = null, listener) {
     let acc = getAccessType(keyserver);
-    return acc.download(keyIDs, keyserver, listener);
+    return acc.download(true, keyIDs, keyserver, listener);
+  },
+
+  downloadNoImport(keyIDs, keyserver = null, listener) {
+    let acc = getAccessType(keyserver);
+    return acc.download(false, keyIDs, keyserver, listener);
   },
 
   /**
    * Upload keys to a keyserver
    * @param keyIDs:      String  - space-separated list of key IDs or FPR
    * @param keyserver:   String  - keyserver URL (optionally incl. protocol)
    * @param listener:    optional Object implementing the KeySrvListener API (above)
    *
--- a/mail/extensions/openpgp/content/modules/rnp.jsm
+++ b/mail/extensions/openpgp/content/modules/rnp.jsm
@@ -377,20 +377,25 @@ var RNP = {
         }
 
         RNPLib.rnp_uid_handle_destroy(uid_handle);
       }
 
       if (!keyObj.userId) {
         let prim_uid_str = new ctypes.char.ptr();
         if (RNPLib.rnp_key_get_primary_uid(handle, prim_uid_str.address())) {
-          throw new Error("rnp_key_get_primary_uid failed");
+          // Seen with some stripped keys from keys.openpgp.org
+          // if an essential key is distributed, but the owner didn't
+          // agree to ship their user id.
+          keyObj.userId = "?";
+          console.debug("rnp_key_get_primary_uid failed");
+        } else {
+          keyObj.userId = prim_uid_str.readString();
+          RNPLib.rnp_buffer_destroy(prim_uid_str);
         }
-        keyObj.userId = prim_uid_str.readString();
-        RNPLib.rnp_buffer_destroy(prim_uid_str);
       }
 
       if (RNPLib.rnp_key_get_subkey_count(handle, sub_count.address())) {
         throw new Error("rnp_key_get_subkey_count failed");
       }
       for (let i = 0; i < sub_count.value; i++) {
         let sub_handle = new RNPLib.rnp_key_handle_t();
         if (RNPLib.rnp_key_get_subkey_at(handle, i, sub_handle.address())) {
@@ -457,34 +462,35 @@ var RNP = {
           }
           let userIdStr = uid_str.readString();
           RNPLib.rnp_buffer_destroy(uid_str);
 
           if (userIdStr !== RNP_PHOTO_USERID_ID) {
             let id = outputIndex;
             ++outputIndex;
 
-            rList[id] = {};
-            rList[id].created = mainKeyObj.created;
-            rList[id].fpr = mainKeyObj.fpr;
-            rList[id].keyId = mainKeyObj.keyId;
+            let subList = {};
 
-            rList[id].userId = userIdStr;
-            rList[id].sigList = [];
+            subList = {};
+            subList.created = mainKeyObj.created;
+            subList.fpr = mainKeyObj.fpr;
+            subList.keyId = mainKeyObj.keyId;
+
+            subList.userId = userIdStr;
+            subList.sigList = [];
 
             let sig_count = new ctypes.size_t();
             if (
               RNPLib.rnp_uid_get_signature_count(
                 uid_handle,
                 sig_count.address()
               )
             ) {
               throw new Error("rnp_uid_get_signature_count failed");
             }
-
             for (let j = 0; j < sig_count.value; j++) {
               let sigObj = {};
 
               let sig_handle = new RNPLib.rnp_signature_handle_t();
               if (
                 RNPLib.rnp_uid_get_signature_at(
                   uid_handle,
                   j,
@@ -531,36 +537,37 @@ var RNP = {
               ) {
                 throw new Error("rnp_signature_get_signer failed");
               }
 
               if (signerHandle.isNull()) {
                 if (!ignoreUnknownUid) {
                   sigObj.userId = "?";
                   sigObj.sigKnown = false;
-                  rList[id].sigList.push(sigObj);
+                  subList.sigList.push(sigObj);
                 }
               } else {
                 let signer_uid_str = new ctypes.char.ptr();
                 if (
                   RNPLib.rnp_key_get_primary_uid(
                     signerHandle,
                     signer_uid_str.address()
                   )
                 ) {
                   throw new Error("rnp_key_get_uid_at failed");
                 }
                 sigObj.userId = signer_uid_str.readString();
                 RNPLib.rnp_buffer_destroy(signer_uid_str);
                 sigObj.sigKnown = true;
-                rList[id].sigList.push(sigObj);
+                subList.sigList.push(sigObj);
                 RNPLib.rnp_key_handle_destroy(signerHandle);
               }
               RNPLib.rnp_signature_handle_destroy(sig_handle);
             }
+            rList[id] = subList;
           }
         }
 
         RNPLib.rnp_uid_handle_destroy(uid_handle);
       }
     } catch (ex) {
       console.log(ex);
     } finally {
@@ -664,32 +671,45 @@ var RNP = {
       return false;
     }
 
     let sig = new RNPLib.rnp_op_verify_signature_t();
     if (RNPLib.rnp_op_verify_get_signature_at(verify_op, 0, sig.address())) {
       throw new Error("rnp_op_verify_get_signature_at failed");
     }
 
+    let sig_handle = new RNPLib.rnp_signature_handle_t();
+    if (RNPLib.rnp_op_verify_signature_get_handle(sig, sig_handle.address())) {
+      throw new Error("rnp_op_verify_signature_get_handle failed");
+    }
+
+    let sig_id_str = new ctypes.char.ptr();
+    if (RNPLib.rnp_signature_get_keyid(sig_handle, sig_id_str.address())) {
+      throw new Error("rnp_signature_get_keyid failed");
+    }
+    result.keyId = sig_id_str.readString();
+    RNPLib.rnp_buffer_destroy(sig_id_str);
+    RNPLib.rnp_signature_handle_destroy(sig_handle);
+
     let sig_status = RNPLib.rnp_op_verify_signature_get_status(sig);
-
     if (sig_status != RNPLib.RNP_SUCCESS && !result.exitCode) {
       /* Don't allow a good exit code. Keep existing bad code. */
       result.exitCode = -1;
     }
 
     let query_times = true;
     let query_signer = true;
 
     switch (sig_status) {
       case RNPLib.RNP_SUCCESS:
         result.statusFlags |= EnigmailConstants.GOOD_SIGNATURE;
         break;
       case RNPLib.RNP_ERROR_KEY_NOT_FOUND:
-        result.statusFlags |= EnigmailConstants.UNCERTAIN_SIGNATURE;
+        result.statusFlags |=
+          EnigmailConstants.UNCERTAIN_SIGNATURE | EnigmailConstants.NO_PUBKEY;
         query_signer = false;
         break;
       case RNPLib.RNP_ERROR_SIGNATURE_EXPIRED:
         result.statusFlags |= EnigmailConstants.EXPIRED_SIGNATURE;
         break;
       case RNPLib.RNP_ERROR_SIGNATURE_INVALID:
         result.statusFlags |= EnigmailConstants.BAD_SIGNATURE;
         break;
@@ -722,18 +742,16 @@ var RNP = {
       }
 
       let keyInfo = {};
       let ok = this.getKeyInfoFromHandle(ffi, key, keyInfo, true, false);
       if (!ok) {
         throw new Error("getKeyInfoFromHandle failed");
       }
 
-      result.keyId = keyInfo.keyId;
-
       let fromMatchesAnyUid = false;
       let fromLower = fromAddr ? fromAddr.toLowerCase() : "";
 
       for (let uid of keyInfo.userIds) {
         if (uid.type !== "uid") {
           continue;
         }
         let split = {};
@@ -1018,27 +1036,34 @@ var RNP = {
 
   importToFFI(ffi, keyBlockStr, usePublic, useSecret) {
     let input_from_memory = new RNPLib.rnp_input_t();
 
     if (!keyBlockStr) {
       throw new Error("no keyBlockStr parameter in importToFFI");
     }
 
+    if (typeof keyBlockStr != "string") {
+      throw new Error(
+        "keyBlockStr of unepected type importToFFI: %o",
+        keyBlockStr
+      );
+    }
+
     let arr = [];
     for (let i = 0; i < keyBlockStr.length; i++) {
       arr[i] = keyBlockStr.charCodeAt(i);
     }
     var key_array = ctypes.uint8_t.array()(arr);
 
     if (
       RNPLib.rnp_input_from_memory(
         input_from_memory.address(),
         key_array,
-        keyBlockStr.length,
+        key_array.length,
         false
       )
     ) {
       throw new Error("rnp_input_from_memory failed");
     }
 
     let jsonInfo = new ctypes.char.ptr();
 
--- a/mail/extensions/openpgp/content/modules/rnpLib.jsm
+++ b/mail/extensions/openpgp/content/modules/rnpLib.jsm
@@ -986,16 +986,24 @@ function enableRNPLibJS() {
       "rnp_op_verify_get_signature_at",
       abi,
       rnp_result_t,
       rnp_op_verify_t,
       ctypes.size_t,
       rnp_op_verify_signature_t.ptr
     ),
 
+    rnp_op_verify_signature_get_handle: librnp.declare(
+      "rnp_op_verify_signature_get_handle",
+      abi,
+      rnp_result_t,
+      rnp_op_verify_signature_t,
+      rnp_signature_handle_t.ptr
+    ),
+
     rnp_op_verify_signature_get_status: librnp.declare(
       "rnp_op_verify_signature_get_status",
       abi,
       rnp_result_t,
       rnp_op_verify_signature_t
     ),
 
     rnp_op_verify_signature_get_key: librnp.declare(
--- a/mail/extensions/openpgp/content/modules/sqliteDb.jsm
+++ b/mail/extensions/openpgp/content/modules/sqliteDb.jsm
@@ -170,41 +170,43 @@ var PgpSqliteDb2 = {
 var EnigmailSqliteDb = {
   /**
    * Provide an sqlite conection object asynchronously, retrying if needed
    *
    * @return {Promise<Sqlite Connection>}: the Sqlite database object
    */
 
   openDatabase() {
-    /*
     EnigmailLog.DEBUG("sqliteDb.jsm: openDatabase()\n");
     return new Promise((resolve, reject) => {
-      openDatabaseConn("enigmail.sqlite", resolve, reject, 100, Date.now() + 10000);
+      openDatabaseConn(
+        "enigmail.sqlite",
+        resolve,
+        reject,
+        100,
+        Date.now() + 10000
+      );
     });
-    */
   },
 
   async checkDatabaseStructure() {
-    /*
     EnigmailLog.DEBUG(`sqliteDb.jsm: checkDatabaseStructure()\n`);
     let conn;
     try {
       conn = await this.openDatabase();
-      await checkAutocryptTable(conn);
+      //await checkAutocryptTable(conn);
       await checkWkdTable(conn);
       conn.close();
       EnigmailLog.DEBUG(`sqliteDb.jsm: checkDatabaseStructure - success\n`);
     } catch (ex) {
       EnigmailLog.ERROR(`sqliteDb.jsm: checkDatabaseStructure: ERROR: ${ex}\n`);
       if (conn) {
         conn.close();
       }
     }
-    */
   },
 };
 
 /**
  * use a promise to open the Enigmail database.
  *
  * it's possible that there will be an NS_ERROR_STORAGE_BUSY
  * so we're willing to retry for a little while.
@@ -365,43 +367,39 @@ async function createAutocryptTable(conn
 
 /**
  * Ensure that the database has the wkd_lookup_timestamp table.
  *
  * @param connection: Object - SQLite connection
  *
  * @return Promise
  */
-/*
 async function checkWkdTable(connection) {
   EnigmailLog.DEBUG("sqliteDB.jsm: checkWkdTable()\n");
 
   try {
     let exists = await connection.tableExists("wkd_lookup_timestamp");
     EnigmailLog.DEBUG("sqliteDB.jsm: checkWkdTable - success\n");
     if (!exists) {
       await createWkdTable(connection);
     }
   } catch (error) {
     EnigmailLog.DEBUG("sqliteDB.jsm: checkWkdTable - error\n");
     throw error;
   }
 }
-*/
 
 /**
  * Create the "wkd_lookup_timestamp" table.
  *
  * @param connection: Object - SQLite connection
  *
  * @return Promise
  */
-/*
 function createWkdTable(connection) {
   EnigmailLog.DEBUG("sqliteDB.jsm: createWkdTable()\n");
 
   return connection.execute(
     "create table wkd_lookup_timestamp (" +
     "email text not null primary key, " + // email address of correspondent
       "last_seen integer);"
   ); // timestamp of last mail received for the email/type combination
 }
-*/
--- a/mail/extensions/openpgp/content/modules/uidHelper.jsm
+++ b/mail/extensions/openpgp/content/modules/uidHelper.jsm
@@ -18,16 +18,20 @@ var uidHelper = {
   getPartsFromUidStr(uid, resultObj) {
     // 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 = "";
--- a/mail/extensions/openpgp/content/modules/wkdLookup.jsm
+++ b/mail/extensions/openpgp/content/modules/wkdLookup.jsm
@@ -18,16 +18,23 @@ const { EnigmailZBase32 } = ChromeUtils.
   "chrome://openpgp/content/modules/zbase32.jsm"
 );
 const { EnigmailData } = ChromeUtils.import(
   "chrome://openpgp/content/modules/data.jsm"
 );
 const { EnigmailSqliteDb } = ChromeUtils.import(
   "chrome://openpgp/content/modules/sqliteDb.jsm"
 );
+var { EnigmailKey } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/key.jsm"
+);
+var EnigmailKeyRing = ChromeUtils.import(
+  "chrome://openpgp/content/modules/keyRing.jsm"
+).EnigmailKeyRing;
+var { DNS } = ChromeUtils.import("resource:///modules/DNS.jsm");
 
 Cu.importGlobalProperties(["fetch"]);
 
 // Those domains are not expected to have WKD:
 var BLACKLIST_DOMAINS = [
   /* Default domains included */
   "aol.com",
   "att.net",
@@ -268,17 +275,21 @@ var EnigmailWkdLookup = {
                 let gotKeys = [];
                 for (let i = 0; i < dataArr.length; i++) {
                   if (dataArr[i] !== null) {
                     gotKeys.push(dataArr[i]);
                   }
                 }
 
                 if (gotKeys.length > 0) {
-                  importDownloadedKeys(gotKeys);
+                  if (gotKeys.length == 1) {
+                    importOneDownloadedKey(gotKeys[0]);
+                  } else {
+                    importDownloadedKeys(gotKeys);
+                  }
                   resolve(true);
                 } else {
                   resolve(false);
                 }
               }
             });
           } else {
             resolve(false);
@@ -530,50 +541,74 @@ function timeForRecheck(connection, emai
 /**
  * Import downloaded keys
  *
  * @param {Array of String}: ASCII armored or binary string
  *
  * no return value
  */
 function importDownloadedKeys(keysArr) {
-  throw new Error("Not implemented");
-
-  /*
-  EnigmailLog.DEBUG("wkdLookup.jsm: importDownloadedKeys(" + keysArr.length + ")\n");
+  EnigmailLog.DEBUG(
+    "wkdLookup.jsm: importDownloadedKeys(" + keysArr.length + ")\n"
+  );
 
   let keyData = "";
   let domainArr = [];
   for (let k in keysArr) {
     if (keysArr[k]) {
-      if (keysArr[k].keyData.search(/^-----BEGIN PGP PUBLIC KEY BLOCK-----/) < 0) {
+      if (
+        keysArr[k].keyData.search(/^-----BEGIN PGP PUBLIC KEY BLOCK-----/) < 0
+      ) {
         try {
           // TODO: need a MPL version of bytesToArmor
-          keyData += EnigmailOpenPGP.enigmailFuncs.bytesToArmor(EnigmailOpenPGP.armor.public_key, keysArr[k].keyData);
+          throw new Error("WKD importDownloadedKeys dont' have bytesToArmor");
+          /*
+          keyData += EnigmailOpenPGP.enigmailFuncs.bytesToArmor(
+            EnigmailOpenPGP.armor.public_key,
+            keysArr[k].keyData
+          );
+          */
+        } catch (ex) {
+          EnigmailLog.DEBUG(
+            "wkdLookup.jsm: importDownloadedKeys: exeption=" + ex + "\n"
+          );
         }
-        catch (ex) {
-          EnigmailLog.DEBUG("wkdLookup.jsm: importDownloadedKeys: exeption=" + ex + "\n");
-        }
-      }
-      else {
+      } else {
         keyData += keysArr[k].keyData;
       }
 
       domainArr.push(keysArr[k].email.replace(/^.*@/, "@"));
     }
   }
 
   let keyList = EnigmailKey.getKeyListFromKeyBlock(keyData, {}, false);
 
   for (let k in keyList) {
-    EnigmailLog.DEBUG("wkdLookup.jsm: importDownloadedKeys: fpr=" + keyList[k].fpr + "\n");
+    EnigmailLog.DEBUG(
+      "wkdLookup.jsm: importDownloadedKeys: fpr=" + keyList[k].fpr + "\n"
+    );
   }
 
-  EnigmailKeyRing.importKey(null, false, keyData, false, "", {}, {}, false, domainArr);
-  */
+  EnigmailKeyRing.importKey(
+    null,
+    false,
+    keyData,
+    false,
+    "",
+    {},
+    {},
+    false,
+    domainArr
+  );
+}
+
+function importOneDownloadedKey(key) {
+  EnigmailLog.DEBUG("wkdLookup.jsm: importOneDownloadedKey\n");
+  //let keyList = EnigmailKey.getKeyListFromKeyBlock(key.keyData, {}, false);
+  EnigmailKeyRing.importKey(null, true, key.keyData, true, "", {}, {}, false);
 }
 
 /**
  * Get special URLs for specific sites that don't use WKD, but still provide
  * public keys of their users in
  *
  * @param {String}: emailAddr: email address in lowercase
  *
@@ -587,27 +622,25 @@ async function getSiteSpecificUrl(emailA
     case "protonmail.ch":
     case "protonmail.com":
     case "pm.me":
       url =
         "https://api.protonmail.ch/pks/lookup?op=get&options=mr&search=" +
         escape(emailAddr);
       break;
   }
+  if (!url) {
+    let records = await DNS.mx(domain);
+    const mxHosts = records.filter(record => record.host);
+    console.debug(mxHosts);
 
-  if (!url) {
-    throw new Error(
-      "EnigmailWkdLookup getSiteSpecificUrl with DNS not implemented"
-    );
-    /*
-    try {
-      let mxHosts = await EnigmailDns.lookup("MX", domain);
-      if (mxHosts & mxHosts.indexOf("mail.protonmail.ch") >= 0 ||
-        mxHosts.indexOf("mailsec.protonmail.ch") >= 0) {
-        url = "https://api.protonmail.ch/pks/lookup?op=get&options=mr&search=" + escape(emailAddr);
-      }
+    if (
+      mxHosts &&
+      (mxHosts.includes("mail.protonmail.ch") ||
+        mxHosts.includes("mailsec.protonmail.ch"))
+    ) {
+      url =
+        "https://api.protonmail.ch/pks/lookup?op=get&options=mr&search=" +
+        escape(emailAddr);
     }
-    catch (ex) {}
-    */
   }
-
   return url;
 }
--- a/mail/extensions/openpgp/content/strings/bond.dtd
+++ b/mail/extensions/openpgp/content/strings/bond.dtd
@@ -7,8 +7,13 @@
 <!ENTITY enigmail.ctxVerifyAtt.label                "Verify signature">
 <!ENTITY enigmail.ctxDecryptOpen.accesskey          "D">
 <!ENTITY enigmail.ctxDecryptSave.accesskey          "C">
 <!ENTITY enigmail.ctxImportKey.accesskey            "I">
 <!ENTITY enigmail.ctxVerifyAtt.accesskey            "V">
 
 <!ENTITY enigmail.hasSenderKey.label                "This message claims to contain the sender's OpenPGP public key.">
 <!ENTITY enigmail.importSenderKey.label             "Import…">
+
+<!ENTITY searchKeysOpenPGP.label                    "Discover OpenPGP Key">
+
+<!ENTITY enigmail.missingSignatureKey.label         "Message was signed with a key that you don't have yet">
+<!ENTITY enigmail.searchSignatureKey.label          "Discover…">
--- a/mail/extensions/openpgp/content/strings/enigmail.properties
+++ b/mail/extensions/openpgp/content/strings/enigmail.properties
@@ -511,17 +511,17 @@ revokeUidFailed=Revoking the user ID %S 
 revokeUidOK=User ID %S was revoked successfully. If your key is available on a key server, it is recommended to re-upload it, so that others can see the revocation.
 revokeUidQuestion=Do you really want to revoke the user ID %S?
 
 # Strings in enigmailKeyImportInfo.xhtml
 importInfoTitle=SUCCESS! Keys imported
 importInfoBits=Bits
 importInfoCreated=Created
 importInfoFpr=Fingerprint
-importInfoDetails=(Details)
+importInfoDetails2=View Details and manage key acceptance
 importInfoNoKeys=No keys imported.
 
 # Strings in enigmailKeyDetailsDlg.xhtml
 keyTypePublic=public key
 keyTypePrimary=primary key
 keyTypeSubkey=subkey
 keyTypePair2=personal key (secret key and public key)
 keyExpiryNever=never
--- a/mail/extensions/openpgp/content/ui/enigmailKeyImportInfo.js
+++ b/mail/extensions/openpgp/content/ui/enigmailKeyImportInfo.js
@@ -100,17 +100,17 @@ function onLoad() {
 
   EnigmailEvents.dispatchEvent(resizeDlg, 0);
 }
 
 function buildKeyGroupBox(keyObj) {
   let i,
     groupBox = document.createXULElement("vbox"),
     vbox = document.createXULElement("hbox"),
-    caption = document.createXULElement("image"),
+    //caption = document.createXULElement("image"),
     userid = document.createXULElement("label"),
     infoGrid = document.createXULElement("grid"),
     infoColumns = document.createXULElement("columns"),
     infoColId = document.createXULElement("column"),
     infoColDate = document.createXULElement("column"),
     infoRows = document.createXULElement("rows"),
     infoRowHead = document.createXULElement("row"),
     infoRowBody = document.createXULElement("row"),
@@ -126,39 +126,32 @@ function buildKeyGroupBox(keyObj) {
     fprRows = document.createXULElement("rows"),
     fprRow1 = document.createXULElement("row"),
     fprRow2 = document.createXULElement("row");
 
   groupBox.setAttribute("class", "enigmailGroupbox");
   userid.setAttribute("value", keyObj.userId);
   userid.setAttribute("class", "enigmailKeyImportUserId");
   vbox.setAttribute("align", "start");
-  caption.setAttribute("class", "enigmailKeyImportCaption");
+  //caption.setAttribute("class", "enigmailKeyImportCaption");
   infoLabelH1.setAttribute("value", EnigmailLocale.getString("importInfoBits"));
   infoLabelH2.setAttribute(
     "value",
     EnigmailLocale.getString("importInfoCreated")
   );
   infoLabelH3.setAttribute("value", "");
   infoLabelB1.setAttribute("value", keyObj.keySize);
   infoLabelB2.setAttribute("value", keyObj.created);
-  infoLabelB3.setAttribute(
-    "value",
-    EnigmailLocale.getString("importInfoDetails")
-  );
-  infoLabelB3.setAttribute("keyid", keyObj.keyId);
-  infoLabelB3.setAttribute("class", "enigmailKeyImportDetails");
 
   infoRowHead.appendChild(infoLabelH1);
   infoRowHead.appendChild(infoLabelH2);
   infoRowHead.appendChild(infoLabelH3);
   infoRowHead.setAttribute("class", "enigmailKeyImportHeader");
   infoRowBody.appendChild(infoLabelB1);
   infoRowBody.appendChild(infoLabelB2);
-  infoRowBody.appendChild(infoLabelB3);
   infoRows.appendChild(infoRowHead);
   infoRows.appendChild(infoRowBody);
   infoColumns.appendChild(infoColId);
   infoColumns.appendChild(infoColDate);
   infoGrid.appendChild(infoColumns);
   infoGrid.appendChild(infoRows);
 
   fprLabel.setAttribute("value", EnigmailLocale.getString("importInfoFpr"));
@@ -173,23 +166,31 @@ function buildKeyGroupBox(keyObj) {
       fprRow2.appendChild(label);
     }
   }
 
   fprRows.appendChild(fprRow1);
   fprRows.appendChild(fprRow2);
   fprGrid.appendChild(fprColumns);
   fprGrid.appendChild(fprRows);
-  vbox.appendChild(caption);
+  //vbox.appendChild(caption);
   groupBox.appendChild(vbox);
   groupBox.appendChild(userid);
   groupBox.appendChild(infoGrid);
   groupBox.appendChild(fprLabel);
   groupBox.appendChild(fprGrid);
 
+  infoLabelB3.setAttribute(
+    "value",
+    EnigmailLocale.getString("importInfoDetails2")
+  );
+  infoLabelB3.setAttribute("keyid", keyObj.keyId);
+  infoLabelB3.setAttribute("class", "enigmailKeyImportDetails");
+  groupBox.appendChild(infoLabelB3);
+
   return groupBox;
 }
 
 function resizeDlg() {
   var txt = document.getElementById("keyInfo");
   var box = document.getElementById("outerbox");
 
   var deltaWidth = window.outerWidth - box.clientWidth;
--- a/mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
+++ b/mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
@@ -81,16 +81,22 @@ var EnigmailStreams = ChromeUtils.import
   "chrome://openpgp/content/modules/streams.jsm"
 ).EnigmailStreams;
 var EnigmailEvents = ChromeUtils.import(
   "chrome://openpgp/content/modules/events.jsm"
 ).EnigmailEvents;
 var EnigmailKeyRing = ChromeUtils.import(
   "chrome://openpgp/content/modules/keyRing.jsm"
 ).EnigmailKeyRing;
+var EnigmailKeyServer = ChromeUtils.import(
+  "chrome://openpgp/content/modules/keyserver.jsm"
+).EnigmailKeyServer;
+var { EnigmailKeyserverURIs } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/keyserverUris.jsm"
+);
 var EnigmailDecryption = ChromeUtils.import(
   "chrome://openpgp/content/modules/decryption.jsm"
 ).EnigmailDecryption;
 var EnigmailAttachment = ChromeUtils.import(
   "chrome://openpgp/content/modules/attachment.jsm"
 ).EnigmailAttachment;
 var EnigmailConstants = ChromeUtils.import(
   "chrome://openpgp/content/modules/constants.jsm"
@@ -110,16 +116,19 @@ var EnigmailMime = ChromeUtils.import(
 var EnigmailArmor = ChromeUtils.import(
   "chrome://openpgp/content/modules/armor.jsm"
 ).EnigmailArmor;
 /*
 var EnigmailWks = ChromeUtils.import(
   "chrome://openpgp/content/modules/webKey.jsm"
 ).EnigmailWks;
 */
+var EnigmailWkdLookup = ChromeUtils.import(
+  "chrome://openpgp/content/modules/wkdLookup.jsm"
+).EnigmailWkdLookup;
 var EnigmailStdlib = ChromeUtils.import(
   "chrome://openpgp/content/modules/stdlib.jsm"
 ).EnigmailStdlib;
 var EnigmailConfigure = ChromeUtils.import(
   "chrome://openpgp/content/modules/configure.jsm"
 ).EnigmailConfigure;
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
@@ -300,16 +309,21 @@ Enigmail.msg = {
   },
 
   messageListener: {
     onStartHeaders() {
       Enigmail.msg.mimeParts = null;
       let b = document.getElementById("openpgpKeyBox");
       b.setAttribute("hidden", true);
       b.removeAttribute("keydata");
+
+      let b2 = document.getElementById("signatureKeyBox");
+      b2.setAttribute("hidden", true);
+      b2.removeAttribute("keyid");
+
       /*
       if ("autocrypt" in gExpandedHeaderView) {
         delete gExpandedHeaderView.autocrypt;
       }
       */
       if ("openpgp" in gExpandedHeaderView) {
         delete gExpandedHeaderView.openpgp;
       }
@@ -1789,16 +1803,49 @@ Enigmail.msg = {
     } else {
       EnigmailDialog.alert(
         window,
         EnigmailLocale.getString("previewFailed") + "\n" + errorMsgObj.value
       );
     }
   },
 
+  async searchSignatureKey() {
+    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");
+      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);
+    } else {
+      console.debug("searchKeysOnInternet no data in keys.openpgp.org");
+    }
+  },
+
   importKeyFromMsgBody(msgData) {
     let beginIndexObj = {};
     let endIndexObj = {};
     let indentStrObj = {};
     let blockType = EnigmailArmor.locateArmoredBlock(
       msgData,
       0,
       "",
@@ -3054,16 +3101,63 @@ Enigmail.msg = {
         this.handleAttachment("importKey", currentAttachments[i]);
         keyFound = true;
       }
     }
 
     return keyFound;
   },
 
+  async searchKeysOnInternet(aHeaderNode) {
+    let address = aHeaderNode
+      .closest("mail-emailaddress")
+      .getAttribute("emailAddress");
+
+    let foundKeys = null;
+    foundKeys = await EnigmailWkdLookup.downloadKey(address);
+    if (!foundKeys) {
+      console.debug("searchKeysOnInternet no wkd data");
+    } else {
+      let keyList = EnigmailKey.getKeyListFromKeyBlock(
+        foundKeys.keyData,
+        {},
+        false
+      );
+      this.importKeyDataWithConfirmation(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");
+      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);
+    } else {
+      console.debug("searchKeysOnInternet no data in keys.openpgp.org");
+    }
+  },
+
   importKeyFromKeyserver() {
     var pubKeyId = "0x" + Enigmail.msg.securityInfo.keyId;
     var inputObj = {
       searchList: [pubKeyId],
       autoKeyServer: EnigmailPrefs.getPref("autoKeyServerSelection")
         ? EnigmailPrefs.getPref("keyserver").split(/[ ,;]/g)[0]
         : null,
     };
--- a/mail/extensions/openpgp/content/ui/enigmailMsgHdrViewOverlay.js
+++ b/mail/extensions/openpgp/content/ui/enigmailMsgHdrViewOverlay.js
@@ -179,19 +179,19 @@ Enigmail.hdrView = {
       //enigMsgPane.setAttribute("collapsed", true);
       bodyElement.removeAttribute("collapsed");
     } catch (ex) {
       console.debug(ex);
     }
   },
 
   setStatusText(txt) {
-    let s = document.getElementById("enigmailStatusText");
-    if (s) {
-      s.firstChild.data = txt;
+    // TODO: replace with other information display, tracker bug 1595227
+    if (txt) {
+      console.debug(txt);
     }
   },
 
   updateHdrIcons(
     exitCode,
     statusFlags,
     extStatusFlags,
     keyId,
@@ -368,16 +368,21 @@ Enigmail.hdrView = {
         statusInfo = EnigmailLocale.getString("missingPassphrase");
       } else if (statusFlags & EnigmailConstants.NO_SECKEY) {
         statusInfo = EnigmailLocale.getString("needKey");
       } else {
         statusInfo = EnigmailLocale.getString("failedDecrypt");
       }
     } else if (statusFlags & EnigmailConstants.UNCERTAIN_SIGNATURE) {
       statusInfo = EnigmailLocale.getString("uncertainSig");
+      if (statusFlags & EnigmailConstants.NO_PUBKEY) {
+        let b = document.getElementById("signatureKeyBox");
+        b.setAttribute("hidden", false);
+        b.setAttribute("keyid", keyId);
+      }
     } else if (statusFlags & EnigmailConstants.GOOD_SIGNATURE) {
       statusInfo = EnigmailLocale.getString("goodSig", [keyId]);
     } else if (
       statusFlags &
       (EnigmailConstants.BAD_SIGNATURE |
         EnigmailConstants.EXPIRED_SIGNATURE |
         EnigmailConstants.EXPIRED_KEY_SIGNATURE)
     ) {
@@ -631,18 +636,16 @@ Enigmail.hdrView = {
   displayAutocryptMessage(allowImport) {
     EnigmailLog.DEBUG(
       "enigmailMsgHdrViewOverlay.js: displayAutocryptMessage()\n"
     );
   },
   */
 
   displayStatusBar() {
-    //let statusText = document.getElementById("enigmailStatusText");
-    //let icon = document.getElementById("enigToggleHeaderView2");
     let bodyElement = document.getElementById("messagepanebox");
 
     let secInfo = Enigmail.msg.securityInfo;
     let statusFlags = secInfo.statusFlags;
     let extStatusFlags =
       "extStatusFlags" in secInfo ? secInfo.extStatusFlags : 0;
     let sMimeContainer, encryptedUINode, signedUINode;