Bug 1594855 - Initial import of Enigmail backend modules, skin files, (renamed) prefs, strings. r=patrick DONTBUILD
authorKai Engert <kaie@kuix.de>
Sun, 17 Nov 2019 13:10:00 +0100
changeset 36654 4d8319181ab24b707c73be57d2277a0576bd8e02
parent 36653 e72aa5579de0aba0d6424cfa57e485b1ce944419
child 36655 c3f401cda821b1873b02698ba8a5541eb4677633
push id2534
push userclokep@gmail.com
push dateMon, 02 Dec 2019 19:52:51 +0000
treeherdercomm-beta@055c50840778 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspatrick
bugs1594855
Bug 1594855 - Initial import of Enigmail backend modules, skin files, (renamed) prefs, strings. r=patrick DONTBUILD
mail/extensions/openpgp/content/modules/addrbook.jsm
mail/extensions/openpgp/content/modules/amPrefsService.jsm
mail/extensions/openpgp/content/modules/app.jsm
mail/extensions/openpgp/content/modules/armor.jsm
mail/extensions/openpgp/content/modules/attachment.jsm
mail/extensions/openpgp/content/modules/autoSetup.jsm
mail/extensions/openpgp/content/modules/autocrypt.jsm
mail/extensions/openpgp/content/modules/buildDate.jsm
mail/extensions/openpgp/content/modules/card.jsm
mail/extensions/openpgp/content/modules/clipboard.jsm
mail/extensions/openpgp/content/modules/commandLine.jsm
mail/extensions/openpgp/content/modules/compat.jsm
mail/extensions/openpgp/content/modules/configBackup.jsm
mail/extensions/openpgp/content/modules/configure.jsm
mail/extensions/openpgp/content/modules/constants.jsm
mail/extensions/openpgp/content/modules/core.jsm
mail/extensions/openpgp/content/modules/cryptoAPI.jsm
mail/extensions/openpgp/content/modules/cryptoAPI/gnupg-decryption.jsm
mail/extensions/openpgp/content/modules/cryptoAPI/gnupg-key.jsm
mail/extensions/openpgp/content/modules/cryptoAPI/gnupg-keylist.jsm
mail/extensions/openpgp/content/modules/cryptoAPI/gnupg.js
mail/extensions/openpgp/content/modules/cryptoAPI/interface.js
mail/extensions/openpgp/content/modules/data.jsm
mail/extensions/openpgp/content/modules/decryption.jsm
mail/extensions/openpgp/content/modules/dialog.jsm
mail/extensions/openpgp/content/modules/dns.jsm
mail/extensions/openpgp/content/modules/encryption.jsm
mail/extensions/openpgp/content/modules/enigmailprocess_common.jsm
mail/extensions/openpgp/content/modules/enigmailprocess_main.jsm
mail/extensions/openpgp/content/modules/enigmailprocess_shared.js
mail/extensions/openpgp/content/modules/enigmailprocess_shared_unix.js
mail/extensions/openpgp/content/modules/enigmailprocess_shared_win.js
mail/extensions/openpgp/content/modules/enigmailprocess_unix.jsm
mail/extensions/openpgp/content/modules/enigmailprocess_win.jsm
mail/extensions/openpgp/content/modules/enigmailprocess_worker_common.js
mail/extensions/openpgp/content/modules/enigmailprocess_worker_unix.js
mail/extensions/openpgp/content/modules/enigmailprocess_worker_win.js
mail/extensions/openpgp/content/modules/errorHandling.jsm
mail/extensions/openpgp/content/modules/events.jsm
mail/extensions/openpgp/content/modules/execution.jsm
mail/extensions/openpgp/content/modules/files.jsm
mail/extensions/openpgp/content/modules/filters.jsm
mail/extensions/openpgp/content/modules/filtersWrapper.jsm
mail/extensions/openpgp/content/modules/fixExchangeMsg.jsm
mail/extensions/openpgp/content/modules/funcs.jsm
mail/extensions/openpgp/content/modules/glodaUtils.jsm
mail/extensions/openpgp/content/modules/gpg.jsm
mail/extensions/openpgp/content/modules/gpgAgent.jsm
mail/extensions/openpgp/content/modules/hash.jsm
mail/extensions/openpgp/content/modules/httpProxy.jsm
mail/extensions/openpgp/content/modules/key.jsm
mail/extensions/openpgp/content/modules/keyEditor.jsm
mail/extensions/openpgp/content/modules/keyObj.jsm
mail/extensions/openpgp/content/modules/keyRefreshService.jsm
mail/extensions/openpgp/content/modules/keyRing.jsm
mail/extensions/openpgp/content/modules/keyUsability.jsm
mail/extensions/openpgp/content/modules/keyserver.jsm
mail/extensions/openpgp/content/modules/keyserverUris.jsm
mail/extensions/openpgp/content/modules/lazy.jsm
mail/extensions/openpgp/content/modules/locale.jsm
mail/extensions/openpgp/content/modules/localizeHtml.jsm
mail/extensions/openpgp/content/modules/log.jsm
mail/extensions/openpgp/content/modules/mime.jsm
mail/extensions/openpgp/content/modules/mimeDecrypt.jsm
mail/extensions/openpgp/content/modules/mimeEncrypt.jsm
mail/extensions/openpgp/content/modules/mimeVerify.jsm
mail/extensions/openpgp/content/modules/msgRead.jsm
mail/extensions/openpgp/content/modules/openpgp.jsm
mail/extensions/openpgp/content/modules/os.jsm
mail/extensions/openpgp/content/modules/passwordCheck.jsm
mail/extensions/openpgp/content/modules/passwords.jsm
mail/extensions/openpgp/content/modules/persistentCrypto.jsm
mail/extensions/openpgp/content/modules/pgpmimeHandler.jsm
mail/extensions/openpgp/content/modules/pipeConsole.jsm
mail/extensions/openpgp/content/modules/prefs.jsm
mail/extensions/openpgp/content/modules/protocolHandler.jsm
mail/extensions/openpgp/content/modules/rng.jsm
mail/extensions/openpgp/content/modules/rules.jsm
mail/extensions/openpgp/content/modules/searchCallback.jsm
mail/extensions/openpgp/content/modules/send.jsm
mail/extensions/openpgp/content/modules/singletons.jsm
mail/extensions/openpgp/content/modules/socks5Proxy.jsm
mail/extensions/openpgp/content/modules/sqliteDb.jsm
mail/extensions/openpgp/content/modules/stdlib.jsm
mail/extensions/openpgp/content/modules/stdlib/compose.jsm
mail/extensions/openpgp/content/modules/stdlib/misc.jsm
mail/extensions/openpgp/content/modules/stdlib/msgHdrUtils.jsm
mail/extensions/openpgp/content/modules/streams.jsm
mail/extensions/openpgp/content/modules/subprocess.jsm
mail/extensions/openpgp/content/modules/system.jsm
mail/extensions/openpgp/content/modules/time.jsm
mail/extensions/openpgp/content/modules/timer.jsm
mail/extensions/openpgp/content/modules/tor.jsm
mail/extensions/openpgp/content/modules/trust.jsm
mail/extensions/openpgp/content/modules/uris.jsm
mail/extensions/openpgp/content/modules/verify.jsm
mail/extensions/openpgp/content/modules/versioning.jsm
mail/extensions/openpgp/content/modules/webKey.jsm
mail/extensions/openpgp/content/modules/windows.jsm
mail/extensions/openpgp/content/modules/wkdLookup.jsm
mail/extensions/openpgp/content/modules/wksMimeHandler.jsm
mail/extensions/openpgp/content/modules/xhrUtils.jsm
mail/extensions/openpgp/content/modules/zbase32.jsm
mail/extensions/openpgp/content/strings/enigmail.dtd
mail/extensions/openpgp/content/strings/enigmail.properties
mail/extensions/openpgp/prefs/openpgp-prefs.js
mail/extensions/openpgp/skin/tb-linux/attach-active-18.svg
mail/extensions/openpgp/skin/tb-linux/attach-disabled-18.svg
mail/extensions/openpgp/skin/tb-linux/attach-inactive-18.svg
mail/extensions/openpgp/skin/tb-linux/col-encrypted-signed.png
mail/extensions/openpgp/skin/tb-linux/decrypt-active-18.svg
mail/extensions/openpgp/skin/tb-linux/decrypt-inactive-18.svg
mail/extensions/openpgp/skin/tb-linux/encrypt-active-18.svg
mail/extensions/openpgp/skin/tb-linux/encrypt-disabled-18.svg
mail/extensions/openpgp/skin/tb-linux/encrypt-inactive-18.svg
mail/extensions/openpgp/skin/tb-linux/enigEncActiveConflict.png
mail/extensions/openpgp/skin/tb-linux/enigEncActiveMinus.png
mail/extensions/openpgp/skin/tb-linux/enigEncActiveNone.png
mail/extensions/openpgp/skin/tb-linux/enigEncActivePlus.png
mail/extensions/openpgp/skin/tb-linux/enigEncForceNo.png
mail/extensions/openpgp/skin/tb-linux/enigEncForceYes.png
mail/extensions/openpgp/skin/tb-linux/enigEncInactive.png
mail/extensions/openpgp/skin/tb-linux/enigEncInactiveConflict.png
mail/extensions/openpgp/skin/tb-linux/enigEncInactiveMinus.png
mail/extensions/openpgp/skin/tb-linux/enigEncInactiveNone.png
mail/extensions/openpgp/skin/tb-linux/enigEncInactivePlus.png
mail/extensions/openpgp/skin/tb-linux/enigEncNotOk.png
mail/extensions/openpgp/skin/tb-linux/enigEncOk.png
mail/extensions/openpgp/skin/tb-linux/enigSignActiveConflict.png
mail/extensions/openpgp/skin/tb-linux/enigSignActiveMinus.png
mail/extensions/openpgp/skin/tb-linux/enigSignActiveNone.png
mail/extensions/openpgp/skin/tb-linux/enigSignActivePlus.png
mail/extensions/openpgp/skin/tb-linux/enigSignForceNo.png
mail/extensions/openpgp/skin/tb-linux/enigSignForceYes.png
mail/extensions/openpgp/skin/tb-linux/enigSignInactive.png
mail/extensions/openpgp/skin/tb-linux/enigSignInactiveConflict.png
mail/extensions/openpgp/skin/tb-linux/enigSignInactiveMinus.png
mail/extensions/openpgp/skin/tb-linux/enigSignInactiveNone.png
mail/extensions/openpgp/skin/tb-linux/enigSignInactivePlus.png
mail/extensions/openpgp/skin/tb-linux/enigSignNotOk.png
mail/extensions/openpgp/skin/tb-linux/enigSignOk.png
mail/extensions/openpgp/skin/tb-linux/enigSignUnkown.png
mail/extensions/openpgp/skin/tb-linux/enigmail-common.css
mail/extensions/openpgp/skin/tb-linux/enigmail-html.css
mail/extensions/openpgp/skin/tb-linux/enigmail.css
mail/extensions/openpgp/skin/tb-linux/headerProtected-18.svg
mail/extensions/openpgp/skin/tb-linux/headerUnprotected-18.svg
mail/extensions/openpgp/skin/tb-linux/importSuccess.png
mail/extensions/openpgp/skin/tb-linux/ok-sign.svg
mail/extensions/openpgp/skin/tb-linux/password-error.svg
mail/extensions/openpgp/skin/tb-linux/sign-active-18.svg
mail/extensions/openpgp/skin/tb-linux/sign-disabled-18.svg
mail/extensions/openpgp/skin/tb-linux/sign-inactive-18.svg
mail/extensions/openpgp/skin/tb-linux/spinning-wheel.png
mail/extensions/openpgp/skin/tb-linux/twisty-clsd.png
mail/extensions/openpgp/skin/tb-linux/twisty-open.png
mail/extensions/openpgp/skin/tb-linux/warning-16.png
mail/extensions/openpgp/skin/tb-mac/attach-active-18.svg
mail/extensions/openpgp/skin/tb-mac/attach-disabled-18.svg
mail/extensions/openpgp/skin/tb-mac/attach-inactive-18.svg
mail/extensions/openpgp/skin/tb-mac/col-encrypted-signed.png
mail/extensions/openpgp/skin/tb-mac/decrypt-active-18.svg
mail/extensions/openpgp/skin/tb-mac/decrypt-inactive-18.svg
mail/extensions/openpgp/skin/tb-mac/encrypt-active-18.svg
mail/extensions/openpgp/skin/tb-mac/encrypt-disabled-18.svg
mail/extensions/openpgp/skin/tb-mac/encrypt-inactive-18.svg
mail/extensions/openpgp/skin/tb-mac/enigEncActiveConflict.png
mail/extensions/openpgp/skin/tb-mac/enigEncActiveMinus.png
mail/extensions/openpgp/skin/tb-mac/enigEncActiveNone.png
mail/extensions/openpgp/skin/tb-mac/enigEncActivePlus.png
mail/extensions/openpgp/skin/tb-mac/enigEncForceNo.png
mail/extensions/openpgp/skin/tb-mac/enigEncForceYes.png
mail/extensions/openpgp/skin/tb-mac/enigEncInactive.png
mail/extensions/openpgp/skin/tb-mac/enigEncInactiveConflict.png
mail/extensions/openpgp/skin/tb-mac/enigEncInactiveMinus.png
mail/extensions/openpgp/skin/tb-mac/enigEncInactiveNone.png
mail/extensions/openpgp/skin/tb-mac/enigEncInactivePlus.png
mail/extensions/openpgp/skin/tb-mac/enigEncNotOk.png
mail/extensions/openpgp/skin/tb-mac/enigEncOk.png
mail/extensions/openpgp/skin/tb-mac/enigSignActiveConflict.png
mail/extensions/openpgp/skin/tb-mac/enigSignActiveMinus.png
mail/extensions/openpgp/skin/tb-mac/enigSignActiveNone.png
mail/extensions/openpgp/skin/tb-mac/enigSignActivePlus.png
mail/extensions/openpgp/skin/tb-mac/enigSignForceNo.png
mail/extensions/openpgp/skin/tb-mac/enigSignForceYes.png
mail/extensions/openpgp/skin/tb-mac/enigSignInactive.png
mail/extensions/openpgp/skin/tb-mac/enigSignInactiveConflict.png
mail/extensions/openpgp/skin/tb-mac/enigSignInactiveMinus.png
mail/extensions/openpgp/skin/tb-mac/enigSignInactiveNone.png
mail/extensions/openpgp/skin/tb-mac/enigSignInactivePlus.png
mail/extensions/openpgp/skin/tb-mac/enigSignNotOk.png
mail/extensions/openpgp/skin/tb-mac/enigSignOk.png
mail/extensions/openpgp/skin/tb-mac/enigSignUnkown.png
mail/extensions/openpgp/skin/tb-mac/enigmail-common.css
mail/extensions/openpgp/skin/tb-mac/enigmail-html.css
mail/extensions/openpgp/skin/tb-mac/enigmail.css
mail/extensions/openpgp/skin/tb-mac/headerProtected-18.svg
mail/extensions/openpgp/skin/tb-mac/headerUnprotected-18.svg
mail/extensions/openpgp/skin/tb-mac/importSuccess.png
mail/extensions/openpgp/skin/tb-mac/ok-sign.svg
mail/extensions/openpgp/skin/tb-mac/password-error.svg
mail/extensions/openpgp/skin/tb-mac/sign-active-18.svg
mail/extensions/openpgp/skin/tb-mac/sign-disabled-18.svg
mail/extensions/openpgp/skin/tb-mac/sign-inactive-18.svg
mail/extensions/openpgp/skin/tb-mac/spinning-wheel.png
mail/extensions/openpgp/skin/tb-mac/twisty-clsd.png
mail/extensions/openpgp/skin/tb-mac/twisty-open.png
mail/extensions/openpgp/skin/tb-mac/warning-16.png
mail/extensions/openpgp/skin/tb-win/attach-active-18.svg
mail/extensions/openpgp/skin/tb-win/attach-disabled-18.svg
mail/extensions/openpgp/skin/tb-win/attach-inactive-18.svg
mail/extensions/openpgp/skin/tb-win/col-encrypted-signed.png
mail/extensions/openpgp/skin/tb-win/decrypt-active-18.svg
mail/extensions/openpgp/skin/tb-win/decrypt-inactive-18.svg
mail/extensions/openpgp/skin/tb-win/encrypt-active-18.svg
mail/extensions/openpgp/skin/tb-win/encrypt-disabled-18.svg
mail/extensions/openpgp/skin/tb-win/encrypt-inactive-18.svg
mail/extensions/openpgp/skin/tb-win/enigEncActiveConflict.png
mail/extensions/openpgp/skin/tb-win/enigEncActiveMinus.png
mail/extensions/openpgp/skin/tb-win/enigEncActiveNone.png
mail/extensions/openpgp/skin/tb-win/enigEncActivePlus.png
mail/extensions/openpgp/skin/tb-win/enigEncForceNo.png
mail/extensions/openpgp/skin/tb-win/enigEncForceYes.png
mail/extensions/openpgp/skin/tb-win/enigEncInactive.png
mail/extensions/openpgp/skin/tb-win/enigEncInactiveConflict.png
mail/extensions/openpgp/skin/tb-win/enigEncInactiveMinus.png
mail/extensions/openpgp/skin/tb-win/enigEncInactiveNone.png
mail/extensions/openpgp/skin/tb-win/enigEncInactivePlus.png
mail/extensions/openpgp/skin/tb-win/enigEncNotOk.png
mail/extensions/openpgp/skin/tb-win/enigEncOk.png
mail/extensions/openpgp/skin/tb-win/enigSignActiveConflict.png
mail/extensions/openpgp/skin/tb-win/enigSignActiveMinus.png
mail/extensions/openpgp/skin/tb-win/enigSignActiveNone.png
mail/extensions/openpgp/skin/tb-win/enigSignActivePlus.png
mail/extensions/openpgp/skin/tb-win/enigSignForceNo.png
mail/extensions/openpgp/skin/tb-win/enigSignForceYes.png
mail/extensions/openpgp/skin/tb-win/enigSignInactive.png
mail/extensions/openpgp/skin/tb-win/enigSignInactiveConflict.png
mail/extensions/openpgp/skin/tb-win/enigSignInactiveMinus.png
mail/extensions/openpgp/skin/tb-win/enigSignInactiveNone.png
mail/extensions/openpgp/skin/tb-win/enigSignInactivePlus.png
mail/extensions/openpgp/skin/tb-win/enigSignNotOk.png
mail/extensions/openpgp/skin/tb-win/enigSignOk.png
mail/extensions/openpgp/skin/tb-win/enigSignUnkown.png
mail/extensions/openpgp/skin/tb-win/enigmail-common.css
mail/extensions/openpgp/skin/tb-win/enigmail-html.css
mail/extensions/openpgp/skin/tb-win/enigmail.css
mail/extensions/openpgp/skin/tb-win/headerProtected-18.svg
mail/extensions/openpgp/skin/tb-win/headerUnprotected-18.svg
mail/extensions/openpgp/skin/tb-win/importSuccess.png
mail/extensions/openpgp/skin/tb-win/ok-sign.svg
mail/extensions/openpgp/skin/tb-win/password-error.svg
mail/extensions/openpgp/skin/tb-win/sign-active-18.svg
mail/extensions/openpgp/skin/tb-win/sign-disabled-18.svg
mail/extensions/openpgp/skin/tb-win/sign-inactive-18.svg
mail/extensions/openpgp/skin/tb-win/spinning-wheel.png
mail/extensions/openpgp/skin/tb-win/twisty-clsd.png
mail/extensions/openpgp/skin/tb-win/twisty-open.png
mail/extensions/openpgp/skin/tb-win/warning-16.png
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/addrbook.jsm
@@ -0,0 +1,52 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailAddrbook"];
+
+/*
+ * Functionality related to the Thunderbird address book
+ *
+ */
+
+
+
+
+
+const ABMANAGER = "@mozilla.org/abmanager;1";
+
+var EnigmailAddrbook = {
+  /**
+   * Look up the address book card for a given email address
+   *
+   * @param emailAddr: String - email address to find
+   *
+   * @return if found: Object:
+   *           - card: nsIAbCard for found email address
+   *           - directory: nsIAbDirectory of found card
+   *         NULL if not found
+   */
+  lookupEmailAddress: function(emailAddr) {
+    let abm = Cc[ABMANAGER].getService(Ci.nsIAbManager);
+    let enumerator = abm.directories;
+
+    while (enumerator.hasMoreElements()) {
+      let abd = enumerator.getNext().QueryInterface(Ci.nsIAbDirectory);
+      try {
+        let crd = abd.cardForEmailAddress(emailAddr);
+        if (crd) return {
+          directory: abd,
+          card: crd
+        };
+      }
+      catch (x) {}
+    }
+
+    return null;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/amPrefsService.jsm
@@ -0,0 +1,92 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+  manager: Cm,
+  results: Cr,
+  Constructor: CC
+} = Components;
+Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+
+const CATEGORY = "mailnews-accountmanager-extensions";
+const CATEGORY_ENTRY = "openpgp-account-manager-extension";
+const PREF_SERVICE_NAME = "@mozilla.org/accountmanager/extension;1?name=enigprefs";
+
+var EXPORTED_SYMBOLS = ["EnigmailAmPrefsService"];
+
+var EnigmailAmPrefsService = {
+  startup: function(reason) {
+    try {
+      var catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+      catMan.addCategoryEntry(CATEGORY,
+        CATEGORY_ENTRY,
+        PREF_SERVICE_NAME,
+        false, true);
+      this.factory = new Factory(EnigmailPrefService);
+    }
+    catch (ex) {}
+  },
+
+  shutdown: function(reason) {
+    var catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+    catMan.deleteCategoryEntry(CATEGORY, CATEGORY_ENTRY, false);
+
+    if (this.factory) {
+      this.factory.unregister();
+    }
+  }
+};
+
+function EnigmailPrefService() {}
+
+EnigmailPrefService.prototype = {
+  name: "enigprefs",
+  chromePackageName: "openpgp",
+  classID: Components.ID("{f2be6d32-ff3c-11e9-8e8b-00163e5e6c00}"),
+  classDescription: "OpenPGP Account Manager Extension Service",
+  contractID: PREF_SERVICE_NAME,
+  QueryInterface: EnigmailCompat.generateQI(["nsIMsgAccountManagerExtension"]),
+
+  showPanel: function(server) {
+    // show Enigmail panel for POP3, IMAP, NNTP and "movemail" (unix) account types
+    switch (server.type) {
+      case "nntp":
+      case "imap":
+      case "pop3":
+      case "movemail":
+        return true;
+    }
+    return false;
+  }
+};
+
+class Factory {
+  constructor(component) {
+    this.component = component;
+    this.register();
+    Object.freeze(this);
+  }
+
+  createInstance(outer, iid) {
+    if (outer) {
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+    }
+    return new this.component();
+  }
+
+  register() {
+    Cm.registerFactory(this.component.prototype.classID,
+      this.component.prototype.classDescription,
+      this.component.prototype.contractID,
+      this);
+  }
+
+  unregister() {
+    Cm.unregisterFactory(this.component.prototype.classID, this);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/app.jsm
@@ -0,0 +1,71 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailApp"];
+
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const getEnigmailLog = EnigmailLazy.loader("enigmail/log.jsm", "EnigmailLog");
+
+const DIR_SERV_CONTRACTID = "@mozilla.org/file/directory_service;1";
+const ENIG_EXTENSION_GUID = "{847b3a00-7ab1-11d4-8f02-006008948af5}";
+const XPCOM_APPINFO = "@mozilla.org/xre/app-info;1";
+
+var EnigmailApp = {
+  /**
+   * Platform application name (e.g. Thunderbird)
+   */
+  getName: function() {
+    return Cc[XPCOM_APPINFO].getService(Ci.nsIXULAppInfo).name;
+  },
+
+  /**
+   * Platform (Gecko) version number (e.g. 42.0)
+   * The platform version for SeaMonkey and for Thunderbird are identical
+   * (unlike the application version numbers)
+   */
+  getPlatformVersion: function() {
+    return Cc[XPCOM_APPINFO].getService(Ci.nsIXULAppInfo).platformVersion;
+  },
+
+  /**
+   * Return the directory holding the current profile as nsIFile object
+   */
+  getProfileDirectory: function() {
+    let ds = Cc[DIR_SERV_CONTRACTID].getService(Ci.nsIProperties);
+    return ds.get("ProfD", Ci.nsIFile);
+  },
+
+  /**
+   * Get Enigmail version
+   */
+  getVersion: function() {
+    getEnigmailLog().DEBUG("app.jsm: getVersion\n");
+    getEnigmailLog().DEBUG("app.jsm: installed version: " + EnigmailApp._version + "\n");
+    return EnigmailApp._version;
+  },
+
+  /**
+   * Get Enigmail installation directory
+   */
+  getInstallLocation: function() {
+    return EnigmailApp._installLocation;
+  },
+
+  setVersion: function(version) {
+    EnigmailApp._version = version;
+  },
+
+  setInstallLocation: function(location) {
+    EnigmailApp._installLocation = location;
+  },
+
+  initAddon: function(addon) {
+    EnigmailApp.setVersion(addon.version);
+    EnigmailApp.setInstallLocation(addon.installPath);
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/armor.jsm
@@ -0,0 +1,293 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailArmor"];
+
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+
+
+// Locates STRing in TEXT occurring only at the beginning of a line
+function indexOfArmorDelimiter(text, str, offset) {
+  let currentOffset = offset;
+
+  while (currentOffset < text.length) {
+    let loc = text.indexOf(str, currentOffset);
+
+    if (loc === -1 || loc === 0 || text.charAt(loc - 1) == "\n") {
+      return loc;
+    }
+
+    currentOffset = loc + str.length;
+  }
+
+  return -1;
+}
+
+function searchBlankLine(str, then) {
+  var offset = str.search(/\n\s*\r?\n/);
+  if (offset === -1) {
+    return "";
+  } else {
+    return then(offset);
+  }
+}
+
+function indexOfNewline(str, off, then) {
+  var offset = str.indexOf("\n", off);
+  if (offset === -1) {
+    return "";
+  } else {
+    return then(offset);
+  }
+}
+
+var EnigmailArmor = {
+  /**
+   * Locates offsets bracketing PGP armored block in text,
+   * starting from given offset, and returns block type string.
+   *
+   * @param text:          String - ASCII armored text
+   * @param offset:        Number - offset to start looking for block
+   * @param indentStr:     String - prefix that is used for all lines (such as "> ")
+   * @param beginIndexObj: Object - o.value will contain offset of first character of block
+   * @param endIndexObj:   Object - o.value will contain offset of last character of block (newline)
+   * @param indentStrObj:  Object - o.value will contain indent of 1st line
+   *
+   * @return String - type of block found (e.g. MESSAGE, PUBLIC KEY)
+   *           If no block is found, an empty String is returned;
+   */
+  locateArmoredBlock: function(text, offset, indentStr, beginIndexObj, endIndexObj, indentStrObj) {
+    EnigmailLog.DEBUG("armor.jsm: Enigmail.locateArmoredBlock: " + offset + ", '" + indentStr + "'\n");
+
+    beginIndexObj.value = -1;
+    endIndexObj.value = -1;
+
+    var beginIndex = indexOfArmorDelimiter(text, indentStr + "-----BEGIN PGP ", offset);
+
+    if (beginIndex == -1) {
+      var blockStart = text.indexOf("-----BEGIN PGP ");
+      if (blockStart >= 0) {
+        var indentStart = text.search(/\n.*-----BEGIN PGP /) + 1;
+        indentStrObj.value = text.substring(indentStart, blockStart);
+        indentStr = indentStrObj.value;
+        beginIndex = indexOfArmorDelimiter(text, indentStr + "-----BEGIN PGP ", offset);
+      }
+    }
+
+    if (beginIndex == -1)
+      return "";
+
+    // Locate newline at end of armor header
+    offset = text.indexOf("\n", beginIndex);
+
+    if (offset == -1)
+      return "";
+
+    var endIndex = indexOfArmorDelimiter(text, indentStr + "-----END PGP ", offset);
+
+    if (endIndex == -1)
+      return "";
+
+    // Locate newline at end of PGP block
+    endIndex = text.indexOf("\n", endIndex);
+
+    if (endIndex == -1) {
+      // No terminating newline
+      endIndex = text.length - 1;
+    }
+
+    var blockHeader = text.substr(beginIndex, offset - beginIndex + 1);
+
+    var blockRegex = new RegExp("^" + indentStr +
+      "-----BEGIN PGP (.{1,30})-----\\s*\\r?\\n");
+
+    var matches = blockHeader.match(blockRegex);
+
+    var blockType = "";
+    if (matches && (matches.length > 1)) {
+      blockType = matches[1];
+      EnigmailLog.DEBUG("armor.jsm: Enigmail.locateArmoredBlock: blockType=" + blockType + "\n");
+    }
+
+    if (blockType == "UNVERIFIED MESSAGE") {
+      // Skip any unverified message block
+      return EnigmailArmor.locateArmoredBlock(text, endIndex + 1, indentStr, beginIndexObj, endIndexObj, indentStrObj);
+    }
+
+    beginIndexObj.value = beginIndex;
+    endIndexObj.value = endIndex;
+
+    return blockType;
+  },
+
+  /**
+   * locateArmoredBlocks returns an array of ASCII Armor block positions
+   *
+   * @param text: String - text containing ASCII armored block(s)
+   *
+   * @return Array of objects with the following structure:
+   *        obj.begin:     Number
+   *        obj.end:       Number
+   *        obj.indent:    String
+   *        obj.blocktype: String
+   *
+   *       if no block was found, an empty array is returned
+   */
+  locateArmoredBlocks: function(text) {
+    var beginObj = {};
+    var endObj = {};
+    var indentStrObj = {};
+    var blocks = [];
+    var i = 0;
+    var b;
+
+    while ((b = EnigmailArmor.locateArmoredBlock(text, i, "", beginObj, endObj, indentStrObj)) !== "") {
+      blocks.push({
+        begin: beginObj.value,
+        end: endObj.value,
+        indent: indentStrObj.value ? indentStrObj.value : "",
+        blocktype: b
+      });
+
+      i = endObj.value;
+    }
+
+    EnigmailLog.DEBUG("armor.jsm: locateArmorBlocks: Found " + blocks.length + " Blocks\n");
+    return blocks;
+  },
+
+  extractSignaturePart: function(signatureBlock, part) {
+    EnigmailLog.DEBUG("armor.jsm: Enigmail.extractSignaturePart: part=" + part + "\n");
+
+    return searchBlankLine(signatureBlock, function(offset) {
+      return indexOfNewline(signatureBlock, offset + 1, function(offset) {
+        var beginIndex = signatureBlock.indexOf("-----BEGIN PGP SIGNATURE-----", offset + 1);
+        if (beginIndex == -1) {
+          return "";
+        }
+
+        if (part === EnigmailConstants.SIGNATURE_TEXT) {
+          return signatureBlock.substr(offset + 1, beginIndex - offset - 1).
+          replace(/^- -/, "-").
+          replace(/\n- -/g, "\n-").
+          replace(/\r- -/g, "\r-");
+        }
+
+        return indexOfNewline(signatureBlock, beginIndex, function(offset) {
+          var endIndex = signatureBlock.indexOf("-----END PGP SIGNATURE-----", offset);
+          if (endIndex == -1) {
+            return "";
+          }
+
+          var signBlock = signatureBlock.substr(offset, endIndex - offset);
+
+          return searchBlankLine(signBlock, function(armorIndex) {
+            if (part == EnigmailConstants.SIGNATURE_HEADERS) {
+              return signBlock.substr(1, armorIndex);
+            }
+
+            return indexOfNewline(signBlock, armorIndex + 1, function(armorIndex) {
+              if (part == EnigmailConstants.SIGNATURE_ARMOR) {
+                return signBlock.substr(armorIndex, endIndex - armorIndex).
+                replace(/\s*/g, "");
+              } else {
+                return "";
+              }
+            });
+          });
+        });
+      });
+    });
+  },
+
+  /**
+   * Remove all headers from an OpenPGP Armored message and replace them
+   * with a set of new headers.
+   *
+   * @param armorText: String - ASCII armored message
+   * @param headers:   Object - key/value pairs of new headers to insert
+   *
+   * @return String - new armored message
+   */
+  replaceArmorHeaders: function(armorText, headers) {
+
+    let text = armorText.replace(/\r\n/g, "\n");
+    let i = text.search(/\n/);
+
+    if (i < 0) return armorText;
+    let m = text.substr(0, i + 1);
+
+    for (let j in headers) {
+      m += j + ": " + headers[j] + "\n";
+    }
+
+    i = text.search(/\n\n/);
+    if (i < 0) return armorText;
+    m += text.substr(i + 1);
+
+    return m;
+  },
+
+  /**
+   * Get a list of all headers found in an armor message
+   *
+   * @param text String - ASCII armored message
+   *
+   * @return Object: key/value pairs of headers. All keys are in lowercase.
+   */
+  getArmorHeaders: function(text) {
+    let headers = {};
+    let b = this.locateArmoredBlocks(text);
+
+    if (b.length === 0) {
+      return headers;
+    }
+
+    let msg = text.substr(b[0].begin);
+
+    let lx = new RegExp("\\n" + b[0].indent + "\\r?\\n");
+    let hdrEnd = msg.search(lx);
+    if (hdrEnd < 0) return headers;
+
+    let lines = msg.substr(0, hdrEnd).split(/\r?\n/);
+
+    let rx = new RegExp("^" + b[0].indent + "([^: ]+)(: )(.*)");
+    // skip 1st line (ARMOR-line)
+    for (let i = 1; i < lines.length; i++) {
+      let m = lines[i].match(rx);
+      if (m && m.length >= 4) {
+        headers[m[1].toLowerCase()] = m[3];
+      }
+    }
+
+    return headers;
+  },
+
+  /**
+   * Split armored blocks into an array of strings
+   */
+  splitArmoredBlocks: function(keyBlockStr) {
+    let myRe = /-----BEGIN PGP (PUBLIC|PRIVATE) KEY BLOCK-----/g;
+    let myArray;
+    let retArr = [];
+    let startIndex = -1;
+    while ((myArray = myRe.exec(keyBlockStr)) !== null) {
+      if (startIndex >= 0) {
+        let s = keyBlockStr.substring(startIndex, myArray.index);
+        retArr.push(s);
+      }
+      startIndex = myArray.index;
+    }
+
+    retArr.push(keyBlockStr.substring(startIndex));
+
+    return retArr;
+  }
+};
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/attachment.jsm
@@ -0,0 +1,19 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailAttachment"];
+
+const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
+
+var EnigmailAttachment = {
+  getFileName: function(parent, byteData) {
+    const cApi = EnigmailCryptoAPI();
+    return cApi.sync(cApi.getFileName(parent, byteData));
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/autoSetup.jsm
@@ -0,0 +1,657 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/*eslint no-loop-func: 0 no-async-promise-executor: 0*/
+
+/**
+ *  Module to determine the type of setup of the user, based on existing emails
+ *  found in the inbox
+ */
+
+var EXPORTED_SYMBOLS = ["EnigmailAutoSetup"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailAutocrypt = ChromeUtils.import("chrome://openpgp/content/modules/autocrypt.jsm").EnigmailAutocrypt;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailWindows = ChromeUtils.import("chrome://openpgp/content/modules/windows.jsm").EnigmailWindows;
+const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailMime = ChromeUtils.import("chrome://openpgp/content/modules/mime.jsm").EnigmailMime;
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+const jsmime = ChromeUtils.import("resource:///modules/jsmime.jsm").jsmime;
+const EnigmailWks = ChromeUtils.import("chrome://openpgp/content/modules/webKey.jsm").EnigmailWks;
+const EnigmailTimer = ChromeUtils.import("chrome://openpgp/content/modules/timer.jsm").EnigmailTimer;
+const EnigmailStreams = ChromeUtils.import("chrome://openpgp/content/modules/streams.jsm").EnigmailStreams;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+
+// Interfaces
+const nsIFolderLookupService = Ci.nsIFolderLookupService;
+const nsIMsgAccountManager = Ci.nsIMsgAccountManager;
+const nsIMsgAccount = Ci.nsIMsgAccount;
+const nsIMsgDBHdr = Ci.nsIMsgDBHdr;
+const nsIMessenger = Ci.nsIMessenger;
+const nsIMsgMessageService = Ci.nsIMsgMessageService;
+const nsIMsgFolder = Ci.nsIMsgFolder;
+
+/**
+ * the determined setup type
+ */
+var gDeterminedSetupType = {
+  value: EnigmailConstants.AUTOSETUP_NOT_INITIALIZED
+};
+
+var EnigmailAutoSetup = {
+
+  getDeterminedSetupType: async function() {
+    if (gDeterminedSetupType.value === EnigmailConstants.AUTOSETUP_NOT_INITIALIZED) {
+      return await this.determinePreviousInstallType();
+    }
+    else
+      return gDeterminedSetupType;
+  },
+
+  /**
+   * Identify which type of setup the user had before Enigmail was (re-)installed
+   *
+   * @return Promise<Object> with:
+   *   - value : For each case assigned value, see EnigmailConstants.AUTOSETUP_xxx values
+   *   - acSetupMessage {nsIMsgDBHdr}  in case value === 1
+   *   - msgHeaders {Object}           in case value === 2
+   */
+  determinePreviousInstallType: function() {
+    let self = this;
+    gDeterminedSetupType = {
+      value: EnigmailConstants.AUTOSETUP_NOT_INITIALIZED
+    };
+
+    return new Promise(async (resolve, reject) => {
+      EnigmailLog.DEBUG("autoSetup.jsm: determinePreviousInstallType()\n");
+
+      try {
+        let msgAccountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(nsIMsgAccountManager);
+        let folderService = Cc["@mozilla.org/mail/folder-lookup;1"].getService(nsIFolderLookupService);
+        let returnMsgValue = {
+          value: EnigmailConstants.AUTOSETUP_NO_HEADER
+        };
+
+        var accounts = msgAccountManager.accounts;
+
+        let msgHeaders = [];
+        let autocryptSetupMessage = {};
+
+        // If no account, except Local Folders is configured
+        if (accounts.length <= 1) {
+          gDeterminedSetupType.value = EnigmailConstants.AUTOSETUP_NO_ACCOUNT;
+          resolve(gDeterminedSetupType);
+          return;
+        }
+
+        // Iterate through each account
+
+        for (var i = 0; i < accounts.length; i++) {
+          var account = accounts.queryElementAt(i, Ci.nsIMsgAccount);
+          var accountMsgServer = account.incomingServer;
+          EnigmailLog.DEBUG(`autoSetup.jsm: determinePreviousInstallType: scanning account "${accountMsgServer.prettyName}"\n`);
+
+          let msgFolderArr = [];
+
+          try {
+            getMsgFolders(account.incomingServer.rootFolder, msgFolderArr);
+          }
+          catch (e) {
+            EnigmailLog.DEBUG("autoSetup.jsm: determinePreviousInstallType: Error: " + e + "\n");
+          }
+
+          if (account.incomingServer.type.search(/^(none|nntp)$/) === 0) {
+            // ignore NNTP accounts and "Local Folders" accounts
+            continue;
+          }
+
+          // Iterating through each non empty Folder Database in the Account
+
+          for (var k = 0; k < msgFolderArr.length; k++) {
+            let msgFolder = msgFolderArr[k];
+            let msgDatabase = msgFolderArr[k].msgDatabase;
+
+            if ((msgFolder.flags & Ci.nsMsgFolderFlags.Junk) ||
+              (msgFolder.flags & Ci.nsMsgFolderFlags.Trash) ||
+              (!account.defaultIdentity)) {
+              continue;
+            }
+
+            EnigmailLog.DEBUG(`autoSetup.jsm: determinePreviousInstallType: scanning folder "${msgFolder.name}"\n`);
+
+            let msgEnumerator = msgDatabase.ReverseEnumerateMessages();
+
+            // Iterating through each message in the Folder
+            while (msgEnumerator.hasMoreElements()) {
+              let msgHeader = msgEnumerator.getNext().QueryInterface(nsIMsgDBHdr);
+              let msgURI = msgFolder.getUriForMsg(msgHeader);
+
+              let msgAuthor = "";
+              try {
+                msgAuthor = EnigmailFuncs.stripEmail(msgHeader.author);
+              }
+              catch (x) {}
+
+              // Listing all the headers in the message
+
+              let messenger = Components.classes["@mozilla.org/messenger;1"].createInstance(nsIMessenger);
+              let mms = messenger.messageServiceFromURI(msgURI).QueryInterface(nsIMsgMessageService);
+
+              let headerObj = await getStreamedHeaders(msgURI, mms);
+              let checkHeaderValues = await checkHeaders(headerObj, msgHeader, msgAuthor, account.defaultIdentity.email, msgFolder, returnMsgValue, msgHeaders);
+
+              msgHeaders = checkHeaderValues.msgHeaders;
+              returnMsgValue = checkHeaderValues.returnMsgValue;
+
+              const currDateInSeconds = getCurrentTime();
+              const diffSecond = currDateInSeconds - msgHeader.dateInSeconds;
+
+              /**
+                  2592000 = No. of Seconds in a Month.
+                  This is to ignore 1 month old messages.
+              */
+              if (diffSecond > 2592000.0) {
+                break;
+              }
+            }
+
+          }
+
+        }
+        if (returnMsgValue.acSetupMessage) {
+          EnigmailLog.DEBUG(`autoSetup.jsm: determinePreviousInstallType: found AC-Setup message\n`);
+          gDeterminedSetupType = returnMsgValue;
+          resolve(gDeterminedSetupType);
+        }
+        else {
+          EnigmailLog.DEBUG(`msgHeaders.length: ${msgHeaders.length}\n`);
+
+          // find newest message to know the protocol
+          let latestMsg = null;
+          for (let i = 0; i < msgHeaders.length; i++) {
+            if (!latestMsg) {
+              latestMsg = msgHeaders[i];
+            }
+
+            if (msgHeaders[i].dateTime > latestMsg.dateTime) {
+              latestMsg = msgHeaders[i];
+            }
+          }
+
+          if (latestMsg) {
+            if (latestMsg.msgType === "Autocrypt") {
+              returnMsgValue.value = EnigmailConstants.AUTOSETUP_AC_HEADER;
+              returnMsgValue.msgHeaders = msgHeaders;
+            }
+            else {
+              returnMsgValue.value = EnigmailConstants.AUTOSETUP_ENCRYPTED_MSG;
+              returnMsgValue.msgHeaders = msgHeaders;
+            }
+          }
+
+          let defId = EnigmailFuncs.getDefaultIdentity();
+          if (defId) {
+            returnMsgValue.userName = defId.fullName;
+            returnMsgValue.userEmail = defId.email;
+          }
+          else {
+            returnMsgValue.userName = undefined;
+            returnMsgValue.userEmail = undefined;
+          }
+
+          gDeterminedSetupType = returnMsgValue;
+          EnigmailLog.DEBUG(`autoSetup.jsm: determinePreviousInstallType: found type: ${returnMsgValue.value}\n`);
+          resolve(returnMsgValue);
+        }
+      }
+      catch (x) {
+        reject(x);
+      }
+    });
+
+  },
+
+  /**
+   * Process the Autocrypt Setup Message
+   *
+   * @param {Object} headerValue: contains header and attachment of an Autocrypt Setup Message
+   * @param {nsIWindow} passwordWindow: parent window for password dialog
+   * @param {nsIWindow} confirmWindow:  parent window for confirmation dialog
+   *        (note: split into 2 parent windows for unit tests)
+   *
+   * @return {Promise<Number>}: Import result.
+   *                  1: imported OK
+   *                  0: no Autocrypt setup message
+   *                 -1: import not OK (wrong password, canceled etc.)
+   */
+
+  performAutocryptSetup: async function(headerValue, passwordWindow = null, confirmWindow = null) {
+    EnigmailLog.DEBUG("autoSetup.jsm: performAutocryptSetup()\n");
+
+    EnigmailDialog.alert(window, "EnigmailAutocrypt.handleBackupMessage not implemented");
+
+    let imported = 0;
+    /*
+    if (headerValue.attachment.contentType.search(/^application\/autocrypt-setup$/i) === 0) {
+      try {
+        let res = await EnigmailAutocrypt.getSetupMessageData(headerValue.attachment.url);
+        let passwd = EnigmailWindows.autocryptSetupPasswd(passwordWindow, "input", res.passphraseFormat, res.passphraseHint);
+
+        if ((!passwd) || passwd == "") {
+          throw "noPasswd";
+        }
+
+        // TODO: await EnigmailAutocrypt.handleBackupMessage(passwd, res.attachmentData, headerValue.acSetupMessage.author);
+        EnigmailDialog.info(confirmWindow, EnigmailLocale.getString("autocrypt.importSetupKey.success", headerValue.acSetupMessage.author));
+        imported = 1;
+      }
+      catch (err) {
+        EnigmailLog.DEBUG("autoSetup.jsm: performAutocryptSetup got cancel status=" + err + "\n");
+        imported = -1;
+
+        switch (err) {
+          case "getSetupMessageData":
+            EnigmailDialog.alert(confirmWindow, EnigmailLocale.getString("autocrypt.importSetupKey.invalidMessage"));
+            break;
+          case "wrongPasswd":
+            if (EnigmailDialog.confirmDlg(confirmWindow, EnigmailLocale.getString("autocrypt.importSetupKey.wrongPasswd"), EnigmailLocale.getString("dlg.button.retry"),
+                EnigmailLocale.getString("dlg.button.cancel"))) {
+              EnigmailAutoSetup.performAutocryptSetup(headerValue);
+            }
+            break;
+          case "keyImportFailed":
+            EnigmailDialog.alert(confirmWindow, EnigmailLocale.getString("autocrypt.importSetupKey.invalidKey"));
+            break;
+          default:
+            EnigmailDialog.alert(confirmWindow, EnigmailLocale.getString("keyserver.error.unknown"));
+        }
+      }
+    }
+    */
+
+    return imported;
+  },
+
+  /**
+   * Process accounts with Autocrypt headers
+   *
+   * @param {Object} setupType: containing Autocrypt headers from accounts
+   *
+   * @return {Promise<Number>}: Result: 0: OK / 1: failure
+   */
+
+  processAutocryptHeader: function(setupType) {
+    EnigmailLog.DEBUG("autoSetup.jsm: processAutocryptHeader()\n");
+
+    return new Promise(async (resolve, reject) => {
+
+      // find newest message to know the protocol
+      let latestMsg = null;
+      for (let i = 0; i < setupType.msgHeaders.length; i++) {
+        if (!latestMsg) {
+          latestMsg = setupType.msgHeaders[i];
+        }
+
+        if (setupType.msgHeaders[i].dateTime > latestMsg) {
+          latestMsg = setupType.msgHeaders[i];
+        }
+      }
+
+      let sysType = latestMsg.msgType;
+      EnigmailLog.DEBUG(`autoSetup.jsm: processAutocryptHeader: got type: ${sysType}\n`);
+
+
+      for (let i = 0; i < setupType.msgHeaders.length; i++) {
+        if (setupType.msgHeaders[i].msgType === "Autocrypt") {
+          // FIXME
+          let success = await EnigmailAutocrypt.processAutocryptHeader(setupType.msgHeaders[i].fromAddr, [setupType.msgHeaders[i].msgData],
+            setupType.msgHeaders[i].date);
+          if (success !== 0) {
+            resolve(1);
+          }
+        }
+      }
+      resolve(0);
+    });
+  },
+
+  /**
+   * Create a new autocrypt key for every configured account and configure the account
+   * to use that key. The keys are not protected by a password.
+   *
+   * The creation is done in the background after waiting timeoutValue ms
+   * @param {Number} timeoutValue: number of miliseconds to wait before starting
+   *                               the process
+   */
+  createKeyForAllAccounts: function(timeoutValue = 1000) {
+    EnigmailLog.DEBUG("autoSetup.jsm: createKeyForAllAccounts()\n");
+    let self = this;
+
+    EnigmailTimer.setTimeout(async function _f() {
+      let msgAccountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(nsIMsgAccountManager);
+      let accounts = msgAccountManager.accounts;
+      let createdKeys = [];
+
+      for (let i = 0; i < accounts.length; i++) {
+        let account = accounts.queryElementAt(i, Ci.nsIMsgAccount);
+        let id = account.defaultIdentity;
+
+        if (id && id.email) {
+          let keyId = await self.createAutocryptKey(id.fullName, id.email);
+          EnigmailLog.DEBUG(`autoSetup.jsm: createKeyForAllAccounts: created key ${keyId}\n`);
+          if (keyId) {
+            let keyObj = EnigmailKeyRing.getKeyById(keyId);
+            if (keyObj) createdKeys.push(keyObj);
+            id.setBoolAttribute("enablePgp", true);
+            id.setCharAttribute("pgpkeyId", keyId);
+            id.setIntAttribute("pgpKeyMode", 1);
+            id.setBoolAttribute("pgpMimeMode", true);
+            id.setBoolAttribute("pgpSignEncrypted", true);
+          }
+        }
+      }
+
+      // upload created keys to WKD (if possible)
+      EnigmailWks.wksUpload(createdKeys, null);
+    }, timeoutValue);
+  },
+
+  /**
+   * Create a new autocrypt-complinant key
+   * The keys will not be protected by passwords.
+   *
+   * @param {String} userName:  Display name
+   * @param {String} userEmail: Email address
+   *
+   * @return {Promise<Boolean>}: Success (true = successful)
+   */
+  createAutocryptKey: function(userName, userEmail) {
+    return new Promise((resolve, reject) => {
+      EnigmailLog.DEBUG("autoSetup.jsm: createAutocryptKey()\n");
+
+      let keyType = "ECC",
+        keyLength = 0;
+
+      if (!EnigmailGpg.getGpgFeature("supports-ecc-keys")) {
+        // fallback for gpg < 2.1
+        keyLength = 4096;
+        keyType = "RSA";
+      }
+
+      let expiry = 1825, // 5 years
+        passphrase = "",
+        generateObserver = {
+          keyId: null,
+          backupLocation: null,
+          _state: 0,
+
+          onDataAvailable: function(data) {},
+          onStopRequest: function(exitCode) {
+            EnigmailLog.DEBUG("autoSetup.jsm: createAutocryptKey(): key generation complete\n");
+            resolve(generateObserver.keyId);
+          }
+        };
+
+      try {
+        let keygenRequest = EnigmailKeyRing.generateKey(userName, "", userEmail, expiry, keyLength, keyType, passphrase, generateObserver);
+      }
+      catch (ex) {
+        EnigmailLog.DEBUG("autoSetup.jsm: createAutocryptKey: error: " + ex.message);
+        resolve(null);
+      }
+    });
+  },
+
+  /**
+   * Configure Enigmail to use existing keys
+   */
+  applyExistingKeys: function() {
+    let msgAccountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(nsIMsgAccountManager);
+    let identities = msgAccountManager.allIdentities;
+
+    for (let i = 0; i < identities.length; i++) {
+      let id = identities.queryElementAt(i, Ci.nsIMsgIdentity);
+
+      if (id && id.email) {
+        let keyObj = EnigmailKeyRing.getSecretKeyByEmail(id.email);
+        if (keyObj) {
+          EnigmailLog.DEBUG(`autoSetup.jsm: applyExistingKeys: found key ${keyObj.keyId}\n`);
+          id.setBoolAttribute("enablePgp", true);
+          id.setCharAttribute("pgpkeyId", "0x" + keyObj.fpr);
+          id.setIntAttribute("pgpKeyMode", 1);
+          id.setBoolAttribute("pgpMimeMode", true);
+          id.setBoolAttribute("pgpSignEncrypted", true);
+        }
+      }
+    }
+  }
+};
+
+
+/**
+ * Recusrively go through all folders to get a flat array of all sub-folders
+ * starting with a parent folder.
+ *
+ * @param {nsIMsgFolder} folder:       the folder to scan
+ * @param {nsIMsgFolder} msgFolderArr: An array to be filled with all folders that contain messages
+ */
+
+function getMsgFolders(folder, msgFolderArr) {
+  if (folder.getTotalMessages(false) > 0) {
+    msgFolderArr.push(folder);
+  }
+
+  // add all subfolders
+  if (folder.hasSubFolders) {
+    let subFolders = folder.subFolders;
+    while (subFolders.hasMoreElements()) {
+      getMsgFolders(subFolders.getNext().QueryInterface(nsIMsgFolder), msgFolderArr);
+    }
+  }
+}
+
+// Util Function for Extracting manually added Headers
+function streamListener(callback) {
+  let streamListener = {
+    mAttachments: [],
+    mHeaders: [],
+    mBusy: true,
+
+    onStartRequest: function(aRequest) {
+      this.mAttachments = [];
+      this.mHeaders = [];
+      this.mBusy = true;
+
+      var channel = aRequest.QueryInterface(Components.interfaces.nsIChannel);
+      channel.URI.QueryInterface(Components.interfaces.nsIMsgMailNewsUrl);
+      channel.URI.msgHeaderSink = this; // adds this header sink interface to the channel
+    },
+    onStopRequest: function(aRequest, aStatusCode) {
+      callback();
+      this.mBusy = false; // if needed, you can poll this var to see if we are done collecting attachment details
+    },
+    onDataAvailable: function(aRequest, aInputStream, aOffset, aCount) {},
+    onStartHeaders: function() {},
+    onEndHeaders: function() {},
+    processHeaders: function(aHeaderNameEnumerator, aHeaderValueEnumerator, aDontCollectAddress) {
+      while (aHeaderNameEnumerator.hasMore()) {
+        this.mHeaders.push({
+          name: aHeaderNameEnumerator.getNext().toLowerCase(),
+          value: aHeaderValueEnumerator.getNext()
+        });
+      }
+    },
+    handleAttachment: function(aContentType, aUrl, aDisplayName, aUri, aIsExternalAttachment) {
+      if (aContentType == "text/html") {
+        return;
+      }
+      this.mAttachments.push({
+        contentType: aContentType,
+        url: aUrl,
+        displayName: aDisplayName,
+        uri: aUri,
+        isExternal: aIsExternalAttachment
+      });
+    },
+    onEndAllAttachments: function() {},
+    onEndMsgDownload: function(aUrl) {},
+    onEndMsgHeaders: function(aUrl) {},
+    onMsgHasRemoteContent: function(aMsgHdr) {},
+    getSecurityInfo: function() {},
+    setSecurityInfo: function(aSecurityInfo) {},
+    getDummyMsgHeader: function() {},
+
+    QueryInterface: function(aIID) {
+      if (aIID.equals(Components.interfaces.nsIStreamListener) ||
+        aIID.equals(Components.interfaces.nsIMsgHeaderSink) ||
+        aIID.equals(Components.interfaces.nsISupports)) {
+        return this;
+      }
+
+      throw Components.results.NS_NOINTERFACE;
+    }
+  };
+
+  return streamListener;
+}
+
+function getStreamedMessage(msgFolder, msgHeader) {
+  return new Promise((resolve, reject) => {
+    let msgURI = msgFolder.getUriForMsg(msgHeader);
+    var listener = streamListener(() => {
+      resolve(listener.mAttachments[0]);
+    });
+    let messenger = Components.classes["@mozilla.org/messenger;1"].createInstance(nsIMessenger);
+    let mms = messenger.messageServiceFromURI(msgURI).QueryInterface(nsIMsgMessageService);
+    mms.streamMessage(msgURI, listener, null, null, true, "filter");
+  });
+}
+
+function checkHeaders(headerObj, msgHeader, msgAuthor, accountEmail, msgFolder, returnMsgValue, msgHeaders) {
+  return new Promise(async (resolve, reject) => {
+    if (headerObj['autocrypt-setup-message'] && msgHeader.author == msgHeader.recipients) {
+
+      // To extract Attachement for Autocrypt Setup Message
+
+      returnMsgValue.attachment = await getStreamedMessage(msgFolder, msgHeader);
+
+      if (!returnMsgValue.acSetupMessage) {
+        returnMsgValue.value = 1;
+        returnMsgValue.acSetupMessage = msgHeader;
+      }
+      else if (returnMsgValue.acSetupMessage.date < msgHeader.date) {
+        returnMsgValue.acSetupMessage = msgHeader;
+      }
+
+    }
+    else if (msgAuthor == accountEmail &&
+      ("autocrypt" in headerObj)) {
+
+      let msgType = "Autocrypt";
+
+      let fromHeaderExist = null;
+      for (let j = 0; j < msgHeaders.length; j++) {
+        if (msgHeaders[j].fromAddr == msgAuthor) {
+          fromHeaderExist = msgHeaders[j];
+          break;
+        }
+      }
+
+      if (fromHeaderExist === null) {
+        let dateTime = new Date(0);
+        try {
+          dateTime = jsmime.headerparser.parseDateHeader(headerObj.date);
+        }
+        catch (x) {}
+
+        let addHeader = {
+          fromAddr: msgAuthor,
+          msgType: msgType,
+          msgData: headerObj.autocrypt,
+          date: headerObj.date,
+          dateTime: dateTime
+        };
+        msgHeaders.push(addHeader);
+      }
+      else {
+        let dateTime = new Date(0);
+        try {
+          dateTime = jsmime.headerparser.parseDateHeader(headerObj.date);
+        }
+        catch (x) {}
+        if (dateTime > fromHeaderExist.dateTime) {
+          fromHeaderExist.msgData = headerObj.autocrypt;
+          fromHeaderExist.date = headerObj.date;
+          fromHeaderExist.msgType = msgType;
+          fromHeaderExist.dateTime = dateTime;
+        }
+      }
+    }
+
+    resolve({
+      'returnMsgValue': returnMsgValue,
+      'msgHeaders': msgHeaders
+    });
+  });
+}
+
+function getStreamedHeaders(msgURI, mms) {
+
+  return new Promise((resolve, reject) => {
+    let headers = Cc["@mozilla.org/messenger/mimeheaders;1"].createInstance(Ci.nsIMimeHeaders);
+    let headerObj = {};
+    try {
+      mms.streamHeaders(msgURI, EnigmailStreams.newStringStreamListener(aRawString => {
+        try {
+          //EnigmailLog.DEBUG(`getStreamedHeaders: ${aRawString}\n`);
+          headers.initialize(aRawString);
+
+          let i = headers.headerNames;
+          while (i.hasMore()) {
+            let hdrName = i.getNext().toLowerCase();
+
+            let hdrValue = headers.extractHeader(hdrName, true);
+            headerObj[hdrName] = hdrValue;
+          }
+
+          if ("autocrypt" in headerObj) {
+            let acHeader = headers.extractHeader("autocrypt", false);
+            acHeader = acHeader.replace(/keydata=/i, 'keydata="') + '"';
+
+            let paramArr = EnigmailMime.getAllParameters(acHeader);
+            paramArr.keydata = paramArr.keydata.replace(/[\r\n\t ]/g, "");
+
+            headerObj.autocrypt = "";
+            for (i in paramArr) {
+              if (headerObj.autocrypt.length > 0) {
+                headerObj.autocrypt += "; ";
+              }
+              headerObj.autocrypt += `${i}="${paramArr[i]}"`;
+            }
+          }
+        }
+        catch (e) {
+          reject({});
+          EnigmailLog.DEBUG("autoSetup.jsm: getStreamedHeaders: Error: " + e + "\n");
+        }
+        resolve(headerObj);
+      }), null, false);
+    }
+    catch (e) {
+      reject({});
+      EnigmailLog.DEBUG("autoSetup.jsm: getStreamedHeaders: Error: " + e + "\n");
+    }
+  });
+}
+
+
+function getCurrentTime() {
+  return new Date().getTime() / 1000;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/autocrypt.jsm
@@ -0,0 +1,1077 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ *  Module for dealing with received Autocrypt headers, level 0
+ *  See details at https://github.com/mailencrypt/autocrypt
+ */
+
+var EXPORTED_SYMBOLS = ["EnigmailAutocrypt"];
+
+const Cr = Components.results;
+
+Components.utils.importGlobalProperties(["crypto"]);
+
+const jsmime = ChromeUtils.import("resource:///modules/jsmime.jsm").jsmime;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailMime = ChromeUtils.import("chrome://openpgp/content/modules/mime.jsm").EnigmailMime;
+const EnigmailSqliteDb = ChromeUtils.import("chrome://openpgp/content/modules/sqliteDb.jsm").EnigmailSqliteDb;
+const PromiseUtils = ChromeUtils.import("resource://gre/modules/PromiseUtils.jsm").PromiseUtils;
+const EnigmailTimer = ChromeUtils.import("chrome://openpgp/content/modules/timer.jsm").EnigmailTimer;
+const EnigmailKey = ChromeUtils.import("chrome://openpgp/content/modules/key.jsm").EnigmailKey;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailOpenPGP = ChromeUtils.import("chrome://openpgp/content/modules/openpgp.jsm").EnigmailOpenPGP;
+const EnigmailRNG = ChromeUtils.import("chrome://openpgp/content/modules/rng.jsm").EnigmailRNG;
+const EnigmailSend = ChromeUtils.import("chrome://openpgp/content/modules/send.jsm").EnigmailSend;
+const EnigmailStreams = ChromeUtils.import("chrome://openpgp/content/modules/streams.jsm").EnigmailStreams;
+const EnigmailArmor = ChromeUtils.import("chrome://openpgp/content/modules/armor.jsm").EnigmailArmor;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailRules = ChromeUtils.import("chrome://openpgp/content/modules/rules.jsm").EnigmailRules;
+const EnigmailKeyEditor = ChromeUtils.import("chrome://openpgp/content/modules/keyEditor.jsm").EnigmailKeyEditor;
+const EnigmailStdlib = ChromeUtils.import("chrome://openpgp/content/modules/stdlib.jsm").EnigmailStdlib;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
+
+var gCreatedSetupIds = [];
+
+var EnigmailAutocrypt = {
+  /**
+   * Process the "Autocrypt:" header and if successful store the update in the database
+   *
+   * @param {String} fromAddr:               Address of sender (From: header)
+   * @param {Array of String} headerDataArr: all instances of the Autocrypt: header found in the message
+   * @param {String or Number} dateSent:     "Date:" field of the message as readable string or in seconds after 1970-01-01
+   * @param {Boolean} autoCryptEnabled:      if true, autocrypt is enabled for the context of the message
+   *
+   * @return {Promise<Number>}: success: 0 = success, 1+ = failure
+   */
+  processAutocryptHeader: async function(fromAddr, headerDataArr, dateSent, autoCryptEnabled = false, isGossip = false) {
+    EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader(): from=" + fromAddr + "\n");
+    let conn;
+
+    try {
+      // critical parameters: {param: mandatory}
+      const CRITICAL = {
+        addr: true,
+        keydata: true,
+        type: false, // That's actually oboslete according to the Level 1 spec.
+        "prefer-encrypt": false
+      };
+
+      try {
+        fromAddr = EnigmailFuncs.stripEmail(fromAddr).toLowerCase();
+      }
+      catch (ex) {
+        throw "processAutocryptHeader error " + ex;
+      }
+      let foundTypes = {};
+      let paramArr = [];
+
+      for (let hdrNum = 0; hdrNum < headerDataArr.length; hdrNum++) {
+
+        let hdr = headerDataArr[hdrNum].replace(/[\r\n \t]/g, "");
+        let k = hdr.search(/keydata=/);
+        if (k > 0) {
+          let d = hdr.substr(k);
+          if (d.search(/"/) < 0) {
+            hdr = hdr.replace(/keydata=/, 'keydata="') + '"';
+          }
+        }
+
+        paramArr = EnigmailMime.getAllParameters(hdr);
+
+        for (let i in CRITICAL) {
+          if (CRITICAL[i]) {
+            // found mandatory parameter
+            if (!(i in paramArr)) {
+              EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: cannot find param '" + i + "'\n");
+              return 1; // do nothing if not all mandatory parts are present
+            }
+          }
+        }
+
+        for (let i in paramArr) {
+          if (i.substr(0, 1) !== "_") {
+            if (!(i in CRITICAL)) {
+              EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: unknown critical param " + i + "\n");
+              return 2; // do nothing if an unknown critical parameter is found
+            }
+          }
+        }
+
+        paramArr.addr = paramArr.addr.toLowerCase();
+
+        if (fromAddr !== paramArr.addr) {
+          EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: from Addr " + fromAddr + " != " + paramArr.addr.toLowerCase() + "\n");
+
+          return 3;
+        }
+
+        if (!("type" in paramArr)) {
+          paramArr.type = (isGossip ? "1g" : "1");
+        }
+        else {
+          paramArr.type = paramArr.type.toLowerCase();
+          if (paramArr.type !== "1") {
+            EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: unknown type " + paramArr.type + "\n");
+            return 4; // we currently only support 1 (=OpenPGP)
+          }
+        }
+
+        try {
+          let keyData = atob(paramArr.keydata);
+        }
+        catch (ex) {
+          EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: key is not base64-encoded\n");
+          return 5;
+        }
+
+        if (paramArr.type in foundTypes) {
+          EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: duplicate header for type=" + paramArr.type + "\n");
+          return 6; // do not process anything if more than one Autocrypt header for the same type is found
+        }
+
+        foundTypes[paramArr.type] = 1;
+      }
+
+      if (isGossip) {
+        paramArr["prefer-encrypt"] = "nopreference";
+      }
+
+      if (!("prefer-encrypt" in paramArr)) {
+        paramArr["prefer-encrypt"] = "nopreference";
+      }
+
+      let lastDate;
+      if (typeof dateSent === "string") {
+        lastDate = jsmime.headerparser.parseDateHeader(dateSent);
+      }
+      else {
+        lastDate = new Date(dateSent * 1000);
+      }
+      let now = new Date();
+      if (lastDate > now) {
+        lastDate = now;
+      }
+      paramArr.dateSent = lastDate;
+
+      if (("_enigmail_artificial" in paramArr) && (paramArr._enigmail_artificial === "yes")) {
+        if ("_enigmail_fpr" in paramArr) {
+          paramArr.fpr = paramArr._enigmail_fpr;
+        }
+
+        paramArr.keydata = "";
+        paramArr.autocryptDate = 0;
+      }
+      else {
+        paramArr.autocryptDate = lastDate;
+      }
+
+      try {
+        conn = await EnigmailSqliteDb.openDatabase();
+      }
+      catch (ex) {
+        EnigmailLog.DEBUG("autocrypt.jsm: processAutocryptHeader: could not open database\n");
+        return 7;
+      }
+
+      let resultObj = await findUserRecord(conn, [fromAddr], paramArr.type);
+      EnigmailLog.DEBUG("autocrypt.jsm: got " + resultObj.numRows + " rows\n");
+      if (resultObj.data.length === 0) {
+        await appendUser(conn, paramArr);
+      }
+      else {
+        await updateUser(conn, paramArr, resultObj.data, autoCryptEnabled);
+      }
+
+      EnigmailLog.DEBUG("autocrypt.jsm: OK - closing connection\n");
+      conn.close();
+      return 0;
+    }
+    catch (err) {
+      EnigmailLog.DEBUG("autocrypt.jsm: error - closing connection: " + err + "\n");
+      conn.close();
+      return 8;
+    }
+  },
+
+  /**
+   * Import autocrypt OpenPGP keys into regular keyring for a given list of email addresses
+   * @param {Array of String} emailAddr: email addresses
+   * @param {Boolean} acceptGossipKeys: import keys received via gossip
+   *
+   * @return {Promise<Array of keyObj>}
+   */
+  importAutocryptKeys: async function(emailAddr, acceptGossipKeys = false) {
+    EnigmailLog.DEBUG("autocrypt.jsm: importAutocryptKeys()\n");
+
+    let keyArr = await this.getOpenPGPKeyForEmail(emailAddr);
+    if (!keyArr) return [];
+
+    let importedKeys = [];
+    let now = new Date();
+    let prev = null;
+
+    for (let i = 0; i < keyArr.length; i++) {
+      if (prev && prev.email === keyArr[i].email && prev.type === "1" && keyArr[i].type === "1g") {
+        // skip if we have "gossip" key preceeded by a "regular" key
+        continue;
+      }
+      if (!acceptGossipKeys && keyArr[i].type === "1g") {
+        EnigmailLog.DEBUG(`autocrypt.jsm: importAutocryptKeys: skipping gossip key for ${keyArr[i].email}\n`);
+        continue;
+      }
+
+      prev = keyArr[i];
+      if ((now - keyArr[i].lastAutocrypt) / (1000 * 60 * 60 * 24) < 366) {
+        // only import keys received less than 12 months ago
+        try {
+          let keyData = atob(keyArr[i].keyData);
+          if (keyData.length > 1) {
+            importedKeys = await this.applyKeyFromKeydata(keyData, keyArr[i].email, keyArr[i].state, keyArr[i].type);
+          }
+        }
+        catch (ex) {
+          EnigmailLog.DEBUG("autocrypt.jsm importAutocryptKeys: exception " + ex.toString() + "\n");
+        }
+      }
+    }
+
+    return importedKeys;
+  },
+
+  /**
+   * Import given key data and set the per-recipient rule accordingly
+   *
+   * @param {String} keyData - String key data (BLOB, binary form)
+   * @param {String} email - email address associated with key
+   * @param {String} autocryptState - mutual or nopreference
+   * @param {String} type - autocrypt header type (1 / 1g)
+   *
+   * @return {Promise<Array of keys>} list of imported keys
+   */
+  applyKeyFromKeydata: async function(keyData, email, autocryptState, type) {
+    let keysObj = {};
+    let importedKeys = [];
+
+    let pubkey = EnigmailOpenPGP.enigmailFuncs.bytesToArmor(EnigmailOpenPGP.armor.public_key, keyData);
+    await EnigmailKeyRing.importKeyAsync(null, false, pubkey, "", {}, keysObj);
+
+    if (keysObj.value) {
+      importedKeys = importedKeys.concat(keysObj.value);
+
+      if (keysObj.value.length > 0) {
+        let key = EnigmailKeyRing.getKeyById(keysObj.value[0]);
+
+        // enable encryption if state (prefer-encrypt) is "mutual";
+        // otherwise, disable it explicitely
+        let signEncrypt = (autocryptState === "mutual" ? 1 : 0);
+
+        if (key && key.fpr) {
+          let ruleObj = {
+            email: `{${EnigmailConstants.AC_RULE_PREFIX}${email}}`,
+            keyList: `0x${key.fpr}`,
+            sign: signEncrypt,
+            encrypt: signEncrypt,
+            pgpMime: 2,
+            flags: 0
+          };
+
+          EnigmailRules.insertOrUpdateRule(ruleObj);
+          await this.setKeyImported(null, email);
+        }
+      }
+    }
+
+    return importedKeys;
+  },
+
+  /**
+   * Update key in the Autocrypt database to mark it "imported in keyring"
+   */
+  setKeyImported: async function(connection, email) {
+    EnigmailLog.DEBUG(`autocrypt.jsm: setKeyImported(${email})\n`);
+    try {
+      let conn = connection;
+      if (!conn) {
+        conn = await EnigmailSqliteDb.openDatabase();
+      }
+      let updateStr = "update autocrypt_keydata set keyring_inserted = '1' where email = :email;";
+
+      let updateObj = {
+        email: email.toLowerCase()
+      };
+
+      await new Promise((resolve, reject) =>
+        conn.executeTransaction(function _trx() {
+          conn.execute(updateStr, updateObj).then(r => {
+            resolve(r);
+          }).catch(err => {
+            EnigmailLog.DEBUG(`autocrypt.jsm: setKeyImported: error ${err}\n`);
+            reject(err);
+          });
+        }));
+
+      if (!connection) conn.close();
+    }
+    catch (err) {
+      EnigmailLog.DEBUG(`autocrypt.jsm: setKeyImported: error ${err}\n`);
+      throw err;
+    }
+  },
+
+  /**
+   * Go through all emails in the autocrypt store and determine which keys already
+   * have a per-recipient rule
+   */
+  updateAllImportedKeys: async function() {
+    EnigmailLog.DEBUG(`autocrypt.jsm: updateAllImportedKeys()\n`);
+    try {
+      let conn = await EnigmailSqliteDb.openDatabase();
+
+      let rows = [];
+      await conn.execute("select email, type from autocrypt_keydata where type = '1';", {},
+        function _onRow(record) {
+          rows.push(record.getResultByName("email"));
+        });
+
+      for (let i in rows) {
+        let r = EnigmailRules.getRuleByEmail(`${EnigmailConstants.AC_RULE_PREFIX}${rows[i]}`);
+        if (r) {
+          await this.setKeyImported(conn, rows[i], "1");
+        }
+
+      }
+      EnigmailLog.DEBUG(`autocrypt.jsm: updateAllImportedKeys done\n`);
+
+      conn.close();
+    }
+    catch (err) {
+      EnigmailLog.DEBUG(`autocrypt.jsm: updateAllImportedKeys: error ${err}\n`);
+      throw err;
+    }
+  },
+
+  /**
+   * Find an autocrypt OpenPGP key for a given list of email addresses
+   * @param emailAddr: Array of String - email addresses
+   *
+   * @return Promise(<Array of Object>)
+   *      Object: {fpr, keyData, lastAutocrypt}
+   */
+  getOpenPGPKeyForEmail: function(emailAddr) {
+    EnigmailLog.DEBUG("autocrypt.jsm: getOpenPGPKeyForEmail(" + emailAddr.join(",") + ")\n");
+
+    let conn;
+
+    return new Promise((resolve, reject) => {
+      EnigmailSqliteDb.openDatabase().then(
+        function onConnection(connection) {
+          conn = connection;
+          return findUserRecord(conn, emailAddr, "1,1g");
+        },
+        function onError(error) {
+          EnigmailLog.DEBUG("autocrypt.jsm: getOpenPGPKeyForEmail: could not open database\n");
+          reject("getOpenPGPKeyForEmail1 error " + error);
+        }
+      ).then(
+        function gotData(resultObj) {
+          EnigmailLog.DEBUG("autocrypt.jsm: getOpenPGPKeyForEmail got " + resultObj.numRows + " rows\n");
+          conn.close();
+
+          if (resultObj.data.length === 0) {
+            resolve(null);
+          }
+          else {
+            let retArr = [];
+            for (let i in resultObj.data) {
+              let record = resultObj.data[i];
+              retArr.push({
+                email: record.getResultByName("email"),
+                fpr: record.getResultByName("fpr"),
+                keyData: record.getResultByName("keydata"),
+                state: record.getResultByName("state"),
+                type: record.getResultByName("type"),
+                lastAutocrypt: new Date(record.getResultByName("last_seen_autocrypt"))
+              });
+            }
+
+            resolve(retArr);
+          }
+        }
+      ).
+      catch((err) => {
+        conn.close();
+        reject("getOpenPGPKeyForEmail: error " + err);
+      });
+    });
+  },
+
+  /**
+   * Create Autocrypt Setup Message
+   *
+   * @param identity: Object - nsIMsgIdentity
+   *
+   * @return Promise({str, passwd}):
+   *             msg:    String - complete setup message
+   *             passwd: String - backup password
+   */
+  // needs rewrite, OpenPGP.js not available
+  /*
+  createSetupMessage: function(identity) {
+    EnigmailLog.DEBUG("autocrypt.jsm: createSetupMessage()\n");
+
+    return new Promise((resolve, reject) => {
+      let keyId = "";
+      let key;
+      try {
+
+        if (!EnigmailCore.getService(null, false)) {
+          reject(0);
+          return;
+        }
+
+        if (identity.getIntAttribute("pgpKeyMode") === 1) {
+          keyId = identity.getCharAttribute("pgpkeyId");
+        }
+
+        if (keyId.length > 0) {
+          key = EnigmailKeyRing.getKeyById(keyId);
+        }
+        else {
+          key = EnigmailKeyRing.getSecretKeyByUserId(identity.email);
+        }
+
+        if (!key) {
+          EnigmailLog.DEBUG("autocrypt.jsm: createSetupMessage: no key found for " + identity.email + "\n");
+          reject(1);
+          return;
+        }
+
+        let keyData = key.getSecretKey(true).keyData;
+
+        if (!keyData || keyData.length === 0) {
+          EnigmailLog.DEBUG("autocrypt.jsm: createSetupMessage: no key found for " + identity.email + "\n");
+          reject(1);
+          return;
+        }
+
+        let ac = EnigmailFuncs.getAccountForIdentity(identity);
+        let preferEncrypt = ac.incomingServer.getIntValue("acPreferEncrypt") > 0 ? "mutual" : "nopreference";
+
+        let innerMsg = EnigmailArmor.replaceArmorHeaders(keyData, {
+          'Autocrypt-Prefer-Encrypt': preferEncrypt
+        }) + '\r\n';
+
+        let bkpCode = createBackupCode();
+        let enc = {
+          // TODO: message: EnigmailOpenPGP.openpgp.message.fromText(innerMsg),
+          passwords: bkpCode,
+          armor: true
+        };
+
+        // create symmetrically encrypted message
+        // TODO: EnigmailOpenPGP.openpgp.encrypt(enc).then(msg => {
+          let msgData = EnigmailArmor.replaceArmorHeaders(msg.data, {
+            'Passphrase-Format': 'numeric9x4',
+            'Passphrase-Begin': bkpCode.substr(0, 2)
+          }).replace(/\n/g, "\r\n");
+
+          let m = createBackupOuterMsg(identity.email, msgData);
+          resolve({
+            msg: m,
+            passwd: bkpCode
+          });
+        }).catch(e => {
+          EnigmailLog.DEBUG("autocrypt.jsm: createSetupMessage: error " + e + "\n");
+          reject(2);
+        });
+      }
+      catch (ex) {
+        EnigmailLog.DEBUG("autocrypt.jsm: createSetupMessage: error " + ex.toString() + "\n");
+        reject(4);
+      }
+    });
+  },
+  */
+
+  /**
+   * Create and send the Autocrypt Setup Message to yourself
+   * The message is sent asynchronously.
+   *
+   * @param identity: Object - nsIMsgIdentity
+   *
+   * @return Promise(passwd):
+   *   passwd: String - backup password
+   *
+   */
+  /*
+  sendSetupMessage: function(identity) {
+    EnigmailLog.DEBUG("autocrypt.jsm: sendSetupMessage()\n");
+
+    let self = this;
+    return new Promise((resolve, reject) => {
+      self.createSetupMessage(identity).then(res => {
+        let composeFields = Cc["@mozilla.org/messengercompose/composefields;1"].createInstance(Ci.nsIMsgCompFields);
+        composeFields.characterSet = "UTF-8";
+        composeFields.messageId = EnigmailRNG.generateRandomString(27) + "-enigmail";
+        composeFields.from = identity.email;
+        composeFields.to = identity.email;
+        gCreatedSetupIds.push(composeFields.messageId);
+
+        let now = new Date();
+        let mimeStr = "Message-Id: " + composeFields.messageId + "\r\n" +
+          "Date: " + now.toUTCString() + "\r\n" + res.msg;
+
+        if (EnigmailSend.sendMessage(mimeStr, composeFields, null)) {
+          resolve(res.passwd);
+        }
+        else {
+          reject(99);
+        }
+      });
+    });
+  },
+  */
+
+
+  /**
+   * get the data of the attachment of a setup message
+   *
+   * @param attachmentUrl: String - URL of the attachment
+   *
+   * @return Promise(Object):
+   *            attachmentData:   String - complete attachment data
+   *            passphraseFormat: String - extracted format from the header (e.g. numeric9x4) [optional]
+   *            passphraseHint:   String - 1st two digits of the password [optional]
+   */
+  getSetupMessageData: function(attachmentUrl) {
+    EnigmailLog.DEBUG("autocrypt.jsm: getSetupMessageData()\n");
+
+    return new Promise((resolve, reject) => {
+      let s = EnigmailStreams.newStringStreamListener(data => {
+        let start = {},
+          end = {};
+        let msgType = EnigmailArmor.locateArmoredBlock(data, 0, "", start, end, {});
+
+        if (msgType === "MESSAGE") {
+          EnigmailLog.DEBUG("autocrypt.jsm: getSetupMessageData: got backup key\n");
+          let armorHdr = EnigmailArmor.getArmorHeaders(data);
+
+          let passphraseFormat = "generic";
+          if ("passphrase-format" in armorHdr) {
+            passphraseFormat = armorHdr["passphrase-format"];
+          }
+          let passphraseHint = "";
+          if ("passphrase-begin" in armorHdr) {
+            passphraseHint = armorHdr["passphrase-begin"];
+          }
+
+          resolve({
+            attachmentData: data,
+            passphraseFormat: passphraseFormat,
+            passphraseHint: passphraseHint
+          });
+        }
+        else {
+          reject("getSetupMessageData");
+        }
+      });
+
+      let channel = EnigmailStreams.createChannel(attachmentUrl);
+      channel.asyncOpen(s, null);
+    });
+  },
+
+  /**
+   * @return Promise(Object):
+   *          fpr:           String - FPR of the imported key
+   *          preferEncrypt: String - Autocrypt preferEncrypt value (e.g. mutual)
+   */
+  // needs rewrite, OpenPGP.js not available
+  /*
+  handleBackupMessage: function(passwd, attachmentData, fromAddr) {
+    EnigmailLog.DEBUG("autocrypt.jsm: handleBackupMessage()\n");
+
+    return new Promise((resolve, reject) => {
+      let start = {},
+        end = {};
+      let msgType = EnigmailArmor.locateArmoredBlock(attachmentData, 0, "", start, end, {});
+
+      // TODO: EnigmailOpenPGP.openpgp.message.readArmored(attachmentData.substring(start.value, end.value)).then(encMessage => {
+          let enc = {
+            message: encMessage,
+            passwords: [passwd],
+            format: 'utf8'
+          };
+
+          // TODO: return EnigmailOpenPGP.openpgp.decrypt(enc);
+        })
+        .then(msg => {
+          EnigmailLog.DEBUG("autocrypt.jsm: handleBackupMessage: data: " + msg.data.length + "\n");
+
+          let setupData = importSetupKey(msg.data);
+          if (setupData) {
+            EnigmailKeyEditor.setKeyTrust(null, "0x" + setupData.fpr, "5", function(returnCode) {
+              if (returnCode === 0) {
+                let id = EnigmailStdlib.getIdentityForEmail(EnigmailFuncs.stripEmail(fromAddr).toLowerCase());
+                let ac = EnigmailFuncs.getAccountForIdentity(id.identity);
+                ac.incomingServer.setBoolValue("enableAutocrypt", true);
+                ac.incomingServer.setIntValue("acPreferEncrypt", (setupData.preferEncrypt === "mutual" ? 1 : 0));
+                id.identity.setCharAttribute("pgpkeyId", "0x" + setupData.fpr);
+                id.identity.setBoolAttribute("enablePgp", true);
+                id.identity.setBoolAttribute("pgpSignEncrypted", true);
+                id.identity.setBoolAttribute("pgpMimeMode", true);
+                id.identity.setIntAttribute("pgpKeyMode", 1);
+                resolve(setupData);
+              }
+              else {
+                reject("keyImportFailed");
+              }
+            });
+          }
+          else {
+            reject("keyImportFailed");
+          }
+        }).
+      catch(err => {
+        reject("wrongPasswd");
+      });
+    });
+  },
+  */
+
+  /**
+   * Determine if a message id was self-created (only during same TB session)
+   */
+  isSelfCreatedSetupMessage: function(messageId) {
+    return (gCreatedSetupIds.indexOf(messageId) >= 0);
+  },
+
+  /**
+   * Check if an account is set up with OpenPGP and if the configured key is valid
+   *
+   * @param emailAddr: String - email address identifying the account
+   *
+   * @return Boolean: true: account is valid / false: OpenPGP not configured or key not valid
+   */
+  isAccountSetupForPgp: function(emailAddr) {
+    let id = EnigmailStdlib.getIdentityForEmail(EnigmailFuncs.stripEmail(emailAddr).toLowerCase());
+    let keyObj = null;
+
+    if (!(id && id.identity)) return false;
+    if (!id.identity.getBoolAttribute("enablePgp")) return false;
+
+    if (id.identity.getIntAttribute("pgpKeyMode") === 1) {
+      keyObj = EnigmailKeyRing.getKeyById(id.identity.getCharAttribute("pgpkeyId"));
+    }
+    else {
+      keyObj = EnigmailKeyRing.getSecretKeyByUserId(emailAddr);
+    }
+
+    if (!keyObj) return false;
+    if (!keyObj.secretAvailable) return false;
+
+    let o = keyObj.getEncryptionValidity();
+    if (!o.keyValid) return false;
+    o = keyObj.getSigningValidity();
+    if (!o.keyValid) return false;
+
+    return true;
+  },
+
+  /**
+   * Delete the record for a user from the autocrypt keystore
+   * The record with the highest precedence is deleted (i.e. type=1 before type=1g)
+   */
+  deleteUser: async function(email, type) {
+    EnigmailLog.DEBUG(`autocrypt.jsm: deleteUser(${email})\n`);
+    let conn = await EnigmailSqliteDb.openDatabase();
+
+    let updateStr = "delete from autocrypt_keydata where email = :email and type = :type";
+    let updateObj = {
+      email: email,
+      type: type
+    };
+
+    await new Promise((resolve, reject) => {
+      conn.executeTransaction(function _trx() {
+        conn.execute(updateStr, updateObj).then(
+          function _ok() {
+            resolve();
+          }
+        ).catch(function _err() {
+          reject("update failed");
+        });
+      });
+    });
+    EnigmailLog.DEBUG(" deletion complete\n");
+
+    conn.close();
+  }
+
+};
+
+/**
+ * Find the database record for a given email address and type
+ *
+ * @param connection: Object - SQLite connection
+ * @param emails      Array of String - Email addresses to search
+ * @param type:       String - types to search (in lowercase), separated by comma
+ *
+ * @return {Promise<Object>}:
+ *   numRows: number of results
+ *   data:    array of RowObject. Query columns using data[i].getResultByName(columnName);
+ */
+async function findUserRecord(connection, emails, type) {
+  EnigmailLog.DEBUG("autocrypt.jsm: findUserRecord\n");
+
+  let data = [];
+  let t = type.split(/[ ,]+/);
+
+  let queryParam = {
+    e0: emails[0],
+    t0: t[0]
+  };
+
+  let numRows = 0;
+
+  let search = ":e0";
+  for (let i = 1; i < emails.length; i++) {
+    search += ", :e" + i;
+    queryParam["e" + i] = emails[i].toLowerCase();
+  }
+
+  let typeParam = ":t0";
+  for (let i = 1; i < t.length; i++) {
+    typeParam += ", :t" + i;
+    queryParam["t" + i] = t[i];
+  }
+
+  try {
+    await connection.execute(
+      "select * from autocrypt_keydata where email in (" + search + ") and type in (" + typeParam + ") order by email, type", queryParam,
+      function _onRow(row) {
+        EnigmailLog.DEBUG("autocrypt.jsm: findUserRecord - got row\n");
+        data.push(row);
+        ++numRows;
+      });
+  }
+  catch (x) {
+    EnigmailLog.DEBUG(`autocrypt.jsm: findUserRecord: error ${x}\n`);
+    throw x;
+  }
+
+  return {
+    data: data,
+    numRows: numRows
+  };
+}
+
+/**
+ * Create new database record for an Autorypt header
+ *
+ * @param connection: Object - SQLite connection
+ * @param paramsArr:  Object - the Autocrypt header parameters
+ *
+ * @return Promise
+ */
+async function appendUser(connection, paramsArr) {
+  EnigmailLog.DEBUG("autocrypt.jsm: appendUser(" + paramsArr.addr + ")\n");
+
+  if (!("fpr" in paramsArr)) {
+    await getFprForKey(paramsArr);
+  }
+
+  return new Promise((resolve, reject) => {
+
+    if (paramsArr.autocryptDate == 0) {
+      // do not insert record for non-autocrypt mail
+      resolve();
+      return;
+    }
+
+    connection.executeTransaction(function _trx() {
+      connection.execute("insert into autocrypt_keydata (email, keydata, fpr, type, last_seen_autocrypt, last_seen, state) values " +
+        "(:email, :keyData, :fpr, :type, :lastAutocrypt, :lastSeen, :state)", {
+          email: paramsArr.addr.toLowerCase(),
+          keyData: paramsArr.keydata,
+          fpr: ("fpr" in paramsArr ? paramsArr.fpr : ""),
+          type: paramsArr.type,
+          lastAutocrypt: paramsArr.dateSent.toJSON(),
+          lastSeen: paramsArr.dateSent.toJSON(),
+          state: paramsArr["prefer-encrypt"]
+        }).then(
+        function _ok() {
+          EnigmailLog.DEBUG("autocrypt.jsm: appendUser - OK\n");
+          resolve();
+        }
+      ).catch(function _err() {
+        reject("appendUser");
+      });
+    });
+  });
+}
+
+/**
+ * Update the record for an email address and type, if the email we got is newer
+ * than the latest record we already stored
+ *
+ * @param connection: Object - SQLite connection
+ * @param paramsArr:  Object - the Autocrypt header parameters
+ * @param resultRows: Array of mozIStorageRow - records stored in the database
+ * @param autoCryptEnabled: Boolean: is autocrypt enabled for this transaction
+ *
+ * @return Promise
+ */
+async function updateUser(connection, paramsArr, resultRows, autoCryptEnabled) {
+  EnigmailLog.DEBUG("autocrypt.jsm: updateUser\n");
+
+  let currData = resultRows[0];
+  let deferred = PromiseUtils.defer();
+
+  let lastSeen = new Date(currData.getResultByName("last_seen"));
+  let lastAutocrypt = new Date(currData.getResultByName("last_seen_autocrypt"));
+  let notGossip = (currData.getResultByName("state") !== "gossip");
+  let currentKeyData = currData.getResultByName("keydata");
+  let isKeyInKeyring = (currData.getResultByName("keyring_inserted") === "1");
+
+  if (lastSeen >= paramsArr.dateSent ||
+    (paramsArr["prefer-encrypt"] === "gossip" && notGossip)) {
+    EnigmailLog.DEBUG("autocrypt.jsm: updateUser: not a relevant new latest message\n");
+
+    return;
+  }
+
+  EnigmailLog.DEBUG("autocrypt.jsm: updateUser: updating latest message\n");
+
+  let updateStr;
+  let updateObj;
+
+  if (paramsArr.autocryptDate > 0) {
+    lastAutocrypt = paramsArr.autocryptDate;
+    if (!("fpr" in paramsArr)) {
+      await getFprForKey(paramsArr);
+    }
+
+    updateStr = "update autocrypt_keydata set state = :state, keydata = :keyData, last_seen_autocrypt = :lastAutocrypt, " +
+      "fpr = :fpr, last_seen = :lastSeen where email = :email and type = :type";
+    updateObj = {
+      email: paramsArr.addr.toLowerCase(),
+      state: paramsArr["prefer-encrypt"],
+      keyData: paramsArr.keydata,
+      fpr: ("fpr" in paramsArr ? paramsArr.fpr : ""),
+      type: paramsArr.type,
+      lastAutocrypt: lastAutocrypt.toJSON(),
+      lastSeen: paramsArr.dateSent.toJSON()
+    };
+  }
+  else {
+    updateStr = "update autocrypt_keydata set state = :state, last_seen = :lastSeen where email = :email and type = :type";
+    updateObj = {
+      email: paramsArr.addr.toLowerCase(),
+      state: paramsArr["prefer-encrypt"],
+      type: paramsArr.type,
+      lastSeen: paramsArr.dateSent.toJSON()
+    };
+  }
+
+  if (!("fpr" in paramsArr)) {
+    await getFprForKey(paramsArr);
+  }
+
+  await new Promise((resolve, reject) => {
+    connection.executeTransaction(function _trx() {
+      connection.execute(updateStr, updateObj).then(
+        function _ok() {
+          resolve();
+        }
+      ).catch(function _err() {
+        reject("update failed");
+      });
+    });
+  });
+
+  if (autoCryptEnabled && isKeyInKeyring && (currentKeyData !== paramsArr.keydata)) {
+    await updateKeyIfNeeded(paramsArr.addr.toLowerCase(), paramsArr.keydata, paramsArr.fpr, paramsArr.type, paramsArr["prefer-encrypt"]);
+  }
+
+  return;
+}
+
+
+/**
+ * Determine if a key in the keyring should be replaced by a new (or updated) key
+ * @param {String} email - Email address
+ * @param {String} keydata - new keydata to import
+ * @param {String} fpr - fingerprint of new key
+ * @param {String} keyType - key type (1 / 1g)
+ * @param {String} autocryptState - mutual or nopreference
+ *
+ * @return {Promise<Boolean>} - key updated
+ */
+async function updateKeyIfNeeded(email, keydata, fpr, keyType, autocryptState) {
+  let ruleNode = EnigmailRules.getRuleByEmail(EnigmailConstants.AC_RULE_PREFIX + email);
+  if (!ruleNode) return false;
+
+  let doImport = false;
+
+  let currentKeyId = ruleNode.getAttribute("keyList");
+  if (`0x${fpr}` === currentKeyId || keyType === "1") {
+    doImport = true;
+  }
+  else {
+    // Gossip keys
+    let keyObj = EnigmailKeyRing.getKeyById(currentKeyId);
+    let encOk = keyObj.getEncryptionValidity().keyValid;
+
+    if (!encOk) {
+      // current key is not valid anymore
+      doImport = true;
+    }
+  }
+
+  if (doImport) {
+    await EnigmailAutocrypt.applyKeyFromKeydata(atob(keydata), email, autocryptState, keyType);
+  }
+
+  return doImport;
+}
+
+/**
+ * Set the fpr attribute for a given key parameter object
+ */
+async function getFprForKey(paramsArr) {
+  let keyData = atob(paramsArr.keydata);
+
+  const cApi = EnigmailCryptoAPI();
+
+  try {
+    let keyInfo = await cApi.getKeyListFromKeyBlock(keyData);
+
+    // keyInfo is an object, not an array => convert to array 1st
+    let keyArr = [];
+
+    for (let k in keyInfo) {
+      keyArr.push(keyInfo[k]);
+    }
+
+    if (keyArr.length === 1) {
+      paramsArr.fpr = keyArr[0].fpr;
+    }
+  }
+  catch (x) {}
+}
+
+
+/**
+ * Create the 9x4 digits backup code as defined in the Autocrypt spec
+ *
+ * @return String: xxxx-xxxx-...
+ */
+
+function createBackupCode() {
+  let bkpCode = "";
+
+  for (let i = 0; i < 9; i++) {
+    if (i > 0) bkpCode += "-";
+
+    let a = new Uint8Array(4);
+    crypto.getRandomValues(a);
+    for (let j = 0; j < 4; j++) {
+      bkpCode += String(a[j] % 10);
+    }
+  }
+  return bkpCode;
+}
+
+
+function createBackupOuterMsg(toEmail, encryptedMsg) {
+
+  let boundary = EnigmailMime.createBoundary();
+
+  let msgStr = 'To: ' + toEmail + '\r\n' +
+    'From: ' + toEmail + '\r\n' +
+    'Autocrypt-Setup-Message: v1\r\n' +
+    'Subject: ' + EnigmailLocale.getString("autocrypt.setupMsg.subject") + '\r\n' +
+    'Content-type: multipart/mixed; boundary="' + boundary + '"\r\n\r\n' +
+    '--' + boundary + '\r\n' +
+    'Content-Type: text/plain\r\n\r\n' +
+    EnigmailLocale.getString("autocryptSetupReq.setupMsg.desc") + '\r\n\r\n' +
+    EnigmailLocale.getString("autocrypt.setupMsg.msgBody") + '\r\n\r\n' +
+    EnigmailLocale.getString("autocryptSetupReq.setupMsg.backup") + '\r\n' +
+    '--' + boundary + '\r\n' +
+    'Content-Type: application/autocrypt-setup\r\n' +
+    'Content-Disposition: attachment; filename="autocrypt-setup-message.html"\r\n\r\n' +
+    '<html><body>\r\n' +
+    '<p>' + EnigmailLocale.getString("autocrypt.setupMsg.fileTxt") + '</p>\r\n' +
+    '<pre>\r\n' +
+    encryptedMsg +
+    '</pre></body></html>\r\n' +
+    '--' + boundary + '--\r\n';
+
+  return msgStr;
+}
+
+
+/**
+ * @return Object:
+ *          fpr:           String - FPR of the imported key
+ *          preferEncrypt: String - Autocrypt preferEncrypt value (e.g. mutual)
+ */
+function importSetupKey(keyData) {
+
+  EnigmailLog.DEBUG("autocrypt.jsm: importSetupKey()\n");
+
+  let preferEncrypt = "nopreference"; // Autocrypt default according spec
+  let start = {},
+    end = {},
+    keyObj = {};
+
+  let msgType = EnigmailArmor.locateArmoredBlock(keyData, 0, "", start, end, {});
+  if (msgType === "PRIVATE KEY BLOCK") {
+
+    let headers = EnigmailArmor.getArmorHeaders(keyData);
+    if ("autocrypt-prefer-encrypt" in headers) {
+      preferEncrypt = headers["autocrypt-prefer-encrypt"];
+    }
+
+    let r = EnigmailKeyRing.importKey(null, false, keyData, "", {}, keyObj);
+
+    if (r === 0 && keyObj.value && keyObj.value.length > 0) {
+      return {
+        fpr: keyObj.value[0],
+        preferEncrypt: preferEncrypt
+      };
+    }
+  }
+
+  return null;
+}
+
+
+function updateRuleForEmail(email, preferEncrypt, fpr = null) {
+  let node = EnigmailRules.getRuleByEmail(EnigmailConstants.AC_RULE_PREFIX + email);
+
+  if (node) {
+    let signEncrypt = (preferEncrypt === "mutual" ? "1" : "0");
+
+    if (node.getAttribute("sign") !== signEncrypt ||
+      node.getAttribute("encrypt") !== signEncrypt) {
+
+      node.setAttribute("sign", signEncrypt);
+      node.setAttribute("encrypt", signEncrypt);
+      if (fpr) {
+        node.setAttribute("keyList", `0x${fpr}`);
+      }
+      EnigmailRules.saveRulesFile();
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/buildDate.jsm
@@ -0,0 +1,3 @@
+"use strict";
+var EXPORTED_SYMBOLS = ["EnigmailBuildDate"];
+var EnigmailBuildDate = { built: "20191029-1710" };
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/card.jsm
@@ -0,0 +1,33 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailCard"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+
+var EnigmailCard = {
+  getCardStatus: function(exitCodeObj, errorMsgObj) {
+    EnigmailLog.DEBUG("card.jsm: EnigmailCard.getCardStatus\n");
+    const GPG_ADDITIONAL_OPTIONS=["--no-verbose", "--status-fd", "2", "--fixed-list-mode", "--with-colons", "--card-status"];
+    const args = EnigmailGpg.getStandardArgs(false).concat(GPG_ADDITIONAL_OPTIONS);
+    const statusMsgObj = {};
+    const statusFlagsObj = {};
+
+    const outputTxt = EnigmailExecution.execCmd(EnigmailGpg.agentPath, args, "", exitCodeObj, statusFlagsObj, statusMsgObj, errorMsgObj);
+
+    if ((exitCodeObj.value === 0) && !outputTxt) {
+      exitCodeObj.value = -1;
+      return "";
+    }
+
+    return outputTxt;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/clipboard.jsm
@@ -0,0 +1,105 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailClipboard"];
+
+// Import the Services module for future use, if we're not in
+// a browser window where it's already loaded.
+const Services = ChromeUtils.import('resource://gre/modules/Services.jsm').Services;
+
+
+// Create a constructor for the built-in supports-string class.
+const nsSupportsString = Components.Constructor("@mozilla.org/supports-string;1", "nsISupportsString");
+
+function SupportsString(str) {
+  // Create an instance of the supports-string class
+  var res = nsSupportsString();
+
+  // Store the JavaScript string that we want to wrap in the new nsISupportsString object
+  res.data = str;
+  return res;
+}
+
+// Create a constructor for the built-in transferable class
+const nsTransferable = Components.Constructor("@mozilla.org/widget/transferable;1", "nsITransferable");
+
+// Create a wrapper to construct an nsITransferable instance and set its source to the given window, when necessary
+function Transferable(source) {
+  let res = nsTransferable();
+  if ('init' in res) {
+    // When passed a Window object, find a suitable privacy context for it.
+    if (source instanceof Ci.nsIDOMWindow)
+      source = source.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebNavigation);
+
+    res.init(source);
+  }
+  return res;
+}
+
+var EnigmailClipboard = {
+
+  /**
+   * Get the content string of a clipboard
+   *
+   * @param window       : nsIWindow or nsIDOMWindow of caller
+   * @param clipBoardType: Number - clipboard type according to nsIClipboard
+   *
+   * @return String - content of clipBoard
+   */
+
+  getClipboardContent: function(window, clipBoardType) {
+    if (!window) throw "erorr - window must not be null";
+
+    let clipBoard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
+    let data = {};
+    let cBoardContent = "";
+
+    if (clipBoardType !== clipBoard.kSelectionClipboard || clipBoard.supportsSelectionClipboard()) {
+      try {
+        let transferable = Transferable(window);
+        transferable.addDataFlavor("text/unicode");
+        clipBoard.getData(transferable, clipBoardType);
+        let flavour = {};
+        let length = {};
+        transferable.getAnyTransferData(flavour, data, length);
+        cBoardContent = data.value.QueryInterface(Ci.nsISupportsString).data;
+      }
+      catch (ex) {}
+    }
+    return cBoardContent;
+  },
+
+  /**
+   * Set the global (and if available, the selection clipboard)
+   *
+   * @param str: String - data to set
+   * @param clipBoardType: Number - clipboard type according to nsIClipboard.
+   *             If not provided, the global plus the selection clipboard will be used
+   *
+   * @return Boolean: true - success / false - failure
+   */
+  setClipboardContent: function(str, clipBoardType) {
+    let useClipboard = clipBoardType;
+    if (clipBoardType === undefined) {
+      useClipboard = Ci.nsIClipboard.kGlobalClipboard;
+    }
+    try {
+      let clipBoard = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
+      let clipBoardHlp = Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper);
+      clipBoardHlp.copyStringToClipboard(str, useClipboard);
+      if (clipBoard.supportsSelectionClipboard() &&
+        (useClipboard === Ci.nsIClipboard.kSelectionClipboard || clipBoardType === undefined)) {
+        clipBoardHlp.copyStringToClipboard(str, Ci.nsIClipboard.kSelectionClipboard);
+      }
+    }
+    catch (ex) {
+      return false;
+    }
+    return true;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/commandLine.jsm
@@ -0,0 +1,47 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailCommandLine"];
+
+const EnigmailCompat = ChromeUtils.import("chrome://openpgp/content/modules/compat.jsm").EnigmailCompat;
+
+const NS_ENIGCLINE_SERVICE_CID = Components.ID("{847b3ab1-7ab1-11d4-8f02-006008948af5}");
+const NS_CLINE_SERVICE_CONTRACTID = "@mozilla.org/enigmail/cline-handler;1";
+
+function Handler() {}
+
+Handler.prototype = {
+  classDescription: "Enigmail Key Management CommandLine Service",
+  classID: NS_ENIGCLINE_SERVICE_CID,
+  contractID: NS_CLINE_SERVICE_CONTRACTID,
+  QueryInterface: EnigmailCompat.generateQI(["nsICommandLineHandler", "nsIFactory"]),
+
+  // nsICommandLineHandler
+  handle: function(cmdLine) {
+    if (cmdLine.handleFlag("pgpkeyman", false)) {
+      cmdLine.preventDefault = true; // do not open main app window
+
+      const wwatch = Cc["@mozilla.org/embedcomp/window-watcher;1"].getService(Ci.nsIWindowWatcher);
+      wwatch.openWindow(null, "chrome://openpgp/content/ui/enigmailKeyManager.xul", "_blank", "chrome,dialog=no,all", cmdLine);
+    }
+  },
+
+  helpInfo: "  -pgpkeyman         Open the OpenPGP key management.\n",
+
+  lockFactory: function(lock) {}
+};
+
+var EnigmailCommandLine = {
+  Handler: Handler,
+  categoryRegistry: {
+    category: "command-line-handler",
+    entry: "m-cline-enigmail",
+    serviceName: NS_CLINE_SERVICE_CONTRACTID
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/compat.jsm
@@ -0,0 +1,182 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+/**
+ *  compatibility Module
+ */
+
+var EXPORTED_SYMBOLS = ["EnigmailCompat"];
+
+const XPCOM_APPINFO = "@mozilla.org/xre/app-info;1";
+
+var gTb68OrNewer = null;
+var MailUtils;
+
+MailUtils = ChromeUtils.import("resource:///modules/MailUtils.jsm").MailUtils;
+
+var gCompFields, gPgpMimeObj;
+
+var EnigmailCompat = {
+  generateQI: function(aCid) {
+    if (this.isAtLeastTb68()) {
+      // TB > 60
+      return ChromeUtils.generateQI(aCid);
+    }
+    else {
+      let XPCOMUtils = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm").XPCOMUtils;
+      return XPCOMUtils.generateQI(aCid);
+    }
+  },
+
+  getSecurityField: function() {
+    if (!gCompFields) {
+      gCompFields = Cc["@mozilla.org/messengercompose/composefields;1"].createInstance(Ci.nsIMsgCompFields);
+    }
+    return ("securityInfo" in gCompFields ? /* TB < 64 */ "securityInfo" : "composeSecure");
+  },
+
+  getExistingFolder: function(folderUri) {
+    if ("getExistingFolder" in MailUtils) {
+      // TB >= 65
+      return MailUtils.getExistingFolder(folderUri);
+    }
+    else {
+      return MailUtils.getFolderForURI(folderUri, false);
+    }
+  },
+
+  isMessageUriInPgpMime: function() {
+    if (!gPgpMimeObj) {
+      gPgpMimeObj = Cc["@mozilla.org/mime/pgp-mime-js-decrypt;1"].createInstance(Ci.nsIPgpMimeProxy);
+    }
+
+    return ("messageURI" in gPgpMimeObj);
+  },
+
+  /**
+   * return true, if platform is newer than or equal a given version
+   */
+  isPlatformNewerThan: function(requestedVersion) {
+    let vc = Cc["@mozilla.org/xpcom/version-comparator;1"].getService(Ci.nsIVersionComparator);
+    let appVer = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).platformVersion;
+
+    return vc.compare(appVer, requestedVersion) >= 0;
+  },
+
+  /**
+   * Get a mail URL from a uriSpec
+   *
+   * @param uriSpec: String - URI of the desired message
+   *
+   * @return Object: nsIURL or nsIMsgMailNewsUrl object
+   */
+  getUrlFromUriSpec: function(uriSpec) {
+    try {
+      if (!uriSpec)
+        return null;
+
+      let messenger = Cc["@mozilla.org/messenger;1"].getService(Ci.nsIMessenger);
+      let msgService = messenger.messageServiceFromURI(uriSpec);
+
+      let url;
+      // TB
+      let urlObj = {};
+      msgService.GetUrlForUri(uriSpec, urlObj, null);
+
+      url = urlObj.value;
+
+      if (url.scheme == "file") {
+        return url;
+      }
+      else {
+        return url.QueryInterface(Ci.nsIMsgMailNewsUrl);
+      }
+
+    }
+    catch (ex) {
+      return null;
+    }
+  },
+  /**
+   * Copy a file to a mail folder.
+   *   in nsIFile aFile,
+   *   in nsIMsgFolder dstFolder,
+   *   in unsigned long aMsgFlags,
+   *   in ACString aMsgKeywords,
+   *   in nsIMsgCopyServiceListener listener,
+   *   in nsIMsgWindow msgWindow
+   */
+  copyFileToMailFolder: function(file, destFolder, msgFlags, msgKeywords, listener, msgWindow) {
+    let copySvc = Cc["@mozilla.org/messenger/messagecopyservice;1"].getService(Ci.nsIMsgCopyService);
+
+    return copySvc.CopyFileMessage(file, destFolder, null, false, msgFlags, msgKeywords, listener, msgWindow);
+  },
+
+  /**
+   * Determine if Platform is at version 68 or newer
+   *
+   * @return {Boolean}: true if at TB 68.0a1 or newer found
+   */
+  isAtLeastTb68: function() {
+    if (gTb68OrNewer === null) {
+      gTb68OrNewer = this.isPlatformNewerThan("68.0a1");
+    }
+
+    return gTb68OrNewer;
+  },
+
+  /**
+   * Get functions that wrap the changes on nsITreeView between TB 60 and TB 68
+   *
+   * @param treeObj
+   * @param listViewHolder
+   *
+   * @return {Object}
+   */
+  getTreeCompatibleFuncs: function(treeObj, listViewHolder) {
+
+    if (this.isAtLeastTb68()) {
+      return {
+        getCellAt: function(x,y) {
+          return treeObj.getCellAt(x, y);
+        },
+        rowCountChanged: function(a, b) {
+          return treeObj.rowCountChanged(a, b);
+        },
+        invalidate: function() {
+          return treeObj.invalidate();
+        },
+        invalidateRow: function(r) {
+          return treeObj.invalidateRow(r);
+        }
+      };
+    }
+    else {
+      return {
+        getCellAt: function(x, y) {
+            let row = {};
+            let col = {};
+            let elt = {};
+            treeObj.treeBoxObject.getCellAt(x, y, row, col, elt);
+
+            return {
+              row: row.value,
+              col: col.value
+            };
+        },
+        rowCountChanged: function(a, b) {
+          return listViewHolder.treebox.rowCountChanged(a, b);
+        },
+        invalidate: function() {
+          return listViewHolder.treebox.invalidate();
+        },
+        invalidateRow: function(r) {
+          return listViewHolder.treebox.invalidateRow(r);
+        }
+      };
+    }
+  },
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/configBackup.jsm
@@ -0,0 +1,213 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailConfigBackup"];
+
+
+
+
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailRules = ChromeUtils.import("chrome://openpgp/content/modules/rules.jsm").EnigmailRules;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+
+const TYPE_BOOL = 1;
+const TYPE_CHAR = 2;
+const TYPE_INT = 3;
+
+const IdentityPref = {
+  enablePgp: TYPE_BOOL,
+  pgpkeyId: TYPE_CHAR,
+  pgpKeyMode: TYPE_INT,
+  pgpSignPlain: TYPE_BOOL,
+  pgpSignEncrypted: TYPE_BOOL,
+  defaultSigningPolicy: TYPE_INT,
+  defaultEncryptionPolicy: TYPE_INT,
+  openPgpUrlName: TYPE_CHAR,
+  pgpMimeMode: TYPE_BOOL,
+  attachPgpKey: TYPE_BOOL,
+  autoEncryptDrafts: TYPE_BOOL
+};
+
+var EnigmailConfigBackup = {
+
+  getAccountManager: function() {
+    let amService = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+    return amService;
+  },
+
+  /**
+   * itereate over all identities and execute a callback function for each found element
+   *
+   * @param callbackFunc  function  - the callback for each identity
+   *                  The function takes the identity as 1st argument, i.e.
+   *                    callbackFunc(nsIMsgIdentity)
+   * @return  - undefined
+   */
+  forAllIdentitites: function(callbackFunc) {
+
+    let amService = this.getAccountManager();
+
+    amService.LoadAccounts(); // ensure accounts are really loaded
+    let a = amService.allIdentities;
+    for (let i = 0; i < a.length; i++) {
+      let id = a.queryElementAt(i, Ci.nsIMsgIdentity);
+      try {
+        callbackFunc(id);
+      }
+      catch (ex) {
+        EnigmailLog.DEBUG("configBackup.jsm: forAllIdentitites: exception " + ex.toString() + "\n");
+      }
+    }
+  },
+
+  /**
+   * backup Enigmail preferences to a file
+   *
+   * @param outputFile  nsIFile - handle to file to be saved
+   *
+   * @return 0: success, other values: failure
+   */
+  backupPrefs: function(outputFile) {
+    EnigmailLog.DEBUG("configBackup.jsm: backupPrefs\n");
+
+    // user preference
+    let prefObj = {
+      enigmailPrefs: EnigmailPrefs.getAllPrefs(),
+      mailIdentities: {}
+    };
+
+    function getIdentityPrefs(identity) {
+
+      if (!identity.getBoolAttribute("enablePgp")) return; // do nothing if Enigmail disabled
+
+      let keyObj = {
+        emailAddress: identity.email.toLowerCase(),
+        identityName: identity.identityName
+      };
+
+      for (let pref in IdentityPref) {
+        switch (IdentityPref[pref]) {
+          case TYPE_BOOL:
+            keyObj[pref] = identity.getBoolAttribute(pref);
+            break;
+          case TYPE_INT:
+            keyObj[pref] = identity.getIntAttribute(pref);
+            break;
+          case TYPE_CHAR:
+            keyObj[pref] = identity.getCharAttribute(pref);
+            break;
+        }
+      }
+
+      prefObj.mailIdentities[identity.key] = keyObj;
+    }
+
+    this.forAllIdentitites(getIdentityPrefs);
+
+    // per-recipient rules (aka pgpRules.xml)
+    var rulesFile = EnigmailRules.getRulesFile();
+    if (rulesFile.exists()) {
+      prefObj.rules = EnigmailFiles.readFile(rulesFile);
+    }
+
+    let jsonStr = JSON.stringify(prefObj);
+
+    // serialize everything to UTF-8 encoded JSON.
+    if (EnigmailFiles.writeFileContents(outputFile, jsonStr)) {
+      return 0;
+    }
+
+    return -1;
+  },
+
+  /**
+   * Restore Enigmail preferences from a file as generated by backpPrefs()
+   *
+   * @param inputFile  nsIFile - handle to file to be saved
+   *
+   * @return Object: {
+   *     retVal:       Number - 0: success, other values: failure
+   *     unmatchedIds: Array (String): keys of identities
+   *   }
+   */
+  restorePrefs: function(inputFile) {
+    EnigmailLog.DEBUG("configBackup.jsm: restorePrefs\n");
+    var prefObj;
+    var returnObj = {
+      retVal: -1,
+      unmatchedIds: []
+    };
+
+    function setIdentityPref(identity) {
+      for (let k in prefObj.mailIdentities) {
+        if (prefObj.mailIdentities[k].emailAddress === identity.email.toLowerCase()) {
+          EnigmailLog.DEBUG("configBackup.jsm: setIdentityPref: restoring values for " + identity.email + "\n");
+          prefObj.mailIdentities[k].foundMatchingEmail = true;
+          let keyObj = prefObj.mailIdentities[k];
+          for (let pref in IdentityPref) {
+            switch (IdentityPref[pref]) {
+              case TYPE_BOOL:
+                identity.setBoolAttribute(pref, keyObj[pref]);
+                break;
+              case TYPE_INT:
+                identity.setIntAttribute(pref, keyObj[pref]);
+                break;
+              case TYPE_CHAR:
+                identity.setCharAttribute(pref, keyObj[pref]);
+                break;
+            }
+          }
+          return;
+        }
+      }
+
+      EnigmailLog.DEBUG("configBackup.jsm: setIdentityPref: no matching data for " + identity.email + "\n");
+    }
+
+    // Profile must be a single UTF-8 encoded JSON object.
+    try {
+      let jsonStr = EnigmailFiles.readFile(inputFile);
+      prefObj = JSON.parse(jsonStr);
+
+      var nsIPB = Ci.nsIPrefBranch;
+      var branch = EnigmailPrefs.getPrefBranch();
+
+      // Set all options recorded in the JSON file.
+      for (let name in prefObj.enigmailPrefs) {
+        EnigmailPrefs.setPref(name, prefObj.enigmailPrefs[name]);
+      }
+
+      this.forAllIdentitites(setIdentityPref);
+
+      for (let i in prefObj.mailIdentities) {
+        if (!("foundMatchingEmail" in prefObj.mailIdentities[i])) {
+          returnObj.unmatchedIds.push(prefObj.mailIdentities[i].identityName);
+        }
+      }
+
+      let am = this.getAccountManager();
+      am.saveAccountInfo();
+      EnigmailPrefs.savePrefs();
+
+      if ("rules" in prefObj) {
+        EnigmailRules.loadRulesFromString(prefObj.rules);
+        EnigmailRules.saveRulesFile();
+      }
+
+    }
+    catch (ex) {
+      EnigmailLog.ERROR("configBackup.jsm: restorePrefs - exception " + ex.toString() + "\n");
+      return returnObj;
+    }
+
+    returnObj.retVal = 0;
+    return returnObj;
+  }
+
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/configure.jsm
@@ -0,0 +1,301 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailConfigure"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailTimer = ChromeUtils.import("chrome://openpgp/content/modules/timer.jsm").EnigmailTimer;
+const EnigmailApp = ChromeUtils.import("chrome://openpgp/content/modules/app.jsm").EnigmailApp;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
+const EnigmailWindows = ChromeUtils.import("chrome://openpgp/content/modules/windows.jsm").EnigmailWindows;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailStdlib = ChromeUtils.import("chrome://openpgp/content/modules/stdlib.jsm").EnigmailStdlib;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const EnigmailAutoSetup = ChromeUtils.import("chrome://openpgp/content/modules/autoSetup.jsm").EnigmailAutoSetup;
+const EnigmailSqliteDb = ChromeUtils.import("chrome://openpgp/content/modules/sqliteDb.jsm").EnigmailSqliteDb;
+
+// Interfaces
+const nsIFolderLookupService = Ci.nsIFolderLookupService;
+const nsIMsgAccountManager = Ci.nsIMsgAccountManager;
+
+/**
+ * Upgrade sending prefs
+ * (v1.6.x -> v1.7 )
+ */
+function upgradePrefsSending() {
+  EnigmailLog.DEBUG("enigmailCommon.jsm: upgradePrefsSending()\n");
+
+  var cbs = EnigmailPrefs.getPref("confirmBeforeSend");
+  var ats = EnigmailPrefs.getPref("alwaysTrustSend");
+  var ksfr = EnigmailPrefs.getPref("keepSettingsForReply");
+  EnigmailLog.DEBUG("enigmailCommon.jsm: upgradePrefsSending cbs=" + cbs + " ats=" + ats + " ksfr=" + ksfr + "\n");
+
+  // Upgrade confirmBeforeSend (bool) to confirmBeforeSending (int)
+  switch (cbs) {
+    case false:
+      EnigmailPrefs.setPref("confirmBeforeSending", 0); // never
+      break;
+    case true:
+      EnigmailPrefs.setPref("confirmBeforeSending", 1); // always
+      break;
+  }
+
+  // Upgrade alwaysTrustSend (bool)   to acceptedKeys (int)
+  switch (ats) {
+    case false:
+      EnigmailPrefs.setPref("acceptedKeys", 0); // valid
+      break;
+    case true:
+      EnigmailPrefs.setPref("acceptedKeys", 1); // all
+      break;
+  }
+
+  // if all settings are default settings, use convenient encryption
+  if (cbs === false && ats === true && ksfr === true) {
+    EnigmailPrefs.setPref("encryptionModel", 0); // convenient
+    EnigmailLog.DEBUG("enigmailCommon.jsm: upgradePrefsSending() encryptionModel=0 (convenient)\n");
+  }
+  else {
+    EnigmailPrefs.setPref("encryptionModel", 1); // manually
+    EnigmailLog.DEBUG("enigmailCommon.jsm: upgradePrefsSending() encryptionModel=1 (manually)\n");
+  }
+
+  // clear old prefs
+  EnigmailPrefs.getPrefBranch().clearUserPref("confirmBeforeSend");
+  EnigmailPrefs.getPrefBranch().clearUserPref("alwaysTrustSend");
+}
+
+/**
+ * Replace short key IDs with FPR in identity settings
+ * (v1.9 -> v2.0)
+ */
+function replaceKeyIdWithFpr() {
+  try {
+    const GetKeyRing = EnigmailLazy.loader("enigmail/keyRing.jsm", "EnigmailKeyRing");
+
+    var accountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+    for (var i = 0; i < accountManager.allIdentities.length; i++) {
+      var id = accountManager.allIdentities.queryElementAt(i, Ci.nsIMsgIdentity);
+      if (id.getBoolAttribute("enablePgp")) {
+        let keyId = id.getCharAttribute("pgpkeyId");
+
+        if (keyId.search(/^(0x)?[a-fA-F0-9]{8}$/) === 0) {
+
+          EnigmailCore.getService();
+
+          let k = GetKeyRing().getKeyById(keyId);
+          if (k) {
+            id.setCharAttribute("pgpkeyId", "0x" + k.fpr);
+          }
+          else {
+            id.setCharAttribute("pgpkeyId", "");
+          }
+        }
+      }
+    }
+  }
+  catch (ex) {
+    EnigmailDialog.alert("config upgrade: error" + ex.toString());
+  }
+}
+
+
+/**
+ * Change the default to PGP/MIME for all accounts, except nntp
+ * (v1.8.x -> v1.9)
+ */
+function defaultPgpMime() {
+  let accountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+  let changedSomething = false;
+
+  for (let acct = 0; acct < accountManager.accounts.length; acct++) {
+    let ac = accountManager.accounts.queryElementAt(acct, Ci.nsIMsgAccount);
+    if (ac.incomingServer.type.search(/(pop3|imap|movemail)/) >= 0) {
+
+      for (let i = 0; i < ac.identities.length; i++) {
+        let id = ac.identities.queryElementAt(i, Ci.nsIMsgIdentity);
+        if (id.getBoolAttribute("enablePgp") && !id.getBoolAttribute("pgpMimeMode")) {
+          changedSomething = true;
+        }
+        id.setBoolAttribute("pgpMimeMode", true);
+      }
+    }
+  }
+
+  if (EnigmailPrefs.getPref("advancedUser") && changedSomething) {
+    EnigmailDialog.alert(null,
+      EnigmailLocale.getString("preferences.defaultToPgpMime"));
+  }
+}
+
+/**
+ * set the Autocrypt prefer-encrypt option to "mutual" for all existing
+ * accounts
+ */
+function setAutocryptForOldAccounts() {
+  try {
+    let accountManager = Cc["@mozilla.org/messenger/account-manager;1"].getService(Ci.nsIMsgAccountManager);
+    let changedSomething = false;
+
+    for (let acct = 0; acct < accountManager.accounts.length; acct++) {
+      let ac = accountManager.accounts.queryElementAt(acct, Ci.nsIMsgAccount);
+      if (ac.incomingServer.type.search(/(pop3|imap|movemail)/) >= 0) {
+        ac.incomingServer.setIntValue("acPreferEncrypt", 1);
+      }
+    }
+  }
+  catch (ex) {}
+}
+
+function setDefaultKeyServer() {
+  EnigmailLog.DEBUG("configure.jsm: setDefaultKeyServer()\n");
+
+  let ks = EnigmailPrefs.getPref("keyserver");
+
+  if (ks.search(/^ldaps?:\/\//) < 0) {
+    ks = "vks://keys.openpgp.org, " + ks;
+  }
+
+  ks = ks.replace(/hkps:\/\/keys.openpgp.org/g, "vks://keys.openpgp.org");
+  EnigmailPrefs.setPref("keyserver", ks);
+}
+
+
+
+function displayUpgradeInfo() {
+  EnigmailLog.DEBUG("configure.jsm: displayUpgradeInfo()\n");
+  try {
+    EnigmailWindows.openMailTab("chrome://openpgp/content/ui/upgradeInfo.html");
+  }
+  catch (ex) {}
+}
+
+
+var EnigmailConfigure = {
+  /**
+   * configureEnigmail: main function for configuring Enigmail during the first run
+   * this method is called from core.jsm if Enigmail has not been set up before
+   * (determined via checking the configuredVersion in the preferences)
+   *
+   * @param {nsIWindow} win:                 The parent window. Null if no parent window available
+   * @param {Boolean}   startingPreferences: if true, called while switching to new preferences
+   *                        (to avoid re-check for preferences)
+   *
+   * @return {Promise<null>}
+   */
+  configureEnigmail: async function(win, startingPreferences) {
+    EnigmailLog.DEBUG("configure.jsm: configureEnigmail()\n");
+
+    if (!EnigmailStdlib.hasConfiguredAccounts()) {
+      EnigmailLog.DEBUG("configure.jsm: configureEnigmail: no account configured. Waiting 60 seconds.\n");
+
+      // try again in 60 seconds
+      EnigmailTimer.setTimeout(
+        function _f() {
+          EnigmailConfigure.configureEnigmail(win, startingPreferences);
+        },
+        60000);
+      return;
+    }
+
+    let oldVer = EnigmailPrefs.getPref("configuredVersion");
+
+    let vc = Cc["@mozilla.org/xpcom/version-comparator;1"].getService(Ci.nsIVersionComparator);
+
+    if (oldVer === "") {
+      try {
+        let setupResult = await EnigmailAutoSetup.determinePreviousInstallType();
+
+        switch (EnigmailAutoSetup.value) {
+          case EnigmailConstants.AUTOSETUP_NOT_INITIALIZED:
+          case EnigmailConstants.AUTOSETUP_NO_ACCOUNT:
+            break;
+          default:
+            EnigmailPrefs.setPref("configuredVersion", EnigmailApp.getVersion());
+            EnigmailWindows.openSetupWizard(win);
+        }
+      }
+      catch(x) {
+        // ignore exceptions and proceed without setup wizard
+      }
+    }
+    else {
+      if (vc.compare(oldVer, "1.7a1pre") < 0) {
+        // 1: rules only
+        //     => assignKeysByRules true; rest false
+        // 2: rules & email addresses (normal)
+        //     => assignKeysByRules/assignKeysByEmailAddr/assignKeysManuallyIfMissing true
+        // 3: email address only (no rules)
+        //     => assignKeysByEmailAddr/assignKeysManuallyIfMissing true
+        // 4: manually (always prompt, no rules)
+        //     => assignKeysManuallyAlways true
+        // 5: no rules, no key selection
+        //     => assignKeysByRules/assignKeysByEmailAddr true
+
+        upgradePrefsSending();
+      }
+      if (vc.compare(oldVer, "1.7") < 0) {
+        // open a modal dialog. Since this might happen during the opening of another
+        // window, we have to do this asynchronously
+        EnigmailTimer.setTimeout(
+          function _cb() {
+            var doIt = EnigmailDialog.confirmDlg(win,
+              EnigmailLocale.getString("enigmailCommon.versionSignificantlyChanged"),
+              EnigmailLocale.getString("enigmailCommon.checkPreferences"),
+              EnigmailLocale.getString("dlg.button.close"));
+            if (!startingPreferences && doIt) {
+              // same as:
+              // - EnigmailWindows.openPrefWindow(window, true, 'sendingTab');
+              // but
+              // - without starting the service again because we do that right now
+              // - and modal (waiting for its end)
+              win.openDialog("chrome://openpgp/content/ui/pref-enigmail.xul",
+                "_blank", "chrome,resizable=yes,modal", {
+                  'showBasic': true,
+                  'clientType': 'thunderbird',
+                  'selectTab': 'sendingTab'
+                });
+            }
+          }, 100);
+      }
+
+      if (vc.compare(oldVer, "1.9a2pre") < 0) {
+        defaultPgpMime();
+      }
+      if (vc.compare(oldVer, "2.0a1pre") < 0) {
+        this.upgradeTo20();
+      }
+      if (vc.compare(oldVer, "2.0.1a2pre") < 0) {
+        this.upgradeTo201();
+      }
+      if (vc.compare(oldVer, "2.1b2") < 0) {
+        this.upgradeTo21();
+      }
+
+    }
+
+    EnigmailPrefs.setPref("configuredVersion", EnigmailApp.getVersion());
+    EnigmailPrefs.savePrefs();
+  },
+
+  upgradeTo20: function() {
+    replaceKeyIdWithFpr();
+    displayUpgradeInfo();
+  },
+
+  upgradeTo201: function() {
+    setAutocryptForOldAccounts();
+  },
+
+  upgradeTo21: function() {
+    setDefaultKeyServer();
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/constants.jsm
@@ -0,0 +1,166 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailConstants"];
+
+var EnigmailConstants = {
+  POSSIBLE_PGPMIME: -2081,
+
+  // possible values for
+  // - encryptByRule, signByRules, pgpmimeByRules
+  // - encryptForced, signForced, pgpmimeForced (except CONFLICT)
+  // NOTE:
+  // - values 0/1/2 are used with this fixed semantics in the persistent rules
+  // - see also enigmailEncryptionDlg.xul
+  ENIG_NEVER: 0,
+  ENIG_UNDEF: 1,
+  ENIG_ALWAYS: 2,
+  ENIG_FORCE_SMIME: 3,
+  ENIG_AUTO_ALWAYS: 22,
+  ENIG_CONFLICT: 99,
+
+  ENIG_FINAL_UNDEF: -1,
+  ENIG_FINAL_NO: 0,
+  ENIG_FINAL_YES: 1,
+  ENIG_FINAL_FORCENO: 10,
+  ENIG_FINAL_FORCEYES: 11,
+  ENIG_FINAL_SMIME: 97, // use S/MIME (automatically chosen)
+  ENIG_FINAL_FORCESMIME: 98, // use S/MIME (forced by user)
+  ENIG_FINAL_CONFLICT: 99,
+
+  MIME_HANDLER_UNDEF: 0,
+  MIME_HANDLER_SMIME: 1,
+  MIME_HANDLER_PGPMIME: 2,
+
+  ICONTYPE_INFO: 1,
+  ICONTYPE_QUESTION: 2,
+  ICONTYPE_ALERT: 3,
+  ICONTYPE_ERROR: 4,
+
+  FILTER_MOVE_DECRYPT: "enigmail@enigmail.net#filterActionMoveDecrypt",
+  FILTER_COPY_DECRYPT: "enigmail@enigmail.net#filterActionCopyDecrypt",
+  FILTER_ENCRYPT: "enigmail@enigmail.net#filterActionEncrypt",
+  FILTER_TERM_PGP_ENCRYPTED: "enigmail@enigmail.net#filterTermPGPEncrypted",
+
+  /* taken over from old nsIEnigmail */
+
+  /* Cleartext signature parts */
+  SIGNATURE_TEXT: 1,
+  SIGNATURE_HEADERS: 2,
+  SIGNATURE_ARMOR: 3,
+
+  /* User interaction flags */
+  UI_INTERACTIVE: 0x01,
+  UI_ALLOW_KEY_IMPORT: 0x02,
+  UI_UNVERIFIED_ENC_OK: 0x04,
+  UI_PGP_MIME: 0x08,
+  UI_TEST: 0x10,
+  UI_RESTORE_STRICTLY_MIME: 0x20,
+  UI_IGNORE_MDC_ERROR: 0x40, // force decryption, even if we got an MDC error
+
+  /* Send message flags */
+  SEND_SIGNED: 0x0001, //    1
+  SEND_ENCRYPTED: 0x0002, //    2
+  SEND_DEFAULT: 0x0004, //    4
+  SEND_LATER: 0x0008, //    8
+  SEND_WITH_CHECK: 0x0010, //   16
+  SEND_ALWAYS_TRUST: 0x0020, //   32
+  SEND_ENCRYPT_TO_SELF: 0x0040, //   64
+  SEND_PGP_MIME: 0x0080, //  128
+  SEND_TEST: 0x0100, //  256
+  SAVE_MESSAGE: 0x0200, //  512
+  SEND_STRIP_WHITESPACE: 0x0400, // 1024
+  SEND_ATTACHMENT: 0x0800, // 2048
+  ENCRYPT_HEADERS: 0x1000, // 4096
+  SEND_VERBATIM: 0x2000, // 8192
+
+  /* Status flags */
+  GOOD_SIGNATURE: 0x00000001,
+  BAD_SIGNATURE: 0x00000002,
+  UNVERIFIED_SIGNATURE: 0x00000004,
+  EXPIRED_SIGNATURE: 0x00000008,
+  EXPIRED_KEY_SIGNATURE: 0x00000010,
+  EXPIRED_KEY: 0x00000020,
+  REVOKED_KEY: 0x00000040,
+  NO_PUBKEY: 0x00000080,
+  NO_SECKEY: 0x00000100,
+  IMPORTED_KEY: 0x00000200,
+  INVALID_RECIPIENT: 0x00000400,
+  MISSING_PASSPHRASE: 0x00000800,
+  BAD_PASSPHRASE: 0x00001000,
+  BAD_ARMOR: 0x00002000,
+  NODATA: 0x00004000,
+  DECRYPTION_INCOMPLETE: 0x00008000,
+  DECRYPTION_FAILED: 0x00010000,
+  DECRYPTION_OKAY: 0x00020000,
+  MISSING_MDC: 0x00040000,
+  TRUSTED_IDENTITY: 0x00080000,
+  PGP_MIME_SIGNED: 0x00100000,
+  PGP_MIME_ENCRYPTED: 0x00200000,
+  DISPLAY_MESSAGE: 0x00400000,
+  INLINE_KEY: 0x00800000,
+  PARTIALLY_PGP: 0x01000000,
+  PHOTO_AVAILABLE: 0x02000000,
+  OVERFLOWED: 0x04000000,
+  CARDCTRL: 0x08000000,
+  SC_OP_FAILURE: 0x10000000,
+  UNKNOWN_ALGO: 0x20000000,
+  SIG_CREATED: 0x40000000,
+  END_ENCRYPTION: 0x80000000,
+
+  /*** key handling functions ***/
+
+  EXTRACT_SECRET_KEY: 0x01,
+
+  /* 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,
+
+  /* attachment handling */
+
+  /* per-recipient rules */
+  AC_RULE_PREFIX: "autocrypt://",
+
+  CARD_PIN_CHANGE: 1,
+  CARD_PIN_UNBLOCK: 2,
+  CARD_ADMIN_PIN_CHANGE: 3,
+
+  /* Keyserver error codes (in keyserver.jsm) */
+  KEYSERVER_ERR_ABORTED: 1,
+  KEYSERVER_ERR_SERVER_ERROR: 2,
+  KEYSERVER_ERR_SECURITY_ERROR: 3,
+  KEYSERVER_ERR_CERTIFICATE_ERROR: 4,
+  KEYSERVER_ERR_SERVER_UNAVAILABLE: 5,
+  KEYSERVER_ERR_IMPORT_ERROR: 6,
+  KEYSERVER_ERR_UNKNOWN: 7,
+
+  /* AutocryptSeup Setup Type */
+  AUTOSETUP_NOT_INITIALIZED: 0,
+  AUTOSETUP_AC_SETUP_MSG: 1,
+  AUTOSETUP_AC_HEADER: 2,
+  AUTOSETUP_PEP_HEADER: 3,
+  AUTOSETUP_ENCRYPTED_MSG: 4,
+  AUTOSETUP_NO_HEADER: 5,
+  AUTOSETUP_NO_ACCOUNT: 6,
+
+  /* Bootstrapped Addon constants */
+  APP_STARTUP: 1, // The application is starting up.
+  APP_SHUTDOWN: 2, // The application is shutting down.
+  ADDON_ENABLE: 3, // The add-on is being enabled.
+  ADDON_DISABLE: 4, // The add-on is being disabled. (Also sent during uninstallation)
+  ADDON_INSTALL: 5, // The add-on is being installed.
+  ADDON_UNINSTALL: 6, // The add-on is being uninstalled.
+  ADDON_UPGRADE: 7, // The add-on is being upgraded.
+  ADDON_DOWNGRADE: 8 // The add-on is being downgraded.
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/core.jsm
@@ -0,0 +1,478 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+const {
+  manager: Cm,
+  Constructor: CC
+} = Components;
+Cm.QueryInterface(Ci.nsIComponentRegistrar);
+
+const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+
+// load all modules lazily to avoid possible cross-reference errors
+const getEnigmailConsole = EnigmailLazy.loader("enigmail/pipeConsole.jsm", "EnigmailConsole");
+const getEnigmailGpgAgent = EnigmailLazy.loader("enigmail/gpgAgent.jsm", "EnigmailGpgAgent");
+const getEnigmailMimeEncrypt = EnigmailLazy.loader("enigmail/mimeEncrypt.jsm", "EnigmailMimeEncrypt");
+const getEnigmailProtocolHandler = EnigmailLazy.loader("enigmail/protocolHandler.jsm", "EnigmailProtocolHandler");
+const getEnigmailFiltersWrapper = EnigmailLazy.loader("enigmail/filtersWrapper.jsm", "EnigmailFiltersWrapper");
+const getEnigmailLog = EnigmailLazy.loader("enigmail/log.jsm", "EnigmailLog");
+const getEnigmailOS = EnigmailLazy.loader("enigmail/os.jsm", "EnigmailOS");
+const getEnigmailLocale = EnigmailLazy.loader("enigmail/locale.jsm", "EnigmailLocale");
+const getEnigmailCommandLine = EnigmailLazy.loader("enigmail/commandLine.jsm", "EnigmailCommandLine");
+const getEnigmailPrefs = EnigmailLazy.loader("enigmail/prefs.jsm", "EnigmailPrefs");
+const getEnigmailVerify = EnigmailLazy.loader("enigmail/mimeVerify.jsm", "EnigmailVerify");
+const getEnigmailWindows = EnigmailLazy.loader("enigmail/windows.jsm", "EnigmailWindows");
+const getEnigmailDialog = EnigmailLazy.loader("enigmail/dialog.jsm", "EnigmailDialog");
+const getEnigmailConfigure = EnigmailLazy.loader("enigmail/configure.jsm", "EnigmailConfigure");
+const getEnigmailApp = EnigmailLazy.loader("enigmail/app.jsm", "EnigmailApp");
+const getEnigmailKeyRefreshService = EnigmailLazy.loader("enigmail/keyRefreshService.jsm", "EnigmailKeyRefreshService");
+const getEnigmailKeyServer = EnigmailLazy.loader("enigmail/keyserver.jsm", "EnigmailKeyServer");
+const getEnigmailWksMimeHandler = EnigmailLazy.loader("enigmail/wksMimeHandler.jsm", "EnigmailWksMimeHandler");
+const getEnigmailOverlays = EnigmailLazy.loader("enigmail/enigmailOverlays.jsm", "EnigmailOverlays");
+const getEnigmailSqlite = EnigmailLazy.loader("enigmail/sqliteDb.jsm", "EnigmailSqliteDb");
+const getEnigmailTimer = EnigmailLazy.loader("enigmail/timer.jsm", "EnigmailTimer");
+const Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+
+var EXPORTED_SYMBOLS = ["EnigmailCore"];
+
+// Interfaces
+const nsIEnvironment = Ci.nsIEnvironment;
+
+var gPreferredGpgPath = null;
+var gOverwriteEnvVar = [];
+var gEnigmailService = null; // Global Enigmail Service
+
+var gEnvList = null; // currently filled from enigmail.js
+
+var EnigmailCore = {
+  /**
+   * Create a new instance of Enigmail, or return the already existing one
+   */
+  createInstance: function() {
+    if (!gEnigmailService) {
+      gEnigmailService = new Enigmail();
+    }
+
+    return gEnigmailService;
+  },
+
+  startup: function(reason) {
+    let self = this;
+
+    let env = getEnvironment();
+    initializeLogDirectory();
+    initializeLogging(env);
+
+    const logger = getEnigmailLog();
+
+    logger.DEBUG("core.jsm: startup()\n");
+
+    getEnigmailSqlite().checkDatabaseStructure();
+    getEnigmailPrefs().startup(reason);
+
+    this.factories = [];
+
+    function continueStartup(type) {
+      logger.DEBUG(`core.jsm: startup.continueStartup(${type})\n`);
+
+      try {
+        let mimeEncrypt = getEnigmailMimeEncrypt();
+        mimeEncrypt.startup(reason);
+        getEnigmailOverlays().startup();
+        self.factories.push(new Factory(getEnigmailProtocolHandler()));
+        self.factories.push(new Factory(mimeEncrypt.Handler));
+      } catch (ex) {
+        logger.DEBUG("core.jsm: startup.continueStartup: error " + ex.message + "\n" + ex.stack + "\n");
+      }
+    }
+
+    getEnigmailVerify().registerContentTypeHandler();
+    getEnigmailWksMimeHandler().registerContentTypeHandler();
+    getEnigmailFiltersWrapper().onStartup();
+    continueStartup(1);
+  },
+
+  shutdown: function(reason) {
+    getEnigmailLog().DEBUG("core.jsm: shutdown():\n");
+
+    let cLineReg = getEnigmailCommandLine().categoryRegistry;
+    let catMan = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+    catMan.deleteCategoryEntry(cLineReg.category, cLineReg.entry, false);
+
+    if (this.factories) {
+      for (let fct of this.factories) {
+        fct.unregister();
+      }
+    }
+
+    getEnigmailFiltersWrapper().onShutdown();
+    getEnigmailVerify().unregisterContentTypeHandler();
+
+    getEnigmailGpgAgent().finalize();
+    getEnigmailLocale().shutdown();
+    getEnigmailLog().onShutdown();
+
+    getEnigmailLog().setLogLevel(3);
+    gEnigmailService = null;
+  },
+
+  version: "",
+
+  init: function(enigmailVersion) {
+    this.version = enigmailVersion;
+  },
+
+  /**
+   * get and or initialize the Enigmail service,
+   * including the handling for upgrading old preferences to new versions
+   *
+   * @win:                - nsIWindow: parent window (optional)
+   * @startingPreferences - Boolean: true - called while switching to new preferences
+   *                        (to avoid re-check for preferences)
+   */
+  getService: function(win, startingPreferences) {
+    // Lazy initialization of Enigmail JS component (for efficiency)
+
+    if (gEnigmailService) {
+      return gEnigmailService.initialized ? gEnigmailService : null;
+    }
+
+    try {
+      this.createInstance();
+      return gEnigmailService.getService(win, startingPreferences);
+    } catch (ex) {
+      return null;
+    }
+
+  },
+
+  getEnigmailService: function() {
+    return gEnigmailService;
+  },
+
+  setEnigmailService: function(v) {
+    gEnigmailService = v;
+  },
+
+  /**
+   * obtain a list of all environment variables
+   *
+   * @return: Array of Strings with the following structrue
+   *          variable_name=variable_content
+   */
+  getEnvList: function() {
+    return gEnvList;
+  },
+
+  addToEnvList: function(str) {
+    gEnvList.push(str);
+  },
+
+  setEnvVariable: function(varname, value) {
+    for (let i = 0; i < gEnvList.length; i++) {
+      if (gEnvList[i].startsWith(varname + "=")) {
+        gEnvList[i] = varname + "=" + value;
+        break;
+      }
+    }
+  }
+};
+
+///////////////////////////////////////////////////////////////////////////////
+// Enigmail encryption/decryption service
+///////////////////////////////////////////////////////////////////////////////
+
+function getLogDirectoryPrefix() {
+  try {
+    return getEnigmailPrefs().getPrefBranch().getCharPref("logDirectory") || "";
+  } catch (ex) {
+    return "";
+  }
+}
+
+function initializeLogDirectory() {
+  const prefix = getLogDirectoryPrefix();
+  if (prefix) {
+    getEnigmailLog().setLogLevel(5);
+    getEnigmailLog().setLogDirectory(prefix);
+    getEnigmailLog().DEBUG("core.jsm: Logging debug output to " + prefix + "/enigdbug.txt\n");
+  }
+}
+
+function initializeLogging(env) {
+  const nspr_log_modules = env.get("NSPR_LOG_MODULES");
+  const matches = nspr_log_modules.match(/enigmail.js:(\d+)/);
+
+  if (matches && (matches.length > 1)) {
+    getEnigmailLog().setLogLevel(Number(matches[1]));
+    getEnigmailLog().WARNING("core.jsm: Enigmail: LogLevel=" + matches[1] + "\n");
+  }
+}
+
+function initializeSubprocessLogging(env) {
+  const nspr_log_modules = env.get("NSPR_LOG_MODULES");
+  const matches = nspr_log_modules.match(/subprocess:(\d+)/);
+
+  subprocess.registerLogHandler(function(txt) {
+    getEnigmailLog().ERROR("subprocess.jsm: " + txt);
+  });
+
+  if (matches && matches.length > 1 && matches[1] > 2) {
+    subprocess.registerDebugHandler(function(txt) {
+      getEnigmailLog().DEBUG("subprocess.jsm: " + txt);
+    });
+  }
+}
+
+function initializeAgentInfo() {
+  if (!getEnigmailOS().isDosLike && !getEnigmailGpgAgent().isDummy()) {
+    EnigmailCore.addToEnvList("GPG_AGENT_INFO=" + getEnigmailGpgAgent().gpgAgentInfo.envStr);
+  }
+}
+
+function failureOn(ex, status) {
+  status.initializationError = getEnigmailLocale().getString("enigmailNotAvailable");
+  getEnigmailLog().ERROR("core.jsm: Enigmail.initialize: Error - " + status.initializationError + "\n");
+  getEnigmailLog().DEBUG("core.jsm: Enigmail.initialize: exception=" + ex.toString() + "\n");
+  throw Components.results.NS_ERROR_FAILURE;
+}
+
+function getEnvironment(status) {
+  try {
+    return Cc["@mozilla.org/process/environment;1"].getService(nsIEnvironment);
+  } catch (ex) {
+    failureOn(ex, status);
+  }
+  return null;
+}
+
+function initializeEnvironment(env) {
+  // Initialize global environment variables list
+  let passEnv = ["GNUPGHOME", "GPGDIR", "ETC",
+    "ALLUSERSPROFILE", "APPDATA", "LOCALAPPDATA", "BEGINLIBPATH",
+    "COMMONPROGRAMFILES", "COMSPEC", "DBUS_SESSION_BUS_ADDRESS", "DISPLAY",
+    "ENIGMAIL_PASS_ENV", "ENDLIBPATH",
+    "GTK_IM_MODULE",
+    "HOME", "HOMEDRIVE", "HOMEPATH",
+    "LOCPATH", "LOGNAME", "LD_LIBRARY_PATH", "MOZILLA_FIVE_HOME",
+    "NLSPATH", "PATH", "PATHEXT", "PINENTRY_USER_DATA", "PROGRAMFILES", "PWD",
+    "QT_IM_MODULE",
+    "SHELL", "SYSTEMDRIVE", "SYSTEMROOT",
+    "TEMP", "TMP", "TMPDIR", "TZ", "TZDIR", "UNIXROOT",
+    "USER", "USERPROFILE", "WINDIR", "XAUTHORITY",
+    "XMODIFIERS"
+  ];
+
+  gEnvList = [];
+
+  // if (!getEnigmailPrefs().getPref("gpgLocaleEn")) {
+  //   passEnv = passEnv.concat([
+  //     "LANG", "LANGUAGE", "LC_ALL", "LC_COLLATE", "LC_CTYPE",
+  //     "LC_MESSAGES", "LC_MONETARY", "LC_NUMERIC", "LC_TIME"
+  //   ]);
+  // }
+  // else if (getEnigmailOS().getOS() === "WINNT") {
+  //   // force output on Windows to EN-US
+  //   EnigmailCore.addToEnvList("LC_ALL=en_US");
+  //   EnigmailCore.addToEnvList("LANG=en_US");
+  // }
+
+  EnigmailCore.addToEnvList("LC_ALL=C");
+  EnigmailCore.addToEnvList("LANG=C");
+
+  const passList = env.get("ENIGMAIL_PASS_ENV");
+  if (passList) {
+    const passNames = passList.split(":");
+    for (var k = 0; k < passNames.length; k++) {
+      passEnv.push(passNames[k]);
+    }
+  }
+
+  for (var j = 0; j < passEnv.length; j++) {
+    const envName = passEnv[j];
+    let envValue;
+
+    if (envName in gOverwriteEnvVar) {
+      envValue = gOverwriteEnvVar[envName];
+    } else {
+      envValue = env.get(envName);
+    }
+    if (envValue) {
+      EnigmailCore.addToEnvList(envName + "=" + envValue);
+    }
+  }
+
+  getEnigmailLog().DEBUG("core.jsm: Enigmail.initialize: Ec.envList = " + gEnvList + "\n");
+}
+
+
+function Enigmail() {
+  this.wrappedJSObject = this;
+}
+
+Enigmail.prototype = {
+  initialized: false,
+  initializationAttempted: false,
+  initializationError: "",
+
+  initialize: function(domWindow, version) {
+    this.initializationAttempted = true;
+
+    getEnigmailLog().DEBUG("core.jsm: Enigmail.initialize: START\n");
+
+    if (this.initialized) return;
+
+    this.environment = getEnvironment(this);
+
+    initializeSubprocessLogging(this.environment);
+    initializeEnvironment(this.environment);
+
+    try {
+      getEnigmailConsole().write("Initializing Enigmail service ...\n");
+    } catch (ex) {
+      failureOn(ex, this);
+    }
+
+    getEnigmailGpgAgent().setAgentPath(domWindow, this, gPreferredGpgPath);
+    getEnigmailGpgAgent().detectGpgAgent(domWindow, this);
+
+    initializeAgentInfo();
+
+    getEnigmailKeyRefreshService().start(getEnigmailKeyServer());
+
+    this.initialized = true;
+
+    getEnigmailLog().DEBUG("core.jsm: Enigmail.initialize: END\n");
+  },
+
+  reinitialize: function() {
+    getEnigmailLog().DEBUG("core.jsm: Enigmail.reinitialize:\n");
+    this.initialized = false;
+    this.initializationAttempted = true;
+
+    getEnigmailConsole().write("Reinitializing Enigmail service ...\n");
+    initializeEnvironment(this.environment);
+    getEnigmailGpgAgent().setAgentPath(null, this, gPreferredGpgPath);
+    this.initialized = true;
+  },
+
+  perferGpgPath: function(gpgPath) {
+    getEnigmailLog().DEBUG("core.jsm: Enigmail.perferGpgPath = " + gpgPath + "\n");
+    gPreferredGpgPath = gpgPath;
+  },
+
+  overwriteEnvVar: function(envVar) {
+    let envLines = envVar.split(/\n/);
+
+    gOverwriteEnvVar = [];
+    for (let i = 0; i < envLines.length; i++) {
+      let j = envLines[i].indexOf("=");
+      if (j > 0) {
+        gOverwriteEnvVar[envLines[i].substr(0, j)] = envLines[i].substr(j + 1);
+      }
+    }
+  },
+
+  getService: function(win, startingPreferences) {
+    if (!win) {
+      win = getEnigmailWindows().getBestParentWin();
+    }
+
+    getEnigmailLog().DEBUG("core.jsm: svc = " + this + "\n");
+
+    if (!this.initialized) {
+      const firstInitialization = !this.initializationAttempted;
+
+      try {
+        // Initialize enigmail
+        EnigmailCore.init(getEnigmailApp().getVersion());
+        this.initialize(win, getEnigmailApp().getVersion());
+
+        try {
+          // Reset alert count to default value
+          getEnigmailPrefs().getPrefBranch().clearUserPref("initAlert");
+        } catch (ex) {}
+      } catch (ex) {
+        if (firstInitialization) {
+          // Display initialization error alert
+          const errMsg = (this.initializationError ? this.initializationError : getEnigmailLocale().getString("accessError")) +
+            "\n\n" + getEnigmailLocale().getString("initErr.howToFixIt");
+
+          const checkedObj = {
+            value: false
+          };
+          if (getEnigmailPrefs().getPref("initAlert")) {
+            const r = getEnigmailDialog().longAlert(win, "Enigmail: " + errMsg,
+              getEnigmailLocale().getString("dlgNoPrompt"),
+              null, getEnigmailLocale().getString("initErr.setupWizard.button"),
+              null, checkedObj);
+            if (r >= 0 && checkedObj.value) {
+              getEnigmailPrefs().setPref("initAlert", false);
+            }
+            if (r == 1) {
+              // start setup wizard
+              getEnigmailWindows().openSetupWizard(win, false);
+              return Enigmail.getService(win);
+            }
+          }
+          if (getEnigmailPrefs().getPref("initAlert")) {
+            this.initializationAttempted = false;
+            gEnigmailService = null;
+          }
+        }
+
+        return null;
+      }
+
+      const configuredVersion = getEnigmailPrefs().getPref("configuredVersion");
+
+      getEnigmailLog().DEBUG("core.jsm: getService: last used version: " + configuredVersion + "\n");
+
+      if (firstInitialization && this.initialized &&
+        getEnigmailGpgAgent().agentType === "pgp") {
+        getEnigmailDialog().alert(win, getEnigmailLocale().getString("pgpNotSupported"));
+      }
+
+      if (this.initialized && (getEnigmailApp().getVersion() != configuredVersion)) {
+        getEnigmailConfigure().configureEnigmail(win, startingPreferences);
+      }
+    }
+
+    return this.initialized ? this : null;
+  }
+}; // Enigmail.prototype
+
+
+class Factory {
+  constructor(component) {
+    this.component = component;
+    this.register();
+    Object.freeze(this);
+  }
+
+  createInstance(outer, iid) {
+    if (outer) {
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+    }
+    return new this.component();
+  }
+
+  register() {
+    Cm.registerFactory(this.component.prototype.classID,
+      this.component.prototype.classDescription,
+      this.component.prototype.contractID,
+      this);
+  }
+
+  unregister() {
+    Cm.unregisterFactory(this.component.prototype.classID, this);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI.jsm
@@ -0,0 +1,26 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailCryptoAPI"];
+
+var gCurrentApi = null;
+var Services = ChromeUtils.import("resource://gre/modules/Services.jsm").Services;
+
+
+function EnigmailCryptoAPI() {
+  if (!gCurrentApi) {
+    const {
+      getGnuPGAPI
+    } = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI/gnupg.js");
+
+    gCurrentApi = getGnuPGAPI();
+  }
+
+  return gCurrentApi;
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/gnupg-decryption.jsm
@@ -0,0 +1,376 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["GnuPGDecryption"];
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
+const EnigmailKey = ChromeUtils.import("chrome://openpgp/content/modules/key.jsm").EnigmailKey;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+
+const STATUS_ERROR = EnigmailConstants.BAD_SIGNATURE | EnigmailConstants.DECRYPTION_FAILED;
+const STATUS_DECRYPTION_OK = EnigmailConstants.DECRYPTION_OKAY;
+const STATUS_GOODSIG = EnigmailConstants.GOOD_SIGNATURE;
+
+var GnuPGDecryption = {
+
+  /*
+   * options:
+   *  - logFile (the actual file)
+   *  - keyserver
+   *  - keyserverProxy
+   *  - fromAddr
+   *  - noOutput
+   *  - verifyOnly
+   *  - mimeSignatureFile
+   *  - maxOutputLength
+   *
+   */
+  getDecryptionArgs: function(options) {
+    var args = EnigmailGpg.getStandardArgs(true);
+
+    args.push("--log-file");
+    args.push(EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePath(options.logFile)));
+
+    if (options.keyserver && options.keyserver !== "") {
+      var keyserver = options.keyserver.trim();
+      args.push("--keyserver-options");
+      var keySrvArgs = "auto-key-retrieve";
+      var srvProxy = options.keyserverProxy;
+      if (srvProxy) {
+        keySrvArgs += ",http-proxy=" + srvProxy;
+      }
+      args.push(keySrvArgs);
+      args.push("--keyserver");
+      args.push(keyserver);
+    }
+
+    if (EnigmailGpg.getGpgFeature("supports-sender") && options.fromAddr) {
+      args.push("--sender");
+      args.push(options.fromAddr.toLowerCase());
+    }
+
+    if (options.noOutput) {
+      args.push("--verify");
+      if (options.mimeSignatureFile) {
+        args.push(options.mimeSignatureFile);
+        args.push("-");
+      }
+    }
+    else {
+      if (options.maxOutputLength) {
+        args.push("--max-output");
+        args.push(String(options.maxOutputLength));
+      }
+
+      args.push("--decrypt");
+    }
+
+    return args;
+  },
+  decryptMessageEnd: function(stderrStr, exitCode, outputLen, verifyOnly, noOutput, uiFlags, retStatusObj) {
+    EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: uiFlags=" + uiFlags + ", verifyOnly=" + verifyOnly + ", noOutput=" + noOutput + "\n");
+
+    stderrStr = stderrStr.replace(/\r\n/g, "\n");
+    EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: stderrStr=\n" + stderrStr + "\n");
+
+
+    var interactive = uiFlags & EnigmailConstants.UI_INTERACTIVE;
+    var pgpMime = uiFlags & EnigmailConstants.UI_PGP_MIME;
+    var allowImport = uiFlags & EnigmailConstants.UI_ALLOW_KEY_IMPORT;
+    var unverifiedEncryptedOK = uiFlags & EnigmailConstants.UI_UNVERIFIED_ENC_OK;
+    var j;
+
+    retStatusObj.statusFlags = 0;
+    retStatusObj.errorMsg = "";
+    retStatusObj.blockSeparation = "";
+
+    var errorMsg = EnigmailErrorHandling.parseErrorOutput(stderrStr, retStatusObj);
+    if (retStatusObj.statusFlags & STATUS_ERROR) {
+      retStatusObj.errorMsg = errorMsg;
+    }
+    else {
+      retStatusObj.errorMsg = "";
+    }
+
+    if (pgpMime) {
+      retStatusObj.statusFlags |= verifyOnly ? EnigmailConstants.PGP_MIME_SIGNED : EnigmailConstants.PGP_MIME_ENCRYPTED;
+    }
+
+    var statusMsg = retStatusObj.statusMsg;
+    exitCode = EnigmailExecution.fixExitCode(exitCode, retStatusObj);
+    if ((exitCode === 0) && !noOutput && !outputLen &&
+      ((retStatusObj.statusFlags & (STATUS_DECRYPTION_OK | STATUS_GOODSIG)) === 0)) {
+      exitCode = -1;
+    }
+
+    if (retStatusObj.statusFlags & EnigmailConstants.DISPLAY_MESSAGE && retStatusObj.extendedStatus.search(/\bdisp:/) >= 0) {
+      EnigmailDialog.alert(null, statusMsg);
+      return -1;
+    }
+
+    var errLines;
+    if (statusMsg) {
+      errLines = statusMsg.split(/\r?\n/);
+    }
+    else {
+      // should not really happen ...
+      errLines = stderrStr.split(/\r?\n/);
+    }
+
+    // possible STATUS Patterns (see GPG dod DETAILS.txt):
+    // one of these should be set for a signature:
+    var newsigPat = /^NEWSIG ?.*$/i;
+    var trustedsigPat = /^TRUST_(FULLY|ULTIMATE) ?.*$/i;
+    var goodsigPat = /^GOODSIG (\w{16}) (.*)$/i;
+    var badsigPat = /^BADSIG (\w{16}) (.*)$/i;
+    var expsigPat = /^EXPSIG (\w{16}) (.*)$/i;
+    var expkeysigPat = /^EXPKEYSIG (\w{16}) (.*)$/i;
+    var revkeysigPat = /^REVKEYSIG (\w{16}) (.*)$/i;
+    var errsigPat = /^ERRSIG (\w{16}) (.*)$/i;
+    // additional infos for good signatures:
+    var validSigPat = /^VALIDSIG (\w+) (.*) (\d+) (.*)/i;
+    // hint for a certain key id:
+    var userIdHintPat = /^USERID_HINT (\w{16}) (.*)$/i;
+    // to find out for which recipients the email was encrypted:
+    var encToPat = /^ENC_TO (\w{16}) (.*)$/i;
+
+    var matches;
+
+    var signed = false;
+    var goodOrExpOrRevSignature = false;
+    var sigKeyId = ""; // key of sender
+    var sigUserId = ""; // user ID of sender
+    var sigDetails = "";
+    var sigTrusted = false;
+    var encToDetails = "";
+    var encToArray = []; // collect ENC_TO lines here
+
+    for (j = 0; j < errLines.length; j++) {
+      EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: process: " + errLines[j] + "\n");
+
+      // ENC_TO entry
+      // - collect them for later processing to print details
+      matches = errLines[j].match(encToPat);
+      if (matches && (matches.length > 2)) {
+        encToArray.push("0x" + matches[1]);
+      }
+
+      // USERID_HINT entry
+      // - NOTE: NO END of loop
+      // ERROR: wrong to set userId because ecom is NOT the sender:
+      //matches = errLines[j].match(userIdHintPat);
+      //if (matches && (matches.length > 2)) {
+      //  sigKeyId = matches[1];
+      //  sigUserId = matches[2];
+      //}
+
+      // check for one of the possible SIG entries:
+
+      matches = errLines[j].match(newsigPat);
+      if (matches) {
+        if (signed) {
+          EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: multiple SIGN entries - ignoring previous signature\n");
+        }
+        signed = true;
+        goodOrExpOrRevSignature = false;
+        sigKeyId = "";
+        sigUserId = "";
+        sigDetails = "";
+        sigTrusted = false;
+        continue;
+      }
+
+      matches = errLines[j].match(trustedsigPat);
+      if (matches) {
+        sigTrusted = true;
+        continue;
+      }
+
+      matches = errLines[j].match(validSigPat);
+      if (matches && (matches.length > 4)) {
+        if (matches[4].length == 40) {
+          // in case of several subkeys refer to the main key ID.
+          // Only works with PGP V4 keys (Fingerprint length ==40)
+          sigKeyId = matches[4];
+        }
+        if (matches && (matches.length > 2)) {
+          sigDetails = errLines[j].substr(9);
+        }
+        continue;
+      }
+
+      // GOODSIG entry
+      matches = errLines[j].match(goodsigPat);
+      if (matches && (matches.length > 2)) {
+        if (signed) {
+          EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
+        }
+        signed = true;
+        goodOrExpOrRevSignature = true;
+        sigKeyId = matches[1];
+        sigUserId = matches[2];
+      }
+      else {
+        // BADSIG entry => signature found but bad
+        matches = errLines[j].match(badsigPat);
+        if (matches && (matches.length > 2)) {
+          if (signed) {
+            EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
+          }
+          signed = true;
+          goodOrExpOrRevSignature = false;
+          sigKeyId = matches[1];
+          sigUserId = matches[2];
+        }
+        else {
+          // EXPSIG entry => expired signature found
+          matches = errLines[j].match(expsigPat);
+          if (matches && (matches.length > 2)) {
+            if (signed) {
+              EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
+            }
+            signed = true;
+            goodOrExpOrRevSignature = true;
+            sigKeyId = matches[1];
+            sigUserId = matches[2];
+          }
+          else {
+            // EXPKEYSIG entry => signature found but key expired
+            matches = errLines[j].match(expkeysigPat);
+            if (matches && (matches.length > 2)) {
+              if (signed) {
+                EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
+              }
+              signed = true;
+              goodOrExpOrRevSignature = true;
+              sigKeyId = matches[1];
+              sigUserId = matches[2];
+            }
+            else {
+              // REVKEYSIG entry => signature found but key revoked
+              matches = errLines[j].match(revkeysigPat);
+              if (matches && (matches.length > 2)) {
+                if (signed) {
+                  EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
+                }
+                signed = true;
+                goodOrExpOrRevSignature = true;
+                sigKeyId = matches[1];
+                sigUserId = matches[2];
+              }
+              else {
+                // ERRSIG entry => signature found but key not usable or unavailable
+                matches = errLines[j].match(errsigPat);
+                if (matches && (matches.length > 2)) {
+                  if (signed) {
+                    EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: OOPS: multiple SIGN entries\n");
+                  }
+                  signed = true;
+                  goodOrExpOrRevSignature = false;
+                  sigKeyId = matches[1];
+                  // no user id with ecom istatus entry
+                }
+              }
+            }
+          }
+        }
+      }
+
+    } // end loop of processing errLines
+
+    if (sigTrusted) {
+      retStatusObj.statusFlags |= EnigmailConstants.TRUSTED_IDENTITY;
+    }
+
+    if (sigUserId && sigKeyId && EnigmailPrefs.getPref("displaySecondaryUid")) {
+      let keyObj = EnigmailKeyRing.getKeyById(sigKeyId);
+      if (keyObj) {
+        if (keyObj.photoAvailable) {
+          retStatusObj.statusFlags |= EnigmailConstants.PHOTO_AVAILABLE;
+        }
+        sigUserId = EnigmailKeyRing.getValidUids(sigKeyId).join("\n");
+      }
+    }
+    else if (sigUserId) {
+      sigUserId = EnigmailData.convertToUnicode(sigUserId, "UTF-8");
+    }
+
+    // add list of keys used for encryption if known (and their user IDs) if known
+    // Parsed status messages are something like (here the German version):
+    //    [GNUPG:] ENC_TO AAAAAAAAAAAAAAAA 1 0
+    //    [GNUPG:] ENC_TO 5B820D2D4553884F 16 0
+    //    [GNUPG:] ENC_TO 37904DF2E631552F 1 0
+    //    [GNUPG:] ENC_TO BBBBBBBBBBBBBBBB 1 0
+    //    gpg: verschlüsselt mit 3072-Bit RSA Schlüssel, ID BBBBBBBB, erzeugt 2009-11-28
+    //          "Joe Doo <joe.doo@domain.de>"
+    //    [GNUPG:] NO_SECKEY E71712DF47BBCC40
+    //    gpg: verschlüsselt mit RSA Schlüssel, ID AAAAAAAA
+    //    [GNUPG:] NO_SECKEY AAAAAAAAAAAAAAAA
+    if (encToArray.length > 0) {
+      // for each key also show an associated user ID if known:
+      for (var encIdx = 0; encIdx < encToArray.length; ++encIdx) {
+        var localKeyId = encToArray[encIdx];
+        // except for ID 00000000, which signals hidden keys
+        if (localKeyId != "0x0000000000000000") {
+          let localKey = EnigmailKeyRing.getKeyById(localKeyId);
+          if (localKey) {
+            encToArray[encIdx] += " (" + localKey.userId + ")";
+          }
+        }
+        else {
+          encToArray[encIdx] = EnigmailLocale.getString("hiddenKey");
+        }
+      }
+      encToDetails = "\n  " + encToArray.join(",\n  ") + "\n";
+    }
+
+    retStatusObj.userId = sigUserId;
+    retStatusObj.keyId = sigKeyId;
+    retStatusObj.sigDetails = sigDetails;
+    retStatusObj.encToDetails = encToDetails;
+
+    if (signed) {
+      if (goodOrExpOrRevSignature) {
+        retStatusObj.errorMsg = EnigmailLocale.getString("prefGood", [sigUserId]);
+        /* + ", " + EnigmailLocale.getString("keyId") + " 0x" + sigKeyId.substring(8,16); */
+      }
+      else {
+        if (sigUserId.length > 0) {
+          retStatusObj.errorMsg = EnigmailLocale.getString("prefBad", [sigUserId]);
+        }
+        if (!exitCode)
+          exitCode = 1;
+      }
+    }
+
+    if (retStatusObj.statusFlags & EnigmailConstants.UNVERIFIED_SIGNATURE) {
+      retStatusObj.keyId = EnigmailKey.extractPubkey(statusMsg);
+
+      if (retStatusObj.statusFlags & EnigmailConstants.DECRYPTION_OKAY) {
+        exitCode = 0;
+      }
+    }
+
+    if (exitCode !== 0) {
+      // Error processing
+      EnigmailLog.DEBUG("gnupg-decryption.jsm: decryptMessageEnd: command execution exit code: " + exitCode + "\n");
+    }
+
+    return exitCode;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/gnupg-key.jsm
@@ -0,0 +1,121 @@
+/*
+ * 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/.
+ */
+
+/****
+   Private sub-module to gnupg.js for handling key import/export
+ ****/
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["GnuPG_importKeyFromFile", "GnuPG_extractSecretKey"];
+
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailLocale;
+
+
+async function GnuPG_importKeyFromFile(inputFile) {
+  EnigmailLog.DEBUG("gnupg-key.jsm: importKeysFromFile: fileName=" + inputFile.path + "\n");
+  var command = EnigmailGpg.agentPath;
+  var args = EnigmailGpg.getStandardArgs(false).concat(["--no-tty", "--batch", "--no-verbose", "--status-fd", "2", "--no-auto-check-trustdb", "--import"]);
+
+  var fileName = EnigmailFiles.getEscapedFilename((inputFile.QueryInterface(Ci.nsIFile)).path);
+
+  args.push(fileName);
+
+  let res = await EnigmailExecution.execAsync(command, args, "");
+  let statusMsg = res.statusMsg;
+
+  var keyList = [];
+  let importedKeys = [];
+  let importSum = 0;
+  let importUnchanged = 0;
+
+  // IMPORT_RES <count> <no_user_id> <imported> 0 <unchanged>
+  //    <n_uids> <n_subk> <n_sigs> <n_revoc> <sec_read> <sec_imported> <sec_dups> <not_imported>
+  if (statusMsg) {
+    let import_res = statusMsg.match(/^IMPORT_RES ([0-9]+) ([0-9]+) ([0-9]+) 0 ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+) ([0-9]+)/m);
+
+    if (import_res !== null) {
+      let secCount = parseInt(import_res[9], 10); // number of secret keys found
+      let secImported = parseInt(import_res[10], 10); // number of secret keys imported
+      let secDups = parseInt(import_res[11], 10); // number of secret keys already on the keyring
+
+      if (secCount !== secImported + secDups) {
+        res.errorMsg = EnigmailLocale.getString("import.secretKeyImportError");
+        res.exitCode = 1;
+      }
+      else {
+        importSum = parseInt(import_res[1], 10);
+        importUnchanged = parseInt(import_res[4], 10);
+        res.exitCode = 0;
+        var statusLines = statusMsg.split(/\r?\n/);
+
+        for (let j = 0; j < statusLines.length; j++) {
+          var matches = statusLines[j].match(/IMPORT_OK ([0-9]+) (\w+)/);
+          if (matches && (matches.length > 2)) {
+            if (typeof (keyList[matches[2]]) != "undefined") {
+              keyList[matches[2]] |= Number(matches[1]);
+            }
+            else
+              keyList[matches[2]] = Number(matches[1]);
+
+            importedKeys.push(matches[2]);
+            EnigmailLog.DEBUG("gnupg-key.jsm: importKeysFromFile: imported " + matches[2] + ":" + matches[1] + "\n");
+          }
+        }
+      }
+    }
+  }
+
+  return {
+    exitCode: res.exitCode,
+    errorMsg: res.errorMsg,
+    importedKeys: importedKeys,
+    importSum: importSum,
+    importUnchanged: importUnchanged
+  };
+}
+
+
+async function GnuPG_extractSecretKey(userId, minimalKey) {
+  let args = EnigmailGpg.getStandardArgs(true);
+  let exitCode = -1,
+    errorMsg = "";
+
+  if (minimalKey) {
+    args.push("--export-options");
+    args.push("export-minimal,no-export-attributes");
+  }
+
+  args.push("-a");
+  args.push("--export-secret-keys");
+
+  if (userId) {
+    args = args.concat(userId.split(/[ ,\t]+/));
+  }
+
+  let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "");
+
+  if (res.stdoutData) {
+    exitCode = 0;
+  }
+
+  if (exitCode !== 0) {
+    if (res.errorMsg) {
+      errorMsg = EnigmailFiles.formatCmdLine(EnigmailGpg.agentPath, args);
+      errorMsg += "\n" + res.errorMsg;
+    }
+  }
+
+  return {
+    keyData: res.stdoutData,
+    exitCode: exitCode,
+    errorMsg: errorMsg
+  };
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/gnupg-keylist.jsm
@@ -0,0 +1,503 @@
+/*
+ * 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/.
+ */
+
+/****
+   Private sub-module to gnupg.js for handling key lists from GnuPG
+ ****/
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["obtainKeyList", "createKeyObj",
+  "getPhotoFileFromGnuPG", "extractSignatures", "getGpgKeyData"
+];
+
+const EnigmailTime = ChromeUtils.import("chrome://openpgp/content/modules/time.jsm").EnigmailTime;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailTrust = ChromeUtils.import("chrome://openpgp/content/modules/trust.jsm").EnigmailTrust;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+
+// field ID's of key list (as described in the doc/DETAILS file in the GnuPG distribution)
+const ENTRY_ID = 0;
+const KEY_TRUST_ID = 1;
+const KEY_SIZE_ID = 2;
+const KEY_ALGO_ID = 3;
+const KEY_ID = 4;
+const CREATED_ID = 5;
+const EXPIRY_ID = 6;
+const UID_ID = 7;
+const OWNERTRUST_ID = 8;
+const USERID_ID = 9;
+const SIG_TYPE_ID = 10;
+const KEY_USE_FOR_ID = 11;
+
+const ALGO_SYMBOL = {
+  1: "RSA",
+  2: "RSA",
+  3: "RSA",
+  16: "ELG",
+  17: "DSA",
+  18: "ECDH",
+  19: "ECDSA",
+  20: "ELG",
+  22: "EDDSA"
+};
+
+const UNKNOWN_SIGNATURE = "[User ID not found]";
+
+const NS_RDONLY = 0x01;
+const NS_WRONLY = 0x02;
+const NS_CREATE_FILE = 0x08;
+const NS_TRUNCATE = 0x20;
+const STANDARD_FILE_PERMS = 0o600;
+
+const NS_LOCALFILEOUTPUTSTREAM_CONTRACTID = "@mozilla.org/network/file-output-stream;1";
+
+/**
+ * Get key list from GnuPG.
+ *
+ * @param {Array of String} onlyKeys: only load data for specified key IDs
+ *
+ * @return {Promise<Array Object>}:
+ * key objects as specified in EnigmailKeyObj.constructor
+ */
+async function obtainKeyList(onlyKeys = null) {
+  EnigmailLog.DEBUG("gnupg-keylist.jsm: obtainKeyList()\n");
+
+  let secKeyList = [],
+    pubKeyList = [];
+  let commonArgs = EnigmailGpg.getStandardArgs(true);
+  commonArgs = commonArgs.concat(["--with-fingerprint", "--fixed-list-mode", "--with-colons"]);
+
+  let args = commonArgs.concat(["--list-keys"]);
+  if (onlyKeys) {
+    args = args.concat(onlyKeys);
+  }
+
+  let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "");
+  pubKeyList = res.stdoutData.split(/\n/);
+
+  let keyList = {
+    keys: [],
+    index: []
+  };
+
+  EnigmailLog.DEBUG(`gnupg-keylist.jsm: obtainKeyList: #lines: ${pubKeyList.length}\n`);
+  if (pubKeyList.length > 0) {
+    appendKeyItems(pubKeyList, keyList);
+
+    args = commonArgs.concat(["--list-secret-keys"]);
+    if (onlyKeys) {
+      args = args.concat(onlyKeys);
+    }
+
+    res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "");
+    secKeyList = res.stdoutData.split(/\n/);
+    appendKeyItems(secKeyList, keyList);
+  }
+
+  return keyList;
+}
+
+
+/**
+ * Append key objects to a given key cache
+ *
+ * @param keyListString: array of |string| formatted output from GnuPG for key listing
+ * @param keyList:    |object| holding the resulting key list
+ *                         obj.keyList:     Array holding key objects
+ *                         obj.keySortList: Array holding values to make sorting easier
+ *
+ * no return value
+ */
+function appendKeyItems(keyListString, keyList) {
+  EnigmailLog.DEBUG("gnupg-keylist.jsm: appendKeyItems()\n");
+  let keyObj = {};
+  let uatNum = 0; // counter for photos (counts per key)
+
+  const TRUSTLEVELS_SORTED = EnigmailTrust.trustLevelsSorted();
+
+  for (let i = 0; i < keyListString.length; i++) {
+    let listRow = keyListString[i].split(/:/);
+    if (listRow.length === 0) continue;
+
+    switch (listRow[ENTRY_ID]) {
+      case "pub":
+        keyObj = createKeyObj(listRow);
+        uatNum = 0;
+        keyList.keys.push(keyObj);
+        keyList.index[keyObj.keyId] = keyObj;
+        break;
+      case "sec":
+        keyObj = keyList.index[listRow[KEY_ID]];
+        if (keyObj) {
+          keyObj.secretAvailable = true;
+          // create a dummy object that is not added to the list since we already have the key
+          keyObj = createKeyObj(listRow);
+        } else {
+          appendUnkownSecretKey(listRow[KEY_ID], keyListString, i, keyList);
+          keyObj = keyList.index[listRow[KEY_ID]];
+          keyObj.secretAvailable = true;
+        }
+        break;
+      case "fpr":
+        // only take first fpr line, this is the fingerprint of the primary key and what we want
+        if (keyObj.fpr === "") {
+          keyObj.fpr = listRow[USERID_ID];
+        }
+        break;
+      case "uid":
+        if (listRow[USERID_ID].length === 0) {
+          listRow[USERID_ID] = "-";
+        }
+        if (typeof(keyObj.userId) !== "string") {
+          keyObj.userId = EnigmailData.convertGpgToUnicode(listRow[USERID_ID]);
+          if (TRUSTLEVELS_SORTED.indexOf(listRow[KEY_TRUST_ID]) < TRUSTLEVELS_SORTED.indexOf(keyObj.keyTrust)) {
+            // reduce key trust if primary UID is less trusted than public key
+            keyObj.keyTrust = listRow[KEY_TRUST_ID];
+          }
+        }
+
+        keyObj.userIds.push({
+          userId: EnigmailData.convertGpgToUnicode(listRow[USERID_ID]),
+          keyTrust: listRow[KEY_TRUST_ID],
+          uidFpr: listRow[UID_ID],
+          type: "uid"
+        });
+
+        break;
+      case "sub":
+        keyObj.subKeys.push({
+          keyId: listRow[KEY_ID],
+          expiry: EnigmailTime.getDateTime(listRow[EXPIRY_ID], true, false),
+          expiryTime: Number(listRow[EXPIRY_ID]),
+          keyTrust: listRow[KEY_TRUST_ID],
+          keyUseFor: listRow[KEY_USE_FOR_ID],
+          keySize: listRow[KEY_SIZE_ID],
+          algoSym: ALGO_SYMBOL[listRow[KEY_ALGO_ID]],
+          created: EnigmailTime.getDateTime(listRow[CREATED_ID], true, false),
+          keyCreated: Number(listRow[CREATED_ID]),
+          type: "sub"
+        });
+        break;
+      case "uat":
+        if (listRow[USERID_ID].indexOf("1 ") === 0) {
+          const userId = EnigmailLocale.getString("userAtt.photo");
+          keyObj.userIds.push({
+            userId: userId,
+            keyTrust: listRow[KEY_TRUST_ID],
+            uidFpr: listRow[UID_ID],
+            type: "uat",
+            uatNum: uatNum
+          });
+          keyObj.photoAvailable = true;
+          ++uatNum;
+        }
+        break;
+    }
+  }
+}
+
+function createKeyObj(lineArr) {
+  let keyObj = {};
+  if (lineArr[ENTRY_ID] === "pub" || lineArr[ENTRY_ID] === "sec") {
+    keyObj.keyId = lineArr[KEY_ID];
+    keyObj.expiryTime = Number(lineArr[EXPIRY_ID]);
+    keyObj.created = EnigmailTime.getDateTime(lineArr[CREATED_ID], true, false);
+    keyObj.keyCreated = Number(lineArr[CREATED_ID]);
+    keyObj.keyTrust = lineArr[KEY_TRUST_ID];
+    keyObj.keyUseFor = lineArr[KEY_USE_FOR_ID];
+    keyObj.ownerTrust = lineArr[OWNERTRUST_ID];
+    keyObj.algoSym = ALGO_SYMBOL[lineArr[KEY_ALGO_ID]];
+    keyObj.keySize = lineArr[KEY_SIZE_ID];
+    keyObj.userIds = [];
+    keyObj.subKeys = [];
+    keyObj.fpr = "";
+    keyObj.userId = null;
+    keyObj.photoAvailable = false;
+  } else if (lineArr[ENTRY_ID] === "grp") {
+    keyObj.keyUseFor = "G";
+    keyObj.userIds = [];
+    keyObj.subKeys = [];
+  }
+  keyObj.type = lineArr[ENTRY_ID];
+
+  return keyObj;
+}
+
+
+/**
+ * Handle secret keys for which gpg 2.0 does not create a public key record
+ */
+function appendUnkownSecretKey(keyId, aKeyList, startIndex, keyList) {
+  EnigmailLog.DEBUG(`gnupg-keylist.jsm: appendUnkownSecretKey: keyId: ${keyId}\n`);
+
+  let keyListStr = [];
+
+  for (let j = startIndex; j < aKeyList.length && (j === startIndex || aKeyList[j].substr(0, 4) !== "sec:"); j++) {
+    keyListStr.push(aKeyList[j]);
+  }
+
+  // make the listing a "public" key
+  keyListStr[0] = keyListStr[0].replace(/^sec:/, "pub:");
+
+  appendKeyItems(keyListStr, keyList);
+}
+
+
+/**
+ * Extract a photo ID from a key, store it as file and return the file object.
+ 
+ * @param {String} keyId:       Key ID / fingerprint
+ * @param {Number} photoNumber: number of the photo on the key, starting with 0
+ *
+ * @return {Promise<nsIFile>} object or null in case no data / error.
+ */
+async function getPhotoFileFromGnuPG(keyId, photoNumber) {
+  EnigmailLog.DEBUG(`gnupg-keylist.jsm: getPhotoFileFromGnuPG, keyId=${keyId} photoNumber=${photoNumber}\n`);
+
+  const GPG_ADDITIONAL_OPTIONS = ["--no-secmem-warning", "--no-verbose", "--no-auto-check-trustdb",
+    "--batch", "--no-tty", "--no-verbose", "--status-fd", "1", "--attribute-fd", "2",
+    "--fixed-list-mode", "--list-keys", keyId
+  ];
+  const args = EnigmailGpg.getStandardArgs(false).concat(GPG_ADDITIONAL_OPTIONS);
+
+  let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args);
+  let photoData = res.stderrData;
+  let outputTxt = res.stdoutData;
+
+  if (!outputTxt || !photoData) {
+    return null;
+  }
+
+  if (EnigmailOS.isDosLike && EnigmailGpg.getGpgFeature("windows-photoid-bug")) {
+    // workaround for error in gpg
+    photoData = photoData.replace(/\r\n/g, "\n");
+  }
+
+  // [GNUPG:] ATTRIBUTE A053069284158FC1E6770BDB57C9EB602B0717E2 2985
+  let foundPicture = -1;
+  let skipData = 0;
+  let imgSize = -1;
+  const statusLines = outputTxt.split(/[\n\r+]/);
+
+  for (let i = 0; i < statusLines.length; i++) {
+    const matches = statusLines[i].match(/\[GNUPG:\] ATTRIBUTE ([A-F\d]+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+) (\d+)/);
+    if (matches && matches[3] == "1") {
+      // attribute is an image
+      foundPicture++;
+      if (foundPicture === photoNumber) {
+        imgSize = Number(matches[2]);
+        break;
+      } else {
+        skipData += Number(matches[2]);
+      }
+    }
+  }
+
+  if (foundPicture >= 0 && foundPicture === photoNumber) {
+    if (photoData.search(/^gpg: /) === 0) {
+      // skip disturbing gpg output
+      let i = photoData.search(/\n/) + 1;
+      skipData += i;
+    }
+
+    const pictureData = photoData.substr(16 + skipData, imgSize);
+    if (!pictureData.length) {
+      return null;
+    }
+
+    try {
+      const flags = NS_WRONLY | NS_CREATE_FILE | NS_TRUNCATE;
+      const picFile = EnigmailFiles.getTempDirObj();
+
+      picFile.append(keyId + ".jpg");
+      picFile.createUnique(picFile.NORMAL_FILE_TYPE, STANDARD_FILE_PERMS);
+
+      const fileStream = Cc[NS_LOCALFILEOUTPUTSTREAM_CONTRACTID].createInstance(Ci.nsIFileOutputStream);
+      fileStream.init(picFile, flags, STANDARD_FILE_PERMS, 0);
+      if (fileStream.write(pictureData, pictureData.length) !== pictureData.length) {
+        fileStream.close();
+        throw Components.results.NS_ERROR_FAILURE;
+      }
+
+      fileStream.flush();
+      fileStream.close();
+
+      // delete picFile upon exit
+      let extAppLauncher = Cc["@mozilla.org/mime;1"].getService(Ci.nsPIExternalAppLauncher);
+      extAppLauncher.deleteTemporaryFileOnExit(picFile);
+      return picFile;
+    } catch (ex) {}
+  }
+  return null;
+}
+
+
+/**
+ * Return signatures for a given key list
+ *
+ * @param {String} gpgKeyList         Output from gpg such as produced by getKeySig()
+ *                                    Only the first public key is processed!
+ * @param {Boolean} ignoreUnknownUid  true if unknown signer's UIDs should be filtered out
+ *
+ * @return {Array of Object}:
+ *     - uid
+ *     - uidLabel
+ *     - creationDate
+ *     - sigList: [uid, creationDate, signerKeyId, sigType ]
+ */
+
+function extractSignatures(gpgKeyList, ignoreUnknownUid) {
+  EnigmailLog.DEBUG("gnupg.js: extractSignatures\n");
+
+  var listObj = {};
+
+  let havePub = false;
+  let currUid = "",
+    keyId = "",
+    fpr = "";
+
+  const lineArr = gpgKeyList.split(/\n/);
+  for (let i = 0; i < lineArr.length; i++) {
+    // process lines such as:
+    //  tru::1:1395895453:1442881280:3:1:5
+    //  pub:f:4096:1:C1B875ED336XX959:2299509307:1546189300::f:::scaESCA:
+    //  fpr:::::::::102A1C8CC524A966849C33D7C8B157EA336XX959:
+    //  uid:f::::1388511201::67D5B96DC564598D4D4D9E0E89F5B83C9931A154::Joe Fox <joe@fox.com>:
+    //  sig:::1:C8B157EA336XX959:2299509307::::Joe Fox <joe@fox.com>:13x:::::2:
+    //  sub:e:2048:1:B214734F0F5C7041:1316219469:1199912694:::::e:
+    //  sub:f:2048:1:70E7A471DABE08B0:1316221524:1546189300:::::s:
+    const lineTokens = lineArr[i].split(/:/);
+    switch (lineTokens[ENTRY_ID]) {
+      case "pub":
+        if (havePub) {
+          return listObj;
+        }
+        havePub = true;
+        keyId = lineTokens[KEY_ID];
+        break;
+      case "fpr":
+        if (fpr === "")
+          fpr = lineTokens[USERID_ID];
+        break;
+      case "uid":
+      case "uat":
+        currUid = lineTokens[UID_ID];
+        listObj[currUid] = {
+          userId: lineTokens[ENTRY_ID] == "uat" ? EnigmailLocale.getString("keyring.photo") : EnigmailData.convertGpgToUnicode(lineTokens[USERID_ID]),
+          rawUserId: lineTokens[USERID_ID],
+          keyId: keyId,
+          fpr: fpr,
+          created: EnigmailTime.getDateTime(lineTokens[CREATED_ID], true, false),
+          sigList: []
+        };
+        break;
+      case "sig":
+        if (lineTokens[SIG_TYPE_ID].substr(0, 2).toLowerCase() !== "1f") {
+          // ignrore revoked signature
+
+          let sig = {
+            userId: EnigmailData.convertGpgToUnicode(lineTokens[USERID_ID]),
+            created: EnigmailTime.getDateTime(lineTokens[CREATED_ID], true, false),
+            signerKeyId: lineTokens[KEY_ID],
+            sigType: lineTokens[SIG_TYPE_ID],
+            sigKnown: lineTokens[USERID_ID] != UNKNOWN_SIGNATURE
+          };
+
+          if (!ignoreUnknownUid || sig.userId != UNKNOWN_SIGNATURE) {
+            listObj[currUid].sigList.push(sig);
+          }
+        }
+        break;
+    }
+  }
+
+  return listObj;
+}
+
+
+async function getGpgKeyData(armorKeyString) {
+  EnigmailLog.DEBUG("gnupg.js: getGpgKeyData()\n");
+
+  if (!EnigmailGpg.getGpgFeature("supports-show-only")) {
+    throw "unsupported";
+  }
+
+  let args = EnigmailGpg.getStandardArgs(false).concat(["--no-tty", "--batch", "--no-verbose", "--with-fingerprint", "--with-colons", "--import-options", "import-show", "--dry-run", "--import"]);
+
+  let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, armorKeyString);
+  let lines = res.stdoutData.split(/\n/);
+
+  let key = {};
+  let keyId = "";
+  let keyList = [];
+  /*
+  pub:u:256:22:84F83BE88C892606:1525969855:1683649855::u:::scESC:::::ed25519:::0:
+  fpr:::::::::AFE1B65C5F39ACA7960B22CD84F83BE88C892606:
+  uid:u::::1525969914::22DB32406212400B52CDC74DA2B33418637430F1::Patrick (ECC) <patrick@enigmail.net>::::::::::0:
+  uid:u::::1525969855::F70B7A77F085AA7BA003D6AFAB6FF0DB1FC901B0::enigmail <patrick@enigmail.net>::::::::::0:
+  sub:u:256:18:329DAB3350400C40:1525969855:1683649855:::::e:::::cv25519::
+  fpr:::::::::3B154538D4DFAA19BDADAAD0329DAB3350400C40:
+  */
+
+  for (let i = 0; i < lines.length; i++) {
+    const lineTokens = lines[i].split(/:/);
+
+    switch (lineTokens[ENTRY_ID]) {
+      case "pub":
+      case "sec":
+        key = {
+          id: lineTokens[KEY_ID],
+          fpr: null,
+          name: null,
+          isSecret: false,
+          created: EnigmailTime.getDateTime(lineTokens[CREATED_ID], true, false),
+          uids: []
+        };
+
+        if (!(key.id in keyList)) {
+          keyList[key.id] = key;
+        }
+
+        if (lineTokens[ENTRY_ID] === "sec") {
+          keyList[key.id].isSecret = true;
+        }
+        break;
+      case "fpr":
+        if (!key.fpr) {
+          key.fpr = lineTokens[USERID_ID];
+        }
+        break;
+      case "uid":
+        if (!key.name) {
+          key.name = lineTokens[USERID_ID];
+        }
+        else {
+          key.uids.push(lineTokens[USERID_ID]);
+        }
+        break;
+      case "rvs":
+      case "rvk":
+        keyId = lineTokens[KEY_ID];
+        if (keyId in keyList) {
+          keyList[keyId].revoke = true;
+        } else {
+          keyList[keyId] = {
+            revoke: true,
+            id: keyId
+          };
+        }
+        break;
+    }
+  }
+
+  return keyList;
+}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/gnupg.js
@@ -0,0 +1,418 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["getGnuPGAPI"];
+
+var Services = Components.utils.import("resource://gre/modules/Services.jsm").Services;
+
+Services.scriptloader.loadSubScript("chrome://openpgp/content/modules/cryptoAPI/interface.js",
+  null, "UTF-8"); /* global CryptoAPI */
+
+/* global getOpenPGP: false, EnigmailLog: false */
+
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailLazy = ChromeUtils.import("chrome://openpgp/content/modules/lazy.jsm").EnigmailLazy;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailTime = ChromeUtils.import("chrome://openpgp/content/modules/time.jsm").EnigmailTime;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailPassword = ChromeUtils.import("chrome://openpgp/content/modules/passwords.jsm").EnigmailPassword;
+const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
+const GnuPGDecryption = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI/gnupg-decryption.jsm").GnuPGDecryption;
+
+const {
+  obtainKeyList,
+  createKeyObj,
+  getPhotoFileFromGnuPG,
+  extractSignatures,
+  getGpgKeyData
+} = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI/gnupg-keylist.jsm");
+
+const {
+  GnuPG_importKeyFromFile,
+  GnuPG_extractSecretKey
+} = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI/gnupg-key.jsm");
+
+/**
+ * GnuPG implementation of CryptoAPI
+ */
+
+class GnuPGCryptoAPI extends CryptoAPI {
+  constructor() {
+    super();
+    this.api_name = "GnuPG";
+  }
+
+  /**
+   * Get the list of all knwn keys (including their secret keys)
+   * @param {Array of String} onlyKeys: [optional] only load data for specified key IDs
+   *
+   * @return {Promise<Array of Object>}
+   */
+  async getKeys(onlyKeys = null) {
+    let keyList = await obtainKeyList(onlyKeys);
+    return keyList.keys;
+  }
+
+  /**
+   * Get groups defined in gpg.conf in the same structure as KeyObject
+   *
+   * @return {Array of KeyObject} with type = "grp"
+   */
+  getGroups() {
+    let groups = EnigmailGpg.getGpgGroups();
+
+    let r = [];
+    for (var i = 0; i < groups.length; i++) {
+
+      let keyObj = createKeyObj(["grp"]);
+      keyObj.keyTrust = "g";
+      keyObj.userId = EnigmailData.convertGpgToUnicode(groups[i].alias).replace(/\\e3A/g, ":");
+      keyObj.keyId = keyObj.userId;
+      var grpMembers = EnigmailData.convertGpgToUnicode(groups[i].keylist).replace(/\\e3A/g, ":").split(/[,;]/);
+      for (var grpIdx = 0; grpIdx < grpMembers.length; grpIdx++) {
+        keyObj.userIds.push({
+          userId: grpMembers[grpIdx],
+          keyTrust: "q"
+        });
+      }
+      r.push(keyObj);
+    }
+
+    return r;
+  }
+
+
+  /**
+   * Obtain signatures for a given set of key IDs.
+   *
+   * @param {String}  keyId:            space-separated list of key IDs
+   * @param {Boolean} ignoreUnknownUid: if true, filter out unknown signer's UIDs
+   *
+   * @return {Promise<Array of Object>} - see extractSignatures()
+   */
+  async getKeySignatures(keyId, ignoreUnknownUid = false) {
+    EnigmailLog.DEBUG(`gnupg.js: getKeySignatures: ${keyId}\n`);
+
+    const args = EnigmailGpg.getStandardArgs(true).concat(["--with-fingerprint", "--fixed-list-mode", "--with-colons", "--list-sig"]).concat(keyId.split(" "));
+
+    let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, "");
+
+    if (!(res.statusFlags & EnigmailConstants.BAD_SIGNATURE)) {
+      // ignore exit code as recommended by GnuPG authors
+      res.exitCode = 0;
+    }
+
+    if (res.exitCode !== 0) {
+      if (res.errorMsg) {
+        res.errorMsg += "\n" + EnigmailFiles.formatCmdLine(EnigmailGpg.agentPath, args);
+        res.errorMsg += "\n" + res.errorMsg;
+      }
+      return "";
+    }
+
+    if (res.stdoutData.length > 0) {
+      return extractSignatures(res.stdoutData, ignoreUnknownUid);
+    }
+    return null;
+  }
+
+
+  /**
+   * Export the minimum key for the public key object:
+   * public key, primary user ID, newest encryption subkey
+   *
+   * @param {String} fpr:                a single FPR
+   * @param {String} email:              [optional] the email address of the desired user ID.
+   *                                     If the desired user ID cannot be found or is not valid, use the primary UID instead
+   * @param {Array<Number>} subkeyDates: [optional] remove subkeys with sepcific creation Dates
+   *
+   * @return {Promise<Object>}:
+   *    - exitCode (0 = success)
+   *    - errorMsg (if exitCode != 0)
+   *    - keyData: BASE64-encded string of key data
+   */
+  async getMinimalPubKey(fpr, email, subkeyDates) {
+    EnigmailLog.DEBUG(`gnupg.js: getMinimalPubKey: ${fpr}\n`);
+
+    let retObj = {
+      exitCode: 0,
+      errorMsg: "",
+      keyData: ""
+    };
+    let minimalKeyBlock = null;
+
+    let args = EnigmailGpg.getStandardArgs(true);
+
+    if (EnigmailGpg.getGpgFeature("export-specific-uid")) {
+      // Use GnuPG filters if possible
+      let dropSubkeyFilter = "usage!~e && usage!~s";
+
+      if (subkeyDates && subkeyDates.length > 0) {
+        dropSubkeyFilter = subkeyDates.map(x => `key_created!=${x}`).join(" && ");
+      }
+      args = args.concat(["--export-options", "export-minimal,no-export-attributes",
+        "--export-filter", "keep-uid=" + (email ? "mbox=" + email : "primary=1"),
+        "--export-filter", "drop-subkey=" + dropSubkeyFilter,
+        "--export", fpr
+      ]);
+    } else {
+      args = args.concat(["--export-options", "export-minimal,no-export-attributes", "-a", "--export", fpr]);
+    }
+
+    const statusObj = {};
+    const exitCodeObj = {};
+    let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args);
+    let keyBlock = res.stdoutData;
+
+    // GnuPG 2.1.10+
+    if (!EnigmailGpg.getGpgFeature("export-result")) {
+      retObj.exitCode = 2;
+      retObj.errorMsg = EnigmailLocale.getString("failKeyExtract");
+    }
+
+    let r = new RegExp("^\\[GNUPG:\\] EXPORTED " + fpr, "m");
+    if (res.stderrData.search(r) < 0) {
+      retObj.exitCode = 2;
+      retObj.errorMsg = EnigmailLocale.getString("failKeyExtract");
+    }
+
+    retObj.keyData = btoa(keyBlock);
+    return retObj;
+  }
+
+  /**
+   * Extract a photo ID from a key, store it as file and return the file object.
+   *
+   * @param {String} keyId:       Key ID / fingerprint
+   * @param {Number} photoNumber: number of the photo on the key, starting with 0
+   *
+   * @return {nsIFile} object or null in case no data / error.
+   */
+  async getPhotoFile(keyId, photoNumber) {
+    let file = await getPhotoFileFromGnuPG(keyId, photoNumber);
+    return file;
+  }
+
+  /**
+   * Import key(s) from a file
+   *
+   * @param {nsIFile} inputFile:  the file holding the keys
+   *
+   * @return {Object} or null in case no data / error:
+   *   - {Number}          exitCode:        result code (0: OK)
+   *   - {Array of String) importedKeys:    imported fingerprints
+   *   - {String}          errorMsg:        human readable error message
+   *   - {Number}          importSum:       total number of processed keys
+   *   - {Number}          importUnchanged: number of unchanged keys
+   */
+  async importKeyFromFile(inputFile) {
+    let keys = await GnuPG_importKeyFromFile(inputFile);
+    return keys;
+  }
+
+  /**
+   * Export secret key(s) to a file
+   *
+   * @param {String}  keyId      Specification by fingerprint or keyID
+   * @param {Boolean} minimalKey  if true, reduce key to minimum required
+   *
+   * @return {Object}:
+   *   - {Number} exitCode:  result code (0: OK)
+   *   - {String} keyData:   ASCII armored key data material
+   *   - {String} errorMsg:  error message in case exitCode !== 0
+   */
+
+  async extractSecretKey(keyId, minimalKey) {
+    let ret = await GnuPG_extractSecretKey(keyId, minimalKey);
+
+    if (ret.exitCode !== 0) {
+      ret.errorMsg = EnigmailLocale.getString("failKeyExtract") + "\n" + ret.errorMsg;
+    }
+    return ret;
+  }
+
+  /**
+   *
+   * @param {byte} byteData    The encrypted data
+   *
+   * @return {String or null} - the name of the attached file
+   */
+
+  async getFileName(byteData) {
+    EnigmailLog.DEBUG(`gnupg.js: getFileName()\n`);
+    const args = EnigmailGpg.getStandardArgs(true).concat(EnigmailPassword.command()).concat(["--decrypt"]);
+
+    let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, byteData + "\n");
+
+    const matches = res.stderrData.match(/^(\[GNUPG:\] PLAINTEXT [0-9]+ [0-9]+ )(.*)$/m);
+    if (matches && (matches.length > 2)) {
+      var filename = matches[2];
+      if (filename.indexOf(" ") > 0) {
+        filename = filename.replace(/ .*$/, "");
+      }
+      return EnigmailData.convertToUnicode(unescape(filename), "utf-8");
+    } else {
+      return null;
+    }
+  }
+
+  /**
+   *
+   * @param {Path} filePath    The signed file
+   * @param {Path} sigPath       The signature to verify
+   *
+   * @return {Promise<String>} - A message from the verification.
+   *
+   * Use Promise.catch to handle failed verifications.
+   * The message will be an error message in this case.
+   */
+
+  async verifyAttachment(filePath, sigPath) {
+    EnigmailLog.DEBUG(`gnupg.js: verifyAttachment()\n`);
+    const args = EnigmailGpg.getStandardArgs(true).concat(["--verify", sigPath, filePath]);
+    let result = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args);
+    const decrypted = {};
+    GnuPGDecryption.decryptMessageEnd(result.stderrData, result.exitCode, 1, true, true, EnigmailConstants.UI_INTERACTIVE, decrypted);
+    if (result.exitCode === 0) {
+      const detailArr = decrypted.sigDetails.split(/ /);
+      const dateTime = EnigmailTime.getDateTime(detailArr[2], true, true);
+      const msg1 = decrypted.errorMsg.split(/\n/)[0];
+      const msg2 = EnigmailLocale.getString("keyAndSigDate", ["0x" + decrypted.keyId, dateTime]);
+      const message = msg1 + "\n" + msg2;
+      return (message);
+    } else {
+      throw (decrypted.errorMsg);
+    }
+  }
+
+
+  /**
+   *
+   * @param {Bytes}  encrypted     The encrypted data
+   *
+   * @return {Promise<Object>} - Return object with decryptedData and
+   * status information
+   *
+   * Use Promise.catch to handle failed decryption.
+   * retObj.errorMsg will be an error message in this case.
+   */
+
+  async decryptAttachment(encrypted) {
+    EnigmailLog.DEBUG(`gnupg.js: decryptAttachment()\n`);
+
+    let args = EnigmailGpg.getStandardArgs(true);
+    args.push("--yes");
+    args = args.concat(EnigmailPassword.command());
+    args.push("-d");
+
+    let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, encrypted);
+    return res;
+  }
+
+
+  /**
+   *
+   * @param {String} encrypted     The encrypted data
+   * @param {Object} options       Decryption options
+   *
+   * @return {Promise<Object>} - Return object with decryptedData and
+   * status information
+   *
+   * Use Promise.catch to handle failed decryption.
+   * retObj.errorMsg will be an error message in this case.
+   */
+
+  async decrypt(encrypted, options) {
+    EnigmailLog.DEBUG(`gnupg.js: decrypt()\n`);
+
+    options.logFile = EnigmailErrorHandling.getTempLogFile();
+    const args = GnuPGDecryption.getDecryptionArgs(options);
+    let res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, encrypted);
+    EnigmailErrorHandling.appendLogFileToDebug(options.logFile);
+
+    if (res.statusFlags & EnigmailConstants.MISSING_PASSPHRASE) {
+      EnigmailLog.ERROR("decryption.jsm: decryptMessageStart: Error - no passphrase supplied\n");
+      throw {
+        errorMsg: EnigmailLocale.getString("noPassphrase")
+      };
+    }
+
+    const result = {
+      exitCode: res.exitCode,
+      decryptedData: res.stdoutData
+    };
+    GnuPGDecryption.decryptMessageEnd(res.stderrData, res.exitCode, res.stdoutData.length, options.verifyOnly, options.noOutput, options.uiFlags, result);
+
+    return result;
+  }
+
+  /**
+   *
+   * @param {String} encrypted     The encrypted data
+   * @param {Object} options       Decryption options
+   *
+   * @return {Promise<Object>} - Return object with decryptedData and
+   * status information
+   *
+   * Use Promise.catch to handle failed decryption.
+   * retObj.errorMsg will be an error message in this case.
+   */
+
+  async decryptMime(encrypted, options) {
+    EnigmailLog.DEBUG(`gnupg.js: decryptMime()\n`);
+
+    // write something to gpg such that the process doesn't get stuck
+    if (encrypted.length === 0) {
+      encrypted = "NO DATA\n";
+    }
+
+    options.noOutput = false;
+    options.verifyOnly = false;
+    options.uiFlags = EnigmailConstants.UI_PGP_MIME;
+
+    return this.decrypt(encrypted, options);
+  }
+
+  /**
+   *
+   * @param {String} signed        The signed data
+   * @param {Object} options       Decryption options
+   *
+   * @return {Promise<Object>} - Return object with decryptedData and
+   * status information
+   *
+   * Use Promise.catch to handle failed decryption.
+   * retObj.errorMsg will be an error message in this case.
+   */
+
+  async verifyMime(signed, options) {
+    EnigmailLog.DEBUG(`gnupg.js: verifyMime()\n`);
+
+    options.noOutput = true;
+    options.verifyOnly = true;
+    options.uiFlags = EnigmailConstants.UI_PGP_MIME;
+
+    return this.decrypt(signed, options);
+  }
+
+  async getKeyListFromKeyBlock(keyBlockStr) {
+    let res;
+    res = await getGpgKeyData(keyBlockStr);
+    return res;
+  }
+
+}
+
+function getGnuPGAPI() {
+  return new GnuPGCryptoAPI();
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/interface.js
@@ -0,0 +1,263 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+/**
+ * CryptoAPI - abstract interface
+ */
+
+var inspector;
+
+class CryptoAPI {
+  constructor() {
+    this.api_name = "null";
+  }
+
+  get apiName() {
+    return this.api_name;
+  }
+
+  /**
+   * Synchronize a promise: wait synchonously until a promise has completed and return
+   * the value that the promise returned.
+   *
+   * @param {Promise} promise: the promise to wait for
+   *
+   * @return {Variant} whatever the promise returns
+   */
+  sync(promise) {
+    console.log("CryptoAPI.sync() starting");
+
+    if (!inspector) {
+      inspector = Cc["@mozilla.org/jsinspector;1"].createInstance(Ci.nsIJSInspector);
+    }
+
+    let res = null;
+    let p = promise.then(gotResult => {
+      console.log("CryptoAPI.sync() good result: " + gotResult);
+      res = gotResult;
+      inspector.exitNestedEventLoop();
+    }).catch(gotResult => {
+      console.log("CryptoAPI.sync() failed result: " + gotResult);
+      res = gotResult;
+      inspector.exitNestedEventLoop();
+    });
+
+    inspector.enterNestedEventLoop(0);
+
+    console.log("CryptoAPI.sync() leaving");
+    return res;
+  }
+
+  /**
+   * Obtain signatures for a given set of key IDs.
+   *
+   * @param {String}  keyId:            space-separated list of key IDs
+   * @param {Boolean} ignoreUnknownUid: if true, filter out unknown signer's UIDs
+   *
+   * @return {Promise<Array of Object>} - see extractSignatures()
+   */
+  async getKeySignatures(keyId, ignoreUnknownUid = false) {
+    return null;
+  }
+
+  /**
+   * Export the minimum key for the public key object:
+   * public key, user ID, newest encryption subkey
+   *
+   * @param {String} fpr  : a single FPR
+   * @param {String} email: [optional] the email address of the desired user ID.
+   *                        If the desired user ID cannot be found or is not valid, use the primary UID instead
+   *
+   * @return {Promise<Object>}:
+   *    - exitCode (0 = success)
+   *    - errorMsg (if exitCode != 0)
+   *    - keyData: BASE64-encded string of key data
+   */
+  async getMinimalPubKey(fpr, email) {
+    return {
+      exitCode: -1,
+      errorMsg: "",
+      keyData: ""
+    };
+  }
+
+  /**
+   * Get the list of all konwn keys (including their secret keys)
+   * @param {Array of String} onlyKeys: [optional] only load data for specified key IDs
+   *
+   * @return {Promise<Array of Object>}
+   */
+  async getKeys(onlyKeys = null) {
+    return [];
+  }
+
+  /**
+   * Get groups defined in gpg.conf in the same structure as KeyObject
+   * [synchronous]
+   *
+   * @return {Array of KeyObject} with type = "grp"
+   */
+  getGroups() {
+    return [];
+  }
+
+  /**
+   * Extract a photo ID from a key, store it as file and return the file object.
+   *
+   * @param {String} keyId:       Key ID / fingerprint
+   * @param {Number} photoNumber: number of the photo on the key, starting with 0
+   *
+   * @return {nsIFile} object or null in case no data / error.
+   */
+  async getPhotoFile(keyId, photoNumber) {
+    return null;
+  }
+
+  /**
+   * Import key(s) from a file
+   *
+   * @param {nsIFile} inputFile:  the file holding the keys
+   *
+   * @return {Object} or null in case no data / error:
+   *   - {Number}          exitCode:        result code (0: OK)
+   *   - {Array of String) importedKeys:    imported fingerprints
+   *   - {Number}          importSum:       total number of processed keys
+   *   - {Number}          importUnchanged: number of unchanged keys
+   */
+
+  async importKeyFromFile(inputFile) {
+    return null;
+  }
+
+  /**
+   * Export secret key(s) to a file
+   *
+   * @param {String}  keyId       Specification by fingerprint or keyID
+   * @param {Boolean} minimalKey  if true, reduce key to minimum required
+   *
+   * @return {Object}:
+   *   - {Number} exitCode:  result code (0: OK)
+   *   - {String} keyData:   ASCII armored key data material
+   *   - {String} errorMsg:  error message in case exitCode !== 0
+   */
+
+  async extractSecretKey(keyId, minimalKey) {
+    return null;
+  }
+
+  /**
+   * Determine the file name from OpenPGP data.
+   *
+   * @param {byte} byteData    The encrypted data
+   *
+   * @return {String} - the name of the attached file
+   */
+
+  async getFileName(byteData) {
+    return null;
+  }
+
+  /**
+   * Verify the detached signature of an attachment (or in other words,
+   * check the signature of a file, given the file and the signature).
+   *
+   * @param {Path} filePath    The signed file
+   * @param {Path} sigPath       The signature to verify
+   *
+   * @return {Promise<String>} - A message from the verification.
+   *
+   * Use Promise.catch to handle failed verifications.
+   * The message will be an error message in this case.
+   */
+
+  async verifyAttachment(filePath, sigPath) {
+    return null;
+  }
+
+  /**
+   * Decrypt an attachment.
+   *
+   * @param {Bytes}  encrypted     The encrypted data
+   *
+   * @return {Promise<Object>} - Return object with decryptedData and
+   * status information
+   *
+   * Use Promise.catch to handle failed decryption.
+   * retObj.errorMsg will be an error message in this case.
+   */
+
+  async decryptAttachment(encrypted) {
+    return null;
+  }
+
+  /**
+   * Generic function to decrypt and/or verify an OpenPGP message.
+   *
+   * @param {String} encrypted     The encrypted data
+   * @param {Object} options       Decryption options
+   *
+   * @return {Promise<Object>} - Return object with decryptedData and
+   * status information
+   *
+   * Use Promise.catch to handle failed decryption.
+   * retObj.errorMsg will be an error message in this case.
+   */
+
+  async decrypt(encrypted, options) {
+    return null;
+  }
+
+  /**
+   * Decrypt a PGP/MIME-encrypted message
+   *
+   * @param {String} encrypted     The encrypted data
+   * @param {Object} options       Decryption options
+   *
+   * @return {Promise<Object>} - Return object with decryptedData and
+   * status information
+   *
+   * Use Promise.catch to handle failed decryption.
+   * retObj.errorMsg will be an error message in this case.
+   */
+
+  async decryptMime(encrypted, options) {
+    return null;
+  }
+
+  /**
+   * Verify a PGP/MIME-signed message
+   *
+   * @param {String} signed        The signed data
+   * @param {Object} options       Decryption options
+   *
+   * @return {Promise<Object>} - Return object with decryptedData and
+   * status information
+   *
+   * Use Promise.catch to handle failed decryption.
+   * retObj.errorMsg will be an error message in this case.
+   */
+
+  async verifyMime(signed, options) {
+    return null;
+  }
+
+  /**
+   * Get details (key ID, UID) of the data contained in a OpenPGP key block
+   *
+   * @param {String} keyBlockStr  String: the contents of one or more public keys
+   *
+   * @return {Promise<Array>}: array of objects with the following structure:
+   *          - id (key ID)
+   *          - fpr
+   *          - name (the UID of the key)
+   */
+
+  async getKeyListFromKeyBlock(keyBlockStr) {
+    return null;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/data.jsm
@@ -0,0 +1,168 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailData"];
+
+const SCRIPTABLEUNICODECONVERTER_CONTRACTID = "@mozilla.org/intl/scriptableunicodeconverter";
+
+const HEX_TABLE = "0123456789abcdef";
+
+function converter(charset) {
+  let unicodeConv = Cc[SCRIPTABLEUNICODECONVERTER_CONTRACTID].getService(Ci.nsIScriptableUnicodeConverter);
+  unicodeConv.charset = charset || "utf-8";
+  return unicodeConv;
+}
+
+var EnigmailData = {
+  getUnicodeData: function(data) {
+    // convert output from subprocess to Unicode
+    var tmpStream = Cc["@mozilla.org/io/string-input-stream;1"].createInstance(Ci.nsIStringInputStream);
+    tmpStream.setData(data, data.length);
+    var inStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
+    inStream.init(tmpStream);
+    return inStream.read(tmpStream.available());
+  },
+
+  extractMessageId: function(uri) {
+    var messageId = "";
+
+    var matches = uri.match(/^enigmail:message\/(.+)/);
+
+    if (matches && (matches.length > 1)) {
+      messageId = matches[1];
+    }
+
+    return messageId;
+  },
+
+  extractMimeMessageId: function(uri) {
+    var messageId = "";
+
+    var matches = uri.match(/^enigmail:mime-message\/(.+)/);
+
+    if (matches && (matches.length > 1)) {
+      messageId = matches[1];
+    }
+
+    return messageId;
+  },
+
+  decodeQuotedPrintable: function(str) {
+    return unescape(str.replace(/%/g, "=25").replace(new RegExp('=', 'g'), '%'));
+  },
+
+  decodeBase64: function(str) {
+    return atob(str.replace(/[\s\r\n]*/g, ""));
+  },
+
+  /***
+   * Encode a string in base64, with a max. line length of 72 characters
+   */
+  encodeBase64: function(str) {
+    return btoa(str).replace(/(.{72})/g, "$1\r\n");
+  },
+
+  convertToUnicode: function(text, charset) {
+    if (!text || (charset && (charset.toLowerCase() == "iso-8859-1"))) {
+      return text;
+    }
+
+    // Encode plaintext
+    try {
+      return converter(charset).ConvertToUnicode(text);
+    }
+    catch (ex) {
+      return text;
+    }
+  },
+
+  convertFromUnicode: function(text, charset) {
+    if (!text) {
+      return "";
+    }
+
+    try {
+      return converter(charset).ConvertFromUnicode(text);
+    }
+    catch (ex) {
+      return text;
+    }
+  },
+
+  convertGpgToUnicode: function(text) {
+    if (typeof(text) === "string") {
+      text = text.replace(/\\x3a/ig, "\\e3A");
+      var a = text.search(/\\x[0-9a-fA-F]{2}/);
+      while (a >= 0) {
+        var ch = unescape('%' + text.substr(a + 2, 2));
+        var r = new RegExp("\\" + text.substr(a, 4));
+        text = text.replace(r, ch);
+
+        a = text.search(/\\x[0-9a-fA-F]{2}/);
+      }
+
+      text = EnigmailData.convertToUnicode(text, "utf-8").replace(/\\e3A/g, ":");
+    }
+
+    return text;
+  },
+
+  pack: function(value, bytes) {
+    let str = '';
+    let mask = 0xff;
+    for (let j = 0; j < bytes; j++) {
+      str = String.fromCharCode((value & mask) >> j * 8) + str;
+      mask <<= 8;
+    }
+
+    return str;
+  },
+
+  unpack: function(str) {
+    let len = str.length;
+    let value = 0;
+
+    for (let j = 0; j < len; j++) {
+      value <<= 8;
+      value |= str.charCodeAt(j);
+    }
+
+    return value;
+  },
+
+  bytesToHex: function(str) {
+    let len = str.length;
+
+    let hex = '';
+    for (let j = 0; j < len; j++) {
+      let charCode = str.charCodeAt(j);
+      hex += HEX_TABLE.charAt((charCode & 0xf0) >> 4) +
+        HEX_TABLE.charAt((charCode & 0x0f));
+    }
+
+    return hex;
+  },
+
+  /**
+   * Convert an ArrayBuffer (or Uint8Array) object into a string
+   */
+  arrayBufferToString: function(buffer) {
+    const MAXLEN = 102400;
+
+    let uArr = new Uint8Array(buffer);
+    let ret = "";
+    let len = buffer.byteLength;
+
+    for (let j = 0; j < Math.floor(len / MAXLEN) + 1; j++) {
+      ret += String.fromCharCode.apply(null, uArr.subarray(j * MAXLEN, ((j + 1) * MAXLEN)));
+    }
+
+    return ret;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/decryption.jsm
@@ -0,0 +1,431 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailDecryption"];
+
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailArmor = ChromeUtils.import("chrome://openpgp/content/modules/armor.jsm").EnigmailArmor;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
+const EnigmailHttpProxy = ChromeUtils.import("chrome://openpgp/content/modules/httpProxy.jsm").EnigmailHttpProxy;
+const EnigmailGpgAgent = ChromeUtils.import("chrome://openpgp/content/modules/gpgAgent.jsm").EnigmailGpgAgent;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailKey = ChromeUtils.import("chrome://openpgp/content/modules/key.jsm").EnigmailKey;
+const EnigmailPassword = ChromeUtils.import("chrome://openpgp/content/modules/passwords.jsm").EnigmailPassword;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
+
+const STATUS_ERROR = EnigmailConstants.BAD_SIGNATURE | EnigmailConstants.DECRYPTION_FAILED;
+const STATUS_DECRYPTION_OK = EnigmailConstants.DECRYPTION_OKAY;
+const STATUS_GOODSIG = EnigmailConstants.GOOD_SIGNATURE;
+
+const NS_WRONLY = 0x02;
+
+function statusObjectFrom(signatureObj, exitCodeObj, statusFlagsObj, keyIdObj, userIdObj, sigDetailsObj, errorMsgObj, blockSeparationObj, encToDetailsObj) {
+  return {
+    signature: signatureObj,
+    exitCode: exitCodeObj,
+    statusFlags: statusFlagsObj,
+    keyId: keyIdObj,
+    userId: userIdObj,
+    sigDetails: sigDetailsObj,
+    message: errorMsgObj,
+    blockSeparation: blockSeparationObj,
+    encToDetails: encToDetailsObj
+  };
+}
+
+function newStatusObject() {
+  return statusObjectFrom({
+    value: ""
+  }, {}, {}, {}, {}, {}, {}, {}, {});
+}
+
+var EnigmailDecryption = {
+  isReady: function(win) {
+    return (EnigmailCore.getService(win)) && (!EnigmailKeyRing.isGeneratingKey());
+  },
+
+  getFromAddr: function(win) {
+    var fromAddr;
+    if (win && win.gFolderDisplay && win.gFolderDisplay.selectedMessage) {
+      fromAddr = win.gFolderDisplay.selectedMessage.author;
+      try {
+        fromAddr = EnigmailFuncs.stripEmail(fromAddr);
+        if (fromAddr.search(/[a-zA-Z0-9]@.*[\(\)]/) >= 0) {
+          fromAddr = false;
+        }
+      }
+      catch (ex) {
+        fromAddr = false;
+      }
+    }
+    return fromAddr;
+  },
+
+  /**
+   *  Decrypts a PGP ciphertext and returns the the plaintext
+   *
+   *in  @parent a window object
+   *in  @uiFlags see flag options in EnigmailConstants, UI_INTERACTIVE, UI_ALLOW_KEY_IMPORT
+   *in  @cipherText a string containing a PGP Block
+   *out @signatureObj
+   *out @exitCodeObj contains the exit code
+   *out @statusFlagsObj see status flags in nslEnigmail.idl, GOOD_SIGNATURE, BAD_SIGNATURE
+   *out @keyIdObj holds the key id
+   *out @userIdObj holds the user id
+   *out @sigDetailsObj
+   *out @errorMsgObj  error string
+   *out @blockSeparationObj
+   *out @encToDetailsObj  returns in details, which keys the mesage was encrypted for (ENC_TO entries)
+   *
+   * @return string plaintext ("" if error)
+   *
+   */
+  decryptMessage: function(parent, uiFlags, cipherText,
+    signatureObj, exitCodeObj,
+    statusFlagsObj, keyIdObj, userIdObj, sigDetailsObj, errorMsgObj,
+    blockSeparationObj, encToDetailsObj) {
+    const esvc = EnigmailCore.getEnigmailService();
+
+    EnigmailLog.DEBUG("decryption.jsm: decryptMessage(" + cipherText.length + " bytes, " + uiFlags + ")\n");
+
+    if (!cipherText)
+      return "";
+
+    var interactive = uiFlags & EnigmailConstants.UI_INTERACTIVE;
+    var allowImport = uiFlags & EnigmailConstants.UI_ALLOW_KEY_IMPORT;
+    var unverifiedEncryptedOK = uiFlags & EnigmailConstants.UI_UNVERIFIED_ENC_OK;
+    var oldSignature = signatureObj.value;
+
+    EnigmailLog.DEBUG("decryption.jsm: decryptMessage: oldSignature=" + oldSignature + "\n");
+
+    signatureObj.value = "";
+    exitCodeObj.value = -1;
+    statusFlagsObj.value = 0;
+    keyIdObj.value = "";
+    userIdObj.value = "";
+    errorMsgObj.value = "";
+
+    var beginIndexObj = {};
+    var endIndexObj = {};
+    var indentStrObj = {};
+    var blockType = EnigmailArmor.locateArmoredBlock(cipherText, 0, "", beginIndexObj, endIndexObj, indentStrObj);
+    if (!blockType || blockType == "SIGNATURE") {
+      // return without displaying a message
+      return "";
+    }
+
+    var publicKey = (blockType == "PUBLIC KEY BLOCK");
+
+    var verifyOnly = (blockType == "SIGNED MESSAGE");
+
+    var pgpBlock = cipherText.substr(beginIndexObj.value,
+      endIndexObj.value - beginIndexObj.value + 1);
+
+    if (indentStrObj.value) {
+      var indentRegexp = new RegExp("^" + indentStrObj.value, "gm");
+      pgpBlock = pgpBlock.replace(indentRegexp, "");
+      if (indentStrObj.value.substr(-1) == " ") {
+        var indentRegexpStr = "^" + indentStrObj.value.replace(/ $/m, "$");
+        indentRegexp = new RegExp(indentRegexpStr, "gm");
+        pgpBlock = pgpBlock.replace(indentRegexp, "");
+      }
+    }
+
+    // HACK to better support messages from Outlook: if there are empty lines, drop them
+    if (pgpBlock.search(/MESSAGE-----\r?\n\r?\nVersion/) >= 0) {
+      EnigmailLog.DEBUG("decryption.jsm: decryptMessage: apply Outlook empty line workaround\n");
+      pgpBlock = pgpBlock.replace(/\r?\n\r?\n/g, "\n");
+    }
+
+    const head = "";
+    var tail = cipherText.substr(endIndexObj.value + 1,
+      cipherText.length - endIndexObj.value - 1);
+
+    if (publicKey) {
+      if (!allowImport) {
+        errorMsgObj.value = EnigmailLocale.getString("keyInMessageBody");
+        statusFlagsObj.value |= EnigmailConstants.DISPLAY_MESSAGE;
+        statusFlagsObj.value |= EnigmailConstants.INLINE_KEY;
+
+        return "";
+      }
+
+      // Import public key
+      exitCodeObj.value = EnigmailKeyRing.importKey(parent, true, pgpBlock, "",
+        errorMsgObj);
+      if (exitCodeObj.value === 0) {
+        statusFlagsObj.value |= EnigmailConstants.IMPORTED_KEY;
+      }
+      return "";
+    }
+
+    var newSignature = "";
+
+    if (verifyOnly) {
+      newSignature = EnigmailArmor.extractSignaturePart(pgpBlock, EnigmailConstants.SIGNATURE_ARMOR);
+      if (oldSignature && (newSignature != oldSignature)) {
+        EnigmailLog.ERROR("enigmail.js: Enigmail.decryptMessage: Error - signature mismatch " + newSignature + "\n");
+        errorMsgObj.value = EnigmailLocale.getString("sigMismatch");
+        statusFlagsObj.value |= EnigmailConstants.DISPLAY_MESSAGE;
+
+        return "";
+      }
+    }
+
+    if (!EnigmailCore.getService(parent)) {
+      EnigmailLog.ERROR("decryption.jsm: decryptMessage: not yet initialized\n");
+      errorMsgObj.value = EnigmailLocale.getString("notInit");
+      statusFlagsObj.value |= EnigmailConstants.DISPLAY_MESSAGE;
+      return "";
+    }
+
+    if (EnigmailKeyRing.isGeneratingKey()) {
+      errorMsgObj.value = EnigmailLocale.getString("notComplete");
+      statusFlagsObj.value |= EnigmailConstants.DISPLAY_MESSAGE;
+      return "";
+    }
+
+    // limit output to 100 times message size to avoid DoS attack
+    var maxOutput = pgpBlock.length * 100;
+    let keyserver = EnigmailPrefs.getPref("autoKeyRetrieve");
+    let options = {
+      keyserver: keyserver,
+      keyserverProxy: EnigmailHttpProxy.getHttpProxy(keyserver),
+      fromAddr: EnigmailDecryption.getFromAddr(parent),
+      verifyOnly: verifyOnly,
+      noOutput: false,
+      maxOutputLength: maxOutput,
+      uiFlags: uiFlags
+    };
+    const cApi = EnigmailCryptoAPI();
+    let result = cApi.sync(cApi.decrypt(pgpBlock, options));
+    EnigmailLog.DEBUG("decryption.jsm: decryptMessage: decryption finished\n");
+    if (! result) {
+      return "";
+    }
+
+    var plainText = EnigmailData.getUnicodeData(result.decryptedData);
+    exitCodeObj.value = result.exitCode;
+    statusFlagsObj.value = result.statusFlags;
+    errorMsgObj.value = result.errorMsg;
+
+    // do not return anything if gpg signales DECRYPTION_FAILED
+    // (which could be possible in case of MDC errors)
+    if ((uiFlags & EnigmailConstants.UI_IGNORE_MDC_ERROR) &&
+      (result.statusFlags & EnigmailConstants.MISSING_MDC)) {
+      EnigmailLog.DEBUG("decryption.jsm: decryptMessage: ignoring MDC error\n");
+    }
+    else if (result.statusFlags & EnigmailConstants.DECRYPTION_FAILED) {
+      plainText = "";
+    }
+
+    userIdObj.value = result.userId;
+    keyIdObj.value = result.keyId;
+    sigDetailsObj.value = result.sigDetails;
+    if (encToDetailsObj) {
+      encToDetailsObj.value = result.encToDetails;
+    }
+    blockSeparationObj.value = result.blockSeparation;
+
+    if (tail.search(/\S/) >= 0) {
+      statusFlagsObj.value |= EnigmailConstants.PARTIALLY_PGP;
+    }
+
+
+    if (exitCodeObj.value === 0) {
+      // Normal return
+
+      var doubleDashSeparator = false;
+      try {
+        doubleDashSeparator = EnigmailPrefs.getPrefBranch().getBoolPref("doubleDashSeparator");
+      }
+      catch (ex) {}
+
+      if (doubleDashSeparator && (plainText.search(/(\r|\n)-- +(\r|\n)/) < 0)) {
+        // Workaround for MsgCompose stripping trailing spaces from sig separator
+        plainText = plainText.replace(/(\r|\n)--(\r|\n)/, "$1-- $2");
+      }
+
+      statusFlagsObj.value |= EnigmailConstants.DISPLAY_MESSAGE;
+
+      if (verifyOnly && indentStrObj.value) {
+        plainText = plainText.replace(/^/gm, indentStrObj.value);
+      }
+
+      return EnigmailDecryption.inlineInnerVerification(parent, uiFlags, plainText,
+        statusObjectFrom(signatureObj, exitCodeObj, statusFlagsObj, keyIdObj, userIdObj,
+          sigDetailsObj, errorMsgObj, blockSeparationObj, encToDetailsObj));
+    }
+
+    var pubKeyId = keyIdObj.value;
+
+    if (statusFlagsObj.value & EnigmailConstants.BAD_SIGNATURE) {
+      if (verifyOnly && indentStrObj.value) {
+        // Probably replied message that could not be verified
+        errorMsgObj.value = EnigmailLocale.getString("unverifiedReply") + "\n\n" + errorMsgObj.value;
+        return "";
+      }
+
+      // Return bad signature (for checking later)
+      signatureObj.value = newSignature;
+
+    }
+    else if (pubKeyId &&
+      (statusFlagsObj.value & EnigmailConstants.UNVERIFIED_SIGNATURE)) {
+
+      var innerKeyBlock;
+      if (verifyOnly) {
+        // Search for indented public key block in signed message
+        var innerBlockType = EnigmailArmor.locateArmoredBlock(pgpBlock, 0, "- ", beginIndexObj, endIndexObj, indentStrObj);
+        if (innerBlockType == "PUBLIC KEY BLOCK") {
+
+          innerKeyBlock = pgpBlock.substr(beginIndexObj.value,
+            endIndexObj.value - beginIndexObj.value + 1);
+
+          innerKeyBlock = innerKeyBlock.replace(/- -----/g, "-----");
+
+          statusFlagsObj.value |= EnigmailConstants.INLINE_KEY;
+          EnigmailLog.DEBUG("decryption.jsm: decryptMessage: innerKeyBlock found\n");
+        }
+      }
+
+      if (allowImport) {
+
+        var importedKey = false;
+
+        if (innerKeyBlock) {
+          var importErrorMsgObj = {};
+          var exitStatus = EnigmailKeyRing.importKey(parent, true, innerKeyBlock,
+            pubKeyId, importErrorMsgObj);
+
+          importedKey = (exitStatus === 0);
+
+          if (exitStatus > 0) {
+            EnigmailDialog.alert(parent, EnigmailLocale.getString("cantImport") + importErrorMsgObj.value);
+          }
+        }
+
+        if (importedKey) {
+          // Recursive call; note that EnigmailConstants.UI_ALLOW_KEY_IMPORT is unset
+          // to break the recursion
+          var uiFlagsDeep = interactive ? EnigmailConstants.UI_INTERACTIVE : 0;
+          signatureObj.value = "";
+          return EnigmailDecryption.decryptMessage(parent, uiFlagsDeep, pgpBlock,
+            signatureObj, exitCodeObj, statusFlagsObj,
+            keyIdObj, userIdObj, sigDetailsObj, errorMsgObj);
+        }
+
+      }
+
+      if (plainText && !unverifiedEncryptedOK) {
+        // Append original PGP block to unverified message
+        plainText = "-----BEGIN PGP UNVERIFIED MESSAGE-----\r\n" + plainText +
+          "-----END PGP UNVERIFIED MESSAGE-----\r\n\r\n" + pgpBlock;
+      }
+
+    }
+
+    return verifyOnly ? "" : plainText;
+  },
+
+  inlineInnerVerification: function(parent, uiFlags, text, statusObject) {
+    EnigmailLog.DEBUG("decryption.jsm: inlineInnerVerification()\n");
+
+    if (text && text.indexOf("-----BEGIN PGP SIGNED MESSAGE-----") === 0) {
+      var status = newStatusObject();
+      var newText = EnigmailDecryption.decryptMessage(parent, uiFlags, text,
+        status.signature, status.exitCode, status.statusFlags, status.keyId, status.userId,
+        status.sigDetails, status.message, status.blockSeparation, status.encToDetails);
+      if (status.exitCode.value === 0) {
+        text = newText;
+        // merge status into status object:
+        statusObject.statusFlags.value = statusObject.statusFlags.value | status.statusFlags.value;
+        statusObject.keyId.value = status.keyId.value;
+        statusObject.userId.value = status.userId.value;
+        statusObject.sigDetails.value = status.sigDetails.value;
+        statusObject.message.value = status.message.value;
+        // we don't merge encToDetails
+      }
+    }
+
+    return text;
+  },
+
+  decryptAttachment: function(parent, outFile, displayName, byteData,
+    exitCodeObj, statusFlagsObj, errorMsgObj) {
+    const esvc = EnigmailCore.getEnigmailService();
+
+    EnigmailLog.DEBUG("decryption.jsm: decryptAttachment(parent=" + parent + ", outFileName=" + outFile.path + ")\n");
+
+    let attachmentHead = byteData.substr(0, 200);
+    if (attachmentHead.match(/-----BEGIN PGP \w{5,10} KEY BLOCK-----/)) {
+      // attachment appears to be a PGP key file
+
+      if (EnigmailDialog.confirmDlg(parent, EnigmailLocale.getString("attachmentPgpKey", [displayName]),
+          EnigmailLocale.getString("keyMan.button.import"), EnigmailLocale.getString("dlg.button.view"))) {
+
+        let preview = EnigmailKey.getKeyListFromKeyBlock(byteData, errorMsgObj);
+        exitCodeObj.keyList = preview;
+        let exitStatus = 0;
+
+        if (errorMsgObj.value === "") {
+          if (preview.length > 0) {
+            if (preview.length == 1) {
+              exitStatus = EnigmailDialog.confirmDlg(parent, EnigmailLocale.getString("doImportOne", [preview[0].name, preview[0].id]));
+            }
+            else {
+              exitStatus = EnigmailDialog.confirmDlg(parent,
+                EnigmailLocale.getString("doImportMultiple", [
+                  preview.map(function(a) {
+                    return "\t" + a.name + " (" + a.id + ")";
+                  }).
+                  join("\n")
+                ]));
+            }
+
+            if (exitStatus) {
+              exitCodeObj.value = EnigmailKeyRing.importKey(parent, false, byteData, "", errorMsgObj);
+              statusFlagsObj.value = EnigmailConstants.IMPORTED_KEY;
+            }
+            else {
+              exitCodeObj.value = 0;
+              statusFlagsObj.value = EnigmailConstants.DISPLAY_MESSAGE;
+            }
+          }
+        }
+      }
+      else {
+        exitCodeObj.value = 0;
+        statusFlagsObj.value = EnigmailConstants.DISPLAY_MESSAGE;
+      }
+      return true;
+    }
+
+    //var outFileName = EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePathReadonly(outFile.QueryInterface(Ci.nsIFile), NS_WRONLY));
+
+    const cApi = EnigmailCryptoAPI();
+    let result = cApi.sync(cApi.decryptAttachment(byteData));
+
+    exitCodeObj.value = result.exitCode;
+    statusFlagsObj.value = result.statusFlags;
+    if (result.stdoutData.length > 0) {
+      return EnigmailFiles.writeFileContents(outFile, result.stdoutData);
+    }
+
+    return false;
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/dialog.jsm
@@ -0,0 +1,448 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailDialog"];
+
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailWindows = ChromeUtils.import("chrome://openpgp/content/modules/windows.jsm").EnigmailWindows;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+
+const BUTTON_POS_0 = 1;
+const BUTTON_POS_1 = 1 << 8;
+const BUTTON_POS_2 = 1 << 16;
+
+const gPromptSvc = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
+
+const LOCAL_FILE_CONTRACTID = "@mozilla.org/file/local;1";
+
+var EnigmailDialog = {
+
+  /***
+   * Confirmation dialog with OK / Cancel buttons (both customizable)
+   *
+   * @win:         nsIWindow - parent window to display modal dialog; can be null
+   * @mesg:        String    - message text
+   * @okLabel:     String    - OPTIONAL label for OK button
+   * @cancelLabel: String    - OPTIONAL label for cancel button
+   *
+   * @return:      Boolean   - true: OK pressed / false: Cancel or ESC pressed
+   */
+  confirmDlg: function(win, mesg, okLabel, cancelLabel) {
+
+    let buttonPressed = EnigmailDialog.msgBox(win, {
+        msgtext: mesg,
+        button1: okLabel ? okLabel : EnigmailLocale.getString("dlg.button.ok"),
+        cancelButton: cancelLabel ? cancelLabel : EnigmailLocale.getString("dlg.button.cancel"),
+        iconType: EnigmailConstants.ICONTYPE_QUESTION,
+        dialogTitle: EnigmailLocale.getString("enigConfirm")
+      },
+      null);
+
+    return (buttonPressed === 0);
+  },
+
+  /**
+   * Displays an alert dialog.
+   *
+   * @win:         nsIWindow - parent window to display modal dialog; can be null
+   * @mesg:        String    - message text
+   *
+   * no return value
+   */
+  alert: function(win, mesg) {
+    EnigmailDialog.msgBox(win, {
+        msgtext: mesg,
+        button1: EnigmailLocale.getString("dlg.button.close"),
+        iconType: EnigmailConstants.ICONTYPE_ALERT,
+        dialogTitle: EnigmailLocale.getString("enigAlert")
+      },
+      null);
+  },
+
+  /**
+   * Displays an information dialog.
+   *
+   * @win:         nsIWindow - parent window to display modal dialog; can be null
+   * @mesg:        String    - message text
+   *
+   * no return value
+   */
+  info: function(win, mesg) {
+    EnigmailDialog.msgBox(win, {
+        msgtext: mesg,
+        button1: EnigmailLocale.getString("dlg.button.close"),
+        iconType: EnigmailConstants.ICONTYPE_INFO,
+        dialogTitle: EnigmailLocale.getString("enigInfo")
+      },
+      null);
+  },
+
+  /**
+   * Displays an alert dialog with 1-3 optional buttons.
+   *
+   * @win:           nsIWindow - parent window to display modal dialog; can be null
+   * @mesg:          String    - message text
+   * @checkboxLabel: String    - if not null, display checkbox with text; the
+   *                             checkbox state is returned in checkedObj.value
+   * @button-Labels: String    - use "&" to indicate access key
+   *     use "buttonType:label" or ":buttonType" to indicate special button types
+   *        (buttonType is one of cancel, help, extra1, extra2)
+   * @checkedObj:    Object    - holding the checkbox value
+   *
+   * @return: 0-2: button Number pressed
+   *          -1: ESC or close window button pressed
+   *
+   */
+  longAlert: function(win, mesg, checkboxLabel, okLabel, labelButton2, labelButton3, checkedObj) {
+    var result = {
+      value: -1,
+      checked: false
+    };
+
+    if (!win) {
+      win = EnigmailWindows.getBestParentWin();
+    }
+
+    win.openDialog("chrome://openpgp/content/ui/enigmailMsgBox.xul", "_blank",
+      "chrome,dialog,modal,centerscreen,resizable,titlebar", {
+        msgtext: mesg,
+        checkboxLabel: checkboxLabel,
+        iconType: EnigmailConstants.ICONTYPE_ALERT,
+        button1: okLabel,
+        button2: labelButton2,
+        button3: labelButton3
+      },
+      result);
+
+    if (checkboxLabel) {
+      checkedObj.value = result.checked;
+    }
+    return result.value;
+  },
+
+  /**
+   * Displays a message box with 1-3 optional buttons.
+   *
+   * @win:           nsIWindow - parent window to display modal dialog; can be null
+   * @argsObj:       Object:
+   *   - msgtext:       String    - message text
+   *   - dialogTitle:   String    - title of the dialog
+   *   - checkboxLabel: String    - if not null, display checkbox with text; the
+   *                                checkbox state is returned in checkedObj.value
+   *   - iconType:      Number    - Icon type: 1=Message / 2=Question / 3=Alert / 4=Error
+   *
+   *   - buttonX:       String    - Button label (button 1-3) [button1 = "accept" button]
+   *                                use "&" to indicate access key
+   *   - cancelButton   String    - Label for cancel button
+   *     use "buttonType:label" or ":buttonType" to indicate special button types
+   *        (buttonType is one of cancel, help, extra1, extra2)
+   *     if no button is provided, OK will be displayed
+   *
+   * @checkedObj:    Object    - holding the checkbox value
+   *
+   * @return: 0-2: button Number pressed
+   *          -1: cancel button, ESC or close window button pressed
+   *
+   */
+  msgBox: function(win, argsObj, checkedObj) {
+    var result = {
+      value: -1,
+      checked: false
+    };
+
+    if (!win) {
+      win = EnigmailWindows.getBestParentWin();
+    }
+
+    win.openDialog("chrome://openpgp/content/ui/enigmailMsgBox.xul", "",
+      "chrome,dialog,modal,centerscreen,resizable", argsObj, result);
+
+    if (argsObj.checkboxLabel) {
+      checkedObj.value = result.checked;
+    }
+    return result.value;
+  },
+
+  /**
+   * Display a dialog with a message and a text entry field
+   *
+   * @win:      nsIWindow - parent window to display modal dialog; can be null
+   * @mesg:     String    - message text
+   * @valueObj: Object    - object to hold the entered text in .value
+   *
+   * @return:   Boolean - true if OK was pressed / false otherwise
+   */
+  promptValue: function(win, mesg, valueObj) {
+    return gPromptSvc.prompt(win, EnigmailLocale.getString("enigPrompt"),
+      mesg, valueObj, "", {});
+  },
+
+  /**
+   * Display an alert message with an OK button and a checkbox to hide
+   * the message in the future.
+   * In case the checkbox was pressed in the past, the dialog is skipped
+   *
+   * @win:      nsIWindow - the parent window to hold the modal dialog
+   * @mesg:     String    - the localized message to display
+   * @prefText: String    - the name of the Enigmail preference to read/store the
+   *                        the future display status
+   */
+  alertPref: function(win, mesg, prefText) {
+    const display = true;
+    const dontDisplay = false;
+
+    let prefValue = EnigmailPrefs.getPref(prefText);
+    if (prefValue === display) {
+      let checkBoxObj = {
+        value: false
+      };
+
+      let buttonPressed = EnigmailDialog.msgBox(win, {
+          msgtext: mesg,
+          dialogTitle: EnigmailLocale.getString("enigInfo"),
+          iconType: EnigmailConstants.ICONTYPE_INFO,
+          checkboxLabel: EnigmailLocale.getString("dlgNoPrompt")
+        },
+        checkBoxObj);
+
+      if (checkBoxObj.value && buttonPressed === 0) {
+        EnigmailPrefs.setPref(prefText, dontDisplay);
+      }
+    }
+  },
+
+  /**
+   * Display an alert dialog together with the message "this dialog will be
+   * displayed |counter| more times".
+   * If |counter| is 0, the dialog is not displayed.
+   *
+   * @win:           nsIWindow - the parent window to hold the modal dialog
+   * @countPrefName: String    - the name of the Enigmail preference to read/store the
+   *                             the |counter| value
+   * @mesg:          String    - the localized message to display
+   *
+   */
+  alertCount: function(win, countPrefName, mesg) {
+    let alertCount = EnigmailPrefs.getPref(countPrefName);
+
+    if (alertCount <= 0)
+      return;
+
+    alertCount--;
+    EnigmailPrefs.setPref(countPrefName, alertCount);
+
+    if (alertCount > 0) {
+      mesg += EnigmailLocale.getString("repeatPrefix", [alertCount]) + " ";
+      mesg += (alertCount == 1) ? EnigmailLocale.getString("repeatSuffixSingular") : EnigmailLocale.getString("repeatSuffixPlural");
+    }
+    else {
+      mesg += EnigmailLocale.getString("noRepeat");
+    }
+
+    EnigmailDialog.alert(win, mesg);
+  },
+
+  /**
+   * Display a confirmation dialog with OK / Cancel buttons (both customizable) and
+   * a checkbox to remember the selected choice.
+   *
+   *
+   * @win:         nsIWindow - parent window to display modal dialog; can be null
+   * @mesg:        String    - message text
+   * @prefText     String    - the name of the Enigmail preference to read/store the
+   *                           the future display status.
+   *                           the default action is chosen
+   * @okLabel:     String    - OPTIONAL label for OK button
+   * @cancelLabel: String    - OPTIONAL label for cancel button
+   *
+   * @return:      Boolean   - true: 1 pressed / 0: Cancel pressed / -1: ESC pressed
+   *
+   * If the dialog is not displayed:
+   *  - if @prefText is type Boolean: return 1
+   *  - if @prefText is type Number:  return the last choice of the user
+   */
+  confirmPref: function(win, mesg, prefText, okLabel, cancelLabel) {
+    const notSet = 0;
+    const yes = 1;
+    const no = 2;
+    const display = true;
+    const dontDisplay = false;
+
+    var prefValue = EnigmailPrefs.getPref(prefText);
+
+    if (typeof(prefValue) != "boolean") {
+      // number: remember user's choice
+      switch (prefValue) {
+        case notSet:
+          {
+            let checkBoxObj = {
+              value: false
+            };
+            let buttonPressed = EnigmailDialog.msgBox(win, {
+              msgtext: mesg,
+              button1: okLabel ? okLabel : EnigmailLocale.getString("dlg.button.ok"),
+              cancelButton: cancelLabel ? cancelLabel : EnigmailLocale.getString("dlg.button.cancel"),
+              checkboxLabel: EnigmailLocale.getString("dlgKeepSetting"),
+              iconType: EnigmailConstants.ICONTYPE_QUESTION,
+              dialogTitle: EnigmailLocale.getString("enigConfirm")
+            }, checkBoxObj);
+
+            if (checkBoxObj.value) {
+              EnigmailPrefs.setPref(prefText, (buttonPressed === 0 ? yes : no));
+            }
+            return (buttonPressed === 0 ? 1 : 0);
+          }
+        case yes:
+          return 1;
+        case no:
+          return 0;
+        default:
+          return -1;
+      }
+    }
+    else {
+      // boolean: "do not show this dialog anymore" (and return default)
+      switch (prefValue) {
+        case display:
+          {
+            let checkBoxObj = {
+              value: false
+            };
+            let buttonPressed = EnigmailDialog.msgBox(win, {
+              msgtext: mesg,
+              button1: okLabel ? okLabel : EnigmailLocale.getString("dlg.button.ok"),
+              cancelButton: cancelLabel ? cancelLabel : EnigmailLocale.getString("dlg.button.cancel"),
+              checkboxLabel: EnigmailLocale.getString("dlgNoPrompt"),
+              iconType: EnigmailConstants.ICONTYPE_QUESTION,
+              dialogTitle: EnigmailLocale.getString("enigConfirm")
+            }, checkBoxObj);
+
+            if (checkBoxObj.value) {
+              EnigmailPrefs.setPref(prefText, false);
+            }
+            return (buttonPressed === 0 ? 1 : 0);
+          }
+        case dontDisplay:
+          return 1;
+        default:
+          return -1;
+      }
+    }
+  },
+
+  /**
+   *  Display a "open file" or "save file" dialog
+   *
+   *  win:              nsIWindow - parent window
+   *  title:            String    - window title
+   *  displayDir:       String    - optional: directory to be displayed
+   *  save:             Boolean   - true = Save file / false = Open file
+   *  defaultExtension: String    - optional: extension for the type of files to work with, e.g. "asc"
+   *  defaultName:      String    - optional: filename, incl. extension, that should be suggested to
+   *                                the user as default, e.g. "keys.asc"
+   *  filterPairs:      Array     - optional: [title, extension], e.g. ["Pictures", "*.jpg; *.png"]
+   *
+   *  return value:     nsIFile object representing the file to load or save
+   */
+  filePicker: function(win, title, displayDir, save, defaultExtension, defaultName, filterPairs) {
+    EnigmailLog.DEBUG("enigmailCommon.jsm: filePicker: " + save + "\n");
+
+    let filePicker = Cc["@mozilla.org/filepicker;1"].createInstance();
+    filePicker = filePicker.QueryInterface(Ci.nsIFilePicker);
+
+    let mode = save ? Ci.nsIFilePicker.modeSave : Ci.nsIFilePicker.modeOpen;
+
+    filePicker.init(win, title, mode);
+
+    if (displayDir) {
+      var localFile = Cc[LOCAL_FILE_CONTRACTID].createInstance(Ci.nsIFile);
+
+      try {
+        localFile.initWithPath(displayDir);
+        filePicker.displayDirectory = localFile;
+      }
+      catch (ex) {}
+    }
+
+    if (defaultExtension) {
+      filePicker.defaultExtension = defaultExtension;
+    }
+
+    if (defaultName) {
+      filePicker.defaultString = defaultName;
+    }
+
+    let nfilters = 0;
+    if (filterPairs && filterPairs.length) {
+      nfilters = filterPairs.length / 2;
+    }
+
+    for (let index = 0; index < nfilters; index++) {
+      filePicker.appendFilter(filterPairs[2 * index], filterPairs[2 * index + 1]);
+    }
+
+    filePicker.appendFilters(Ci.nsIFilePicker.filterAll);
+
+    let inspector = Cc["@mozilla.org/jsinspector;1"].createInstance(Ci.nsIJSInspector);
+    let gotFile = null;
+    filePicker.open(res => {
+      if (res != Ci.nsIFilePicker.returnOK && res != Ci.nsIFilePicker.returnReplace) {
+        inspector.exitNestedEventLoop();
+        return;
+      }
+
+      gotFile = filePicker.file.QueryInterface(Ci.nsIFile);
+      inspector.exitNestedEventLoop();
+    });
+
+    inspector.enterNestedEventLoop(0); // wait for async process to terminate
+
+    return gotFile;
+  },
+
+  /**
+   * Displays a dialog with success/failure information after importing
+   * keys.
+   *
+   * @param win:           nsIWindow - parent window to display modal dialog; can be null
+   * @param keyList:       Array of String - imported keyIDs
+   *
+   * @return: 0-2: button Number pressed
+   *          -1: ESC or close window button pressed
+   *
+   */
+  keyImportDlg: function(win, keyList) {
+    var result = {
+      value: -1,
+      checked: false
+    };
+
+    if (!win) {
+      win = EnigmailWindows.getBestParentWin();
+    }
+
+    win.openDialog("chrome://openpgp/content/ui/enigmailKeyImportInfo.xul", "",
+      "chrome,dialog,modal,centerscreen,resizable", {
+        keyList: keyList
+      },
+      result);
+
+    return result.value;
+  },
+  /**
+   * return a pre-initialized prompt service
+   */
+  getPromptSvc: function() {
+    return gPromptSvc;
+  }
+};
+
+EnigmailWindows.alert = EnigmailDialog.alert;
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/dns.jsm
@@ -0,0 +1,345 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+/**
+ * This module provides DNS query functionality via subprocesses.
+ * Supported record types: MX, SRV
+ *
+ * The following tools are currently supported:
+ *   Windows:    nslookup
+ *   Unix/Linux: dig, kdig, host, nslookup
+ */
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailDns"];
+
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailOS = ChromeUtils.import("chrome://openpgp/content/modules/os.jsm").EnigmailOS;
+const subprocess = ChromeUtils.import("chrome://openpgp/content/modules/subprocess.jsm").subprocess;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+
+const RESTYPE_WIN_NLSOOKUP = 1;
+const RESTYPE_UNIX_NLSOOKUP = 2;
+const RESTYPE_DIG = 3;
+const RESTYPE_HOST = 4;
+const RESTYPE_NOT_AVAILABLE = 99;
+
+var gHandler = null,
+  gResolverExecutable = null;
+
+var EnigmailDns = {
+  /**
+   * Perform a DNS lookup
+   *
+   * @param {String} recordType: The resource record type to query. MX and SRV are currently supported.
+   * @param {String} queryName:  The name to search for, e.g. "enigmail.net"
+   *
+   * @return {Promise<Array{String}>}: array of server(s) handling
+   *
+   */
+  lookup: async function(recordType, queryName) {
+    EnigmailLog.DEBUG(`dns.jsm: lookup(${recordType}, ${queryName})\n`);
+    if (!determineResolver()) return null;
+
+    switch (recordType.toUpperCase()) {
+      case "MX":
+      case "SRV":
+        break;
+      default:
+        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;
+    }
+    let dnsHandler = new gHandler(gResolverExecutable);
+    return dnsHandler.execute(recordType, queryName);
+  }
+};
+
+/**
+ * Determine the DNS resolver tool to use (e.g. dig, nslookup)
+ *
+ * @return {Boolean}: true: tool found / false: no tool found
+ */
+
+function determineResolver() {
+  if (!gHandler) {
+    gHandler = GenericHandler;
+    if (EnigmailOS.isWin32) {
+      gResolverExecutable = EnigmailFiles.resolvePathWithEnv("nslookup");
+      if (gResolverExecutable) {
+        EnigmailLog.DEBUG(`dns.jsm: determineResolver: executable = ${gResolverExecutable.path}\n`);
+        gHandler = NsLookupHandler_Windows;
+      }
+    }
+    else {
+      determineLinuxResolver();
+    }
+
+    if (!gResolverExecutable) EnigmailLog.DEBUG(`dns.jsm: determineResolver: no executable found\n`);
+
+  }
+
+  return gHandler !== GenericHandler;
+}
+
+
+function determineLinuxResolver() {
+  EnigmailLog.DEBUG(`dns.jsm: determineLinuxResolver()\n`);
+  const services = [{
+    exe: "dig",
+    class: DigHandler
+  }, {
+    exe: "kdig",
+    class: DigHandler
+  }, {
+    exe: "host",
+    class: HostHandler
+  }, {
+    exe: "nslookup",
+    class: NsLookupHandler
+  }];
+
+  for (let i of services) {
+    gResolverExecutable = EnigmailFiles.resolvePathWithEnv(i.exe);
+    if (gResolverExecutable) {
+      EnigmailLog.DEBUG(`dns.jsm: determineLinuxResolver: found ${i.class.handlerType}\n`);
+
+      gHandler = i.class;
+      return;
+    }
+  }
+}
+
+/**
+ * Base class for executing DNS requests
+ */
+class GenericHandler {
+  constructor(handlerFile) {
+    this._handlerFile = handlerFile;
+    this.recordType = "";
+    this.hostName = "";
+  }
+
+  getCmdArgs() {
+    return [];
+  }
+
+  execute(recordType, hostName) {
+    return new Promise((resolve, reject) => {
+
+      this.recordType = recordType.toUpperCase();
+      this.hostName = hostName;
+      let args = this.getCmdArgs();
+
+      if (args.length === 0) {
+        resolve([]);
+        return;
+      }
+
+      let stdoutData = "",
+        stderrData = "";
+      let self = this;
+
+      EnigmailLog.DEBUG(`dns.jsm: execute(): launching ${EnigmailFiles.formatCmdLine(this._handlerFile, args)}\n`);
+
+      subprocess.call({
+        command: this._handlerFile,
+        arguments: args,
+        environment: EnigmailCore.getEnvList(),
+        charset: null,
+        stdout: function(data) {
+          //EnigmailLog.DEBUG(`dns.jsm: execute.stdout: got data ${data}\n`);
+          stdoutData += data;
+        },
+        stderr: function(data) {
+          //EnigmailLog.DEBUG(`dns.jsm: execute.stderr: got data ${data}\n`);
+          stderrData += data;
+        },
+        done: function(result) {
+          EnigmailLog.DEBUG(`dns.jsm: execute.done(${result.exitCode})\n`);
+          try {
+            if (result.exitCode === 0) {
+              resolve(self.parseResult(stdoutData));
+            }
+            else {
+              resolve([]);
+            }
+          }
+          catch (ex) {
+            reject(ex);
+          }
+        },
+        mergeStderr: false
+      });
+    });
+  }
+
+  parseResult() {
+    return [];
+  }
+}
+
+/**
+ * Handler class for "dig" and "kdig"
+ */
+class DigHandler extends GenericHandler {
+  constructor(handlerFile) {
+    super(handlerFile);
+    this.handlerType = "dig";
+  }
+
+  getCmdArgs() {
+    return ["-t", this.recordType, "+short", this.hostName];
+  }
+
+  parseResult(stdoutData) {
+    let hosts = [];
+    let lines = stdoutData.split(/[\r\n]+/);
+
+    if (this.recordType === "MX") {
+      for (let i = 0; i < lines.length; i++) {
+        let m = lines[i].match(/^(\d+ )(.*)\./);
+
+        if (m && m.length >= 3) hosts.push(m[2]);
+      }
+    }
+    else if (this.recordType === "SRV") {
+      for (let i = 0; i < lines.length; i++) {
+        let m = lines[i].match(/^(\d+) (\d+) (\d+) ([^ ]+)\.$/);
+
+        if (m && m.length >= 5) hosts.push(m[4] + ":" + m[3]);
+      }
+    }
+
+    return hosts;
+  }
+}
+
+/**
+ * Handler class for "host"
+ */
+
+class HostHandler extends GenericHandler {
+  constructor(handlerFile) {
+    super(handlerFile);
+    this.handlerType = "host";
+  }
+
+  getCmdArgs() {
+    return ["-t", this.recordType, this.hostName];
+  }
+
+  parseResult(stdoutData) {
+    if (stdoutData.search(/3\(NXDOMAIN\)/) >= 0) return [];
+
+    let hosts = [];
+    let lines = stdoutData.split(/[\r\n]+/);
+
+    if (this.recordType === "MX") {
+      for (let i = 0; i < lines.length; i++) {
+        let m = lines[i].match(/^(.* )([^ ]+)\.$/);
+
+        if (m && m.length >= 3) hosts.push(m[2]);
+      }
+    }
+    else if (this.recordType === "SRV") {
+      for (let i = 0; i < lines.length; i++) {
+        let m = lines[i].match(/^(.*) (\d+) ([^ ]+)\.$/);
+
+        if (m && m.length >= 4) hosts.push(m[3] + ":" + m[2]);
+      }
+    }
+
+    return hosts;
+  }
+}
+
+/**
+ * Handler class for "nslookup" (on Linux/Unix)
+ */
+
+class NsLookupHandler extends GenericHandler {
+  constructor(handlerFile) {
+    super(handlerFile);
+    this.handlerType = "nslookup";
+  }
+
+  getCmdArgs() {
+    return ["-type=" + this.recordType, this.hostName];
+  }
+
+  parseResult(stdoutData) {
+    let hosts = [];
+    let lines = stdoutData.split(/[\r\n]+/);
+
+    if (lines.length > 3 && lines[3].search(/: NXDOMAIN/) > 0) return [];
+
+    if (this.recordType === "MX") {
+      let reg = new RegExp("^" + this.hostName.toLowerCase() + "(.* )([^ \t]+.*[^\.])\\.?$");
+      for (let i = 2; i < lines.length; i++) {
+        let m = lines[i].match(reg);
+
+        if (m && m.length >= 3) hosts.push(m[2]);
+        if (lines[i].length < 5) break;
+      }
+    }
+    else if (this.recordType === "SRV") {
+      for (let i = 2; i < lines.length; i++) {
+        let m = lines[i].match(/^(.*) (\d+) ([^ ]+)\.$/);
+
+        if (m && m.length >= 3) hosts.push(m[3] + ":" + m[2]);
+        if (lines[i].length < 5) break;
+      }
+    }
+
+    return hosts;
+  }
+}
+
+/**
+ * Handler class for "nslookup" on Windows
+ */
+
+class NsLookupHandler_Windows extends NsLookupHandler {
+
+  parseResult(stdoutData) {
+    let hosts = [];
+    let lines = stdoutData.split(/[\r\n]+/);
+
+    if (this.recordType === "MX") {
+      let reg = new RegExp("^" + this.hostName.toLowerCase() + "(.* )([^ \t]+.*[^\.])\\.?$");
+      for (let i = 2; i < lines.length; i++) {
+        let m = lines[i].match(reg);
+
+        if (m && m.length >= 3) hosts.push(m[2]);
+        if (lines[i].length < 5) break;
+      }
+    }
+    else if (this.recordType === "SRV") {
+      let svc = null;
+      for (let i = 2; i < lines.length; i++) {
+        if (lines[i].search(/SRV service location:$/) > 0) {
+          svc = null;
+          continue;
+        }
+
+        let m = lines[i].match(/^[\t ]+(port|svr hostname)([\t =]+)([^ \t]+)$/);
+
+        if (m && m.length >= 4) {
+          if (m[1] === "port" && svc === null) {
+            svc = m[3];
+          }
+          else if (m[1] === "svr hostname" && svc) {
+            hosts.push(m[3] + ":" + svc);
+          }
+        }
+        if (lines[i].length < 5) break;
+      }
+    }
+
+    return hosts;
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/modules/encryption.jsm
@@ -0,0 +1,499 @@
+/*
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/.
+ */
+
+
+"use strict";
+
+var EXPORTED_SYMBOLS = ["EnigmailEncryption"];
+
+const EnigmailCore = ChromeUtils.import("chrome://openpgp/content/modules/core.jsm").EnigmailCore;
+const EnigmailData = ChromeUtils.import("chrome://openpgp/content/modules/data.jsm").EnigmailData;
+const EnigmailLog = ChromeUtils.import("chrome://openpgp/content/modules/log.jsm").EnigmailLog;
+const EnigmailPrefs = ChromeUtils.import("chrome://openpgp/content/modules/prefs.jsm").EnigmailPrefs;
+const EnigmailApp = ChromeUtils.import("chrome://openpgp/content/modules/app.jsm").EnigmailApp;
+const EnigmailLocale = ChromeUtils.import("chrome://openpgp/content/modules/locale.jsm").EnigmailLocale;
+const EnigmailDialog = ChromeUtils.import("chrome://openpgp/content/modules/dialog.jsm").EnigmailDialog;
+const EnigmailGpgAgent = ChromeUtils.import("chrome://openpgp/content/modules/gpgAgent.jsm").EnigmailGpgAgent;
+const EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
+const EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
+const EnigmailExecution = ChromeUtils.import("chrome://openpgp/content/modules/execution.jsm").EnigmailExecution;
+const EnigmailFiles = ChromeUtils.import("chrome://openpgp/content/modules/files.jsm").EnigmailFiles;
+const EnigmailPassword = ChromeUtils.import("chrome://openpgp/content/modules/passwords.jsm").EnigmailPassword;
+const EnigmailFuncs = ChromeUtils.import("chrome://openpgp/content/modules/funcs.jsm").EnigmailFuncs;
+const EnigmailKeyRing = ChromeUtils.import("chrome://openpgp/content/modules/keyRing.jsm").EnigmailKeyRing;
+const EnigmailConstants = ChromeUtils.import("chrome://openpgp/content/modules/constants.jsm").EnigmailConstants;
+
+
+const gMimeHashAlgorithms = [null, "sha1", "ripemd160", "sha256", "sha384", "sha512", "sha224", "md5"];
+
+const ENC_TYPE_MSG = 0;
+const ENC_TYPE_ATTACH_BINARY = 1;
+const ENC_TYPE_ATTACH_ASCII = 2;
+
+const GPG_COMMENT_OPT = "Using GnuPG with %s - https://doesnotexist-openpgp-integration.thunderbird/";
+
+
+var EnigmailEncryption = {
+  getEncryptCommand: function(fromMailAddr, toMailAddr, bccMailAddr, hashAlgorithm, sendFlags, isAscii, errorMsgObj,
+    logFileObj) {
+    EnigmailLog.DEBUG("encryption.jsm: getEncryptCommand: hashAlgorithm=" + hashAlgorithm + "\n");
+
+    try {
+      fromMailAddr = EnigmailFuncs.stripEmail(fromMailAddr);
+      toMailAddr = EnigmailFuncs.stripEmail(toMailAddr);
+      bccMailAddr = EnigmailFuncs.stripEmail(bccMailAddr);
+
+    }
+    catch (ex) {
+      errorMsgObj.value = EnigmailLocale.getString("invalidEmail");
+      return null;
+    }
+
+    var defaultSend = sendFlags & EnigmailConstants.SEND_DEFAULT;
+    var signMsg = sendFlags & EnigmailConstants.SEND_SIGNED;
+    var encryptMsg = sendFlags & EnigmailConstants.SEND_ENCRYPTED;
+    var usePgpMime = sendFlags & EnigmailConstants.SEND_PGP_MIME;
+
+    var useDefaultComment = false;
+    try {
+      useDefaultComment = EnigmailPrefs.getPref("useDefaultComment");
+    }
+    catch (ex) {}
+
+    var hushMailSupport = false;
+    try {
+      hushMailSupport = EnigmailPrefs.getPref("hushMailSupport");
+    }
+    catch (ex) {}
+
+    var detachedSig = (usePgpMime || (sendFlags & EnigmailConstants.SEND_ATTACHMENT)) && signMsg && !encryptMsg;
+
+    var toAddrList = toMailAddr.split(/\s*,\s*/);
+    var bccAddrList = bccMailAddr.split(/\s*,\s*/);
+    var k;
+
+    var encryptArgs = EnigmailGpg.getStandardArgs(true);
+
+    if (!useDefaultComment)
+      encryptArgs = encryptArgs.concat(["--comment", GPG_COMMENT_OPT.replace(/%s/, EnigmailApp.getName())]);
+
+    var angledFromMailAddr = ((fromMailAddr.search(/^0x/) === 0) || hushMailSupport) ?
+      fromMailAddr : "<" + fromMailAddr + ">";
+    angledFromMailAddr = angledFromMailAddr.replace(/(["'`])/g, "\\$1");
+
+    if (signMsg && hashAlgorithm) {
+      encryptArgs = encryptArgs.concat(["--digest-algo", hashAlgorithm]);
+    }
+
+    if (logFileObj) {
+      logFileObj.value = EnigmailErrorHandling.getTempLogFile();
+      encryptArgs.push("--log-file");
+      encryptArgs.push(EnigmailFiles.getEscapedFilename(EnigmailFiles.getFilePath(logFileObj.value)));
+    }
+
+    if (encryptMsg) {
+      switch (isAscii) {
+        case ENC_TYPE_MSG:
+          encryptArgs.push("-a");
+          encryptArgs.push("-t");
+          break;
+        case ENC_TYPE_ATTACH_ASCII:
+          encryptArgs.push("-a");
+      }
+
+      encryptArgs.push("--encrypt");
+
+      if (signMsg)
+        encryptArgs.push("--sign");
+
+      if (sendFlags & EnigmailConstants.SEND_ALWAYS_TRUST) {
+        encryptArgs.push("--trust-model");
+        encryptArgs.push("always");
+      }
+      if ((sendFlags & EnigmailConstants.SEND_ENCRYPT_TO_SELF) && fromMailAddr)
+        encryptArgs = encryptArgs.concat(["--encrypt-to", angledFromMailAddr]);
+
+      for (k = 0; k < toAddrList.length; k++) {
+        toAddrList[k] = toAddrList[k].replace(/'/g, "\\'");
+        if (toAddrList[k].length > 0) {
+          encryptArgs.push("-r");
+          if (toAddrList[k].search(/^GROUP:/) === 0) {
+            // groups from gpg.conf file
+            encryptArgs.push(toAddrList[k].substr(6));
+          }
+          else {
+            encryptArgs.push((hushMailSupport || (toAddrList[k].search(/^0x/) === 0)) ? toAddrList[k] : "<" + toAddrList[k] + ">");
+          }
+        }
+      }
+
+      for (k = 0; k < bccAddrList.length; k++) {
+        bccAddrList[k] = bccAddrList[k].replace(/'/g, "\\'");
+        if (bccAddrList[k].length > 0) {
+          encryptArgs.push("--hidden-recipient");
+          encryptArgs.push((hushMailSupport || (bccAddrList[k].search(/^0x/) === 0)) ? bccAddrList[k] : "<" + bccAddrList[k] + ">");
+        }
+      }
+
+    }
+    else if (detachedSig) {
+      encryptArgs = encryptArgs.concat(["-s", "-b"]);
+
+      switch (isAscii) {
+        case ENC_TYPE_MSG:
+          encryptArgs = encryptArgs.concat(["-a", "-t"]);
+          break;
+        case ENC_TYPE_ATTACH_ASCII:
+          encryptArgs.push("-a");
+      }
+
+    }
+    else if (signMsg) {
+      encryptArgs = encryptArgs.concat(["-t", "--clearsign"]);
+    }
+
+    if (fromMailAddr) {
+      encryptArgs = encryptArgs.concat(["-u", angledFromMailAddr]);
+    }
+
+    return encryptArgs;
+  },
+
+  /**
+   * Determine if the sender key ID or user ID can be used for signing and/or encryption
+   *
+   * @param sendFlags:    Number  - the send Flags; need to contain SEND_SIGNED and/or SEND_ENCRYPTED
+   * @param fromMailAddr: String  - the sender email address or key ID
+   *
+   * @return Object:
+   *         - keyId:    String - the found key ID, or null if fromMailAddr is not valid
+   *         - errorMsg: String - the erorr message if key not valid, or null if key is valid
+   */
+  determineOwnKeyUsability: function(sendFlags, fromMailAddr) {
+    EnigmailLog.DEBUG("encryption.jsm: determineOwnKeyUsability: sendFlags=" + sendFlags + ", sender=" + fromMailAddr + "\n");
+
+    let keyList = [];
+    let ret = {
+      keyId: null,
+      errorMsg: null
+    };
+
+    let sign = (sendFlags & EnigmailConstants.SEND_SIGNED ? true : false);
+    let encrypt = (sendFlags & EnigmailConstants.SEND_ENCRYPTED ? true : false);
+
+    if (fromMailAddr.search(/^(0x)?[A-Z0-9]+$/) === 0) {
+      // key ID specified
+      let key = EnigmailKeyRing.getKeyById(fromMailAddr);
+      keyList.push(key);
+    }
+    else {
+      // email address specified
+      keyList = EnigmailKeyRing.getKeysByUserId(fromMailAddr);
+    }
+
+    if (keyList.length === 0) {
+      ret.errorMsg = EnigmailLocale.getString("errorOwnKeyUnusable", fromMailAddr);
+      return ret;
+    }
+
+    if (sign) {
+      keyList = keyList.reduce(function _f(p, keyObj) {
+        if (keyObj && keyObj.getSigningValidity().keyValid) p.push(keyObj);
+        return p;
+      }, []);
+    }
+
+    if (encrypt) {
+      keyList = keyList.reduce(function _f(p, keyObj) {
+        if (keyObj && keyObj.getEncryptionValidity().keyValid) p.push(keyObj);
+        return p;
+      }, []);
+    }
+
+    if (keyList.length === 0) {
+      if (sign) {
+        ret.errorMsg = EnigmailErrorHandling.determineInvSignReason(fromMailAddr);
+      }
+      else {
+        ret.errorMsg = EnigmailErrorHandling.determineInvRcptReason(fromMailAddr);
+      }
+    }
+    else {
+      ret.keyId = keyList[0].fpr;
+    }
+
+    return ret;
+  },
+
+  encryptMessageStart: function(win, uiFlags, fromMailAddr, toMailAddr, bccMailAddr,
+    hashAlgorithm, sendFlags, listener, statusFlagsObj, errorMsgObj) {
+    EnigmailLog.DEBUG("encryption.jsm: encryptMessageStart: uiFlags=" + uiFlags + ", from " + fromMailAddr + " to " + toMailAddr + ", hashAlgorithm=" + hashAlgorithm + " (" + EnigmailData.bytesToHex(
+      EnigmailData.pack(sendFlags, 4)) + ")\n");
+
+    let keyUseability = this.determineOwnKeyUsability(sendFlags, fromMailAddr);
+
+    if (!keyUseability.keyId) {
+      EnigmailLog.DEBUG("encryption.jsm: encryptMessageStart: own key invalid\n");
+      errorMsgObj.value = keyUseability.errorMsg;
+      statusFlagsObj.value = EnigmailConstants.INVALID_RECIPIENT | EnigmailConstants.NO_SECKEY | EnigmailConstants.DISPLAY_MESSAGE;
+
+      return null;
+    }
+
+    var pgpMime = uiFlags & EnigmailConstants.UI_PGP_MIME;
+
+    var hashAlgo = gMimeHashAlgorithms[EnigmailPrefs.getPref("mimeHashAlgorithm")];
+
+    if (hashAlgorithm) {
+      hashAlgo = hashAlgorithm;
+    }
+
+    errorMsgObj.value = "";
+
+    if (!sendFlags) {
+      EnigmailLog.DEBUG("encryption.jsm: encryptMessageStart: NO ENCRYPTION!\n");
+      errorMsgObj.value = EnigmailLocale.getString("notRequired");
+      return null;
+    }
+
+    if (!EnigmailCore.getService(win)) {
+      EnigmailLog.ERROR("encryption.jsm: encryptMessageStart: not yet initialized\n");
+      errorMsgObj.value = EnigmailLocale.getString("notInit");
+      return null;
+    }
+
+    let logFileObj = {};
+    let encryptArgs = EnigmailEncryption.getEncryptCommand(fromMailAddr, toMailAddr, bccMailAddr, hashAlgo, sendFlags, ENC_TYPE_MSG, errorMsgObj, logFileObj);
+    if (!encryptArgs)
+      return null;
+
+    var signMsg = sendFlags & EnigmailConstants.SEND_SIGNED;
+    if (!listener) {
+      listener = {};
+    }
+    if ("done" in listener) {
+      listener.outerDone = listener.done;
+    }
+
+    listener.done = function(exitCode) {
+      EnigmailErrorHandling.appendLogFileToDebug(logFileObj.value);
+      if (this.outerDone) {
+        this.outerDone(exitCode);
+      }
+    };
+
+    var proc = EnigmailExecution.execStart(EnigmailGpgAgent.agentPath, encryptArgs, signMsg, win, listener, statusFlagsObj);
+
+    if (statusFlagsObj.value & EnigmailConstants.MISSING_PASSPHRASE) {
+      EnigmailLog.ERROR("encryption.jsm: encryptMessageStart: Error - no passphrase supplied\n");
+
+      errorMsgObj.value = "";
+    }
+
+    if (pgpMime && errorMsgObj.value) {
+      EnigmailDialog.alert(win, errorMsgObj.value);
+    }
+
+    return proc;
+  },
+
+  encryptMessageEnd: function(fromMailAddr, stderrStr, exitCode, uiFlags, sendFlags, outputLen, retStatusObj) {
+    EnigmailLog.DEBUG("encryption.jsm: encryptMessageEnd: uiFlags=" + uiFlags + ", sendFlags=" + EnigmailData.bytesToHex(EnigmailData.pack(sendFlags, 4)) + ", outputLen=" + outputLen + "\n");
+
+    var pgpMime = uiFlags & EnigmailConstants.UI_PGP_MIME;
+    var defaultSend = sendFlags & EnigmailConstants.SEND_DEFAULT;
+    var signMsg = sendFlags & EnigmailConstants.SEND_SIGNED;
+    var encryptMsg = sendFlags & EnigmailConstants.SEND_ENCRYPTED;
+
+    retStatusObj.statusFlags = 0;
+    retStatusObj.errorMsg = "";
+    retStatusObj.blockSeparation = "";
+
+    if (!EnigmailCore.getService().initialized) {
+      EnigmailLog.ERROR("encryption.jsm: encryptMessageEnd: not yet initialized\n");
+      retStatusObj.errorMsg = EnigmailLocale.getString("notInit");
+      return -1;
+    }
+
+    EnigmailErrorHandling.parseErrorOutput(stderrStr, retStatusObj);
+
+    exitCode = EnigmailExecution.fixExitCode(exitCode, retStatusObj);
+    if ((exitCode === 0) && !outputLen) {
+      exitCode = -1;
+    }
+
+    if (exitCode !== 0 && (signMsg || encryptMsg)) {
+      // GnuPG might return a non-zero exit code, even though the message was correctly
+      // signed or encryped -> try to fix the exit code
+
+      var correctedExitCode = 0;
+      if (signMsg) {
+        if (!(retStatusObj.statusFlags & EnigmailConstants.SIG_CREATED)) correctedExitCode = exitCode;
+      }
+      if (encryptMsg) {
+        if (!(retStatusObj.statusFlags & EnigmailConstants.END_ENCRYPTION)) correctedExitCode = exitCode;
+      }
+      exitCode = correctedExitCode;
+    }
+
+    EnigmailLog.DEBUG("encryption.jsm: encryptMessageEnd: command execution exit code: " + exitCode + "\n");
+
+    if (retStatusObj.statusFlags & EnigmailConstants.DISPLAY_MESSAGE) {
+      if (retStatusObj.extendedStatus.search(/\bdisp:/) >= 0) {
+        retStatusObj.errorMsg = retStatusObj.statusMsg;
+      }
+      else {
+        if (fromMailAddr.search(/^0x/) === 0) {
+          fromMailAddr = fromMailAddr.substr(2);
+        }
+        if (fromMailAddr.search(/^[A-F0-9]{8,40}$/i) === 0) {
+          fromMailAddr = "[A-F0-9]+" + fromMailAddr;
+        }
+
+        let s = new RegExp("^(\\[GNUPG:\\] )?INV_(RECP|SGNR) [0-9]+ (\\<|0x)?" + fromMailAddr + "\\>?", "m");
+        if (retStatusObj.statusMsg.search(s) >= 0) {
+          retStatusObj.errorMsg += "\n\n" + EnigmailLocale.getString("keyError.resolutionAction");
+        }
+        else if (retStatusObj.statusMsg.length > 0) {
+          retStatusObj.errorMsg = retStatusObj.statusMsg;
+        }
+      }
+    }
+    else if (retStatusObj.statusFlags & EnigmailConstants.INVALID_RECIPIENT) {
+      retStatusObj.errorMsg = retStatusObj.statusMsg;
+    }
+    else if (exitCode !== 0) {
+      retStatusObj.errorMsg = EnigmailLocale.getString("badCommand");
+    }
+
+    return exitCode;
+  },
+
+  encryptMessage: function(parent, uiFlags, plainText, fromMailAddr, toMailAddr, bccMailAddr, sendFlags,
+    exitCodeObj, statusFlagsObj, errorMsgObj) {
+    EnigmailLog.DEBUG("enigmail.js: Enigmail.encryptMessage: " + plainText.length + " bytes from " + fromMailAddr + " to " + toMailAddr + " (" + sendFlags + ")\n");
+
+    exitCodeObj.value = -1;
+    statusFlagsObj.value = 0;
+    errorMsgObj.value = "";
+
+    if (!plainText) {
+      EnigmailLog.DEBUG("enigmail.js: Enigmail.encryptMessage: NO ENCRYPTION!\n");
+      exitCodeObj.value = 0;
+      EnigmailLog.DEBUG("  <=== encryptMessage()\n");
+      return plainText;
+    }
+
+    var defaultSend = sendFlags & EnigmailConstants.SEND_DEFAULT;
+    var signMsg = sendFlags & EnigmailConstants.SEND_SIGNED;
+    var encryptMsg = sendFlags & EnigmailConstants.SEND_ENCRYPTED;
+
+    if (encryptMsg) {
+      // First convert all linebreaks to newlines
+      plainText = plainText.replace(/\r\n/g, "\n");
+      plainText = plainText.replace(/\r/g, "\n");
+
+      // we need all data in CRLF according to RFC 4880
+      plainText = plainText.replace(/\n/g, "\r\n");
+    }
+
+    var listener = EnigmailExecution.newSimpleListener(
+      function _stdin(pipe) {
+        pipe.write(plainText);
+        pipe.close();
+      },
+      function _done(exitCode) {});
+
+
+    var proc = EnigmailEncryption.encryptMessageStart(parent, uiFlags,
+      fromMailAddr, toMailAddr, bccMailAddr,
+      null, sendFlags,
+      listener, statusFlagsObj, errorMsgObj);
+    if (!proc) {
+      exitCodeObj.value = -1;
+      EnigmailLog.DEBUG("  <=== encryptMessage()\n");
+      return "";
+    }
+
+    // Wait for child pipes to close
+    proc.wait();
+
+    var retStatusObj = {};
+    exitCodeObj.value = EnigmailEncryption.encryptMessageEnd(fromMailAddr, EnigmailData.getUnicodeData(listener.stderrData), listener.exitCode,
+      uiFlags, sendFlags,
+      listener.stdoutData.length,
+      retStatusObj);
+
+    statusFlagsObj.value = retStatusObj.statusFlags;
+    statusFlagsObj.statusMsg = retStatusObj.statusMsg;
+    errorMsgObj.value = retStatusObj.errorMsg;
+
+
+    if ((exitCodeObj.value === 0) && listener.stdoutData.length === 0)
+      exitCodeObj.value = -1;
+
+    if (exitCodeObj.value === 0) {
+      // Normal return
+      EnigmailLog.DEBUG("  <=== encryptMessage()\n");
+      return EnigmailData.getUnicodeData(listener.stdoutData);
+    }
+
+    // Error processing
+    EnigmailLog.DEBUG("enigmail.js: Enigmail.encryptMessage: command execution exit code: " + exitCodeObj.value + "\n");
+    return "";
+  },
+
+  encryptAttachment: function(parent, fromMailAddr, toMailAddr, bccMailAddr, sendFlags, inFile, outFile,
+    exitCodeObj, statusFlagsObj, errorMsgObj) {
+