Bug 1600147 - Ability to import public key attached to email, and deleting a key using key manager. r=patrick DONTBUILD
authorKai Engert <kaie@kuix.de>
Sat, 30 Nov 2019 19:27:02 +0100
changeset 36757 693ecddb204ff925eb351f44f38b0442e76214f9
parent 36756 5f1b51ad26efeabc949db3a3c23564f59d933930
child 36758 7dcc5e7050596410102eb42e30fdf1003e3477d8
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
bugs1600147
Bug 1600147 - Ability to import public key attached to email, and deleting a key using key manager. r=patrick DONTBUILD
mail/extensions/openpgp/content/modules/cryptoAPI/gnupg.js
mail/extensions/openpgp/content/modules/cryptoAPI/interface.js
mail/extensions/openpgp/content/modules/cryptoAPI/rnp-cryptoAPI.js
mail/extensions/openpgp/content/modules/keyRing.jsm
mail/extensions/openpgp/content/modules/rnp.jsm
mail/extensions/openpgp/content/modules/rnpLib.jsm
mail/extensions/openpgp/content/modules/timer.jsm
mail/extensions/openpgp/content/strings/bond.dtd
mail/extensions/openpgp/content/strings/enigmail.dtd
mail/extensions/openpgp/content/ui/attachmentItemContext.inc.xul
mail/extensions/openpgp/content/ui/enigmailKeyManager.js
mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
--- a/mail/extensions/openpgp/content/modules/cryptoAPI/gnupg.js
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/gnupg.js
@@ -198,16 +198,20 @@ class GnuPGCryptoAPI extends CryptoAPI {
    *
    * @return {nsIFile} object or null in case no data / error.
    */
   async getPhotoFile(keyId, photoNumber) {
     let file = await getPhotoFileFromGnuPG(keyId, photoNumber);
     return file;
   }
 
+  async importKeyBlock(keyBlock) {
+    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
@@ -410,13 +414,17 @@ class GnuPGCryptoAPI extends CryptoAPI {
     res = await getGpgKeyData(keyBlockStr);
     return res;
   }
 
   async genKey(userId, keyType, keySize, expiryTime, passphrase) {
     throw "GnuPG genKey() not implemented";
     return null;
   }
+
+  async deleteKey(keyFingerprint, deleteSecret) {
+    return null;
+  }
 }
 
 function getGnuPGAPI() {
   return new GnuPGCryptoAPI();
 }
--- a/mail/extensions/openpgp/content/modules/cryptoAPI/interface.js
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/interface.js
@@ -131,16 +131,20 @@ class CryptoAPI {
    *   - {Number}          importSum:       total number of processed keys
    *   - {Number}          importUnchanged: number of unchanged keys
    */
 
   async importKeyFromFile(inputFile) {
     return null;
   }
 
+  async importKeyBlock(keyBlock) {
+    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)
@@ -278,9 +282,13 @@ class CryptoAPI {
    *                            Set to null to use an empty passphrase.
    *
    * @return {Promise<String>} - The new KeyID
    */
 
   async genKey(userId, keyType, keySize, expiryTime, passphrase) {
     return null;
   }
+
+  async deleteKey(keyFingerprint, deleteSecret) {
+    return null;
+  }
 }
--- a/mail/extensions/openpgp/content/modules/cryptoAPI/rnp-cryptoAPI.js
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/rnp-cryptoAPI.js
@@ -113,16 +113,23 @@ class RNPCryptoAPI extends CryptoAPI {
    *
    * @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) {}
 
+  async importKeyBlock(keyBlock) {
+    // TODO: get status results
+    let res = RNP.importKeyBlock(keyBlock);
+    RNP.saveKeyRings();
+    return res;
+  }
+
   /**
    * 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
@@ -238,22 +245,38 @@ class RNPCryptoAPI extends CryptoAPI {
    *
    * @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) {}
+  async verifyMime(signed, options) {
+    console.log("rnp-cryptoAPI.js: verifyMime()");
+    EnigmailLog.DEBUG(`rnp-cryptoAPI.js: verifyMime()\n`);
 
-  async getKeyListFromKeyBlock(keyBlockStr) {}
+    options.noOutput = true;
+    options.verifyOnly = true;
+    options.uiFlags = EnigmailConstants.UI_PGP_MIME;
+
+    return this.decrypt(signed, options);
+  }
+
+  async getKeyListFromKeyBlock(keyBlockStr) {
+    return RNP.getKeyListFromKeyBlock(keyBlockStr);
+  }
 
   async genKey(userId, keyType, keySize, expiryTime, passphrase) {
     let id = RNP.genKey(userId, keyType, keySize, expiryTime, passphrase);
     RNP.saveKeyRings();
     return id;
   }
+  
+  async deleteKey(keyFingerprint, deleteSecret) {
+    return RNP.deleteKey(keyId, deleteSecret);
+  }
+  
 }
 
 function getRNPAPI() {
   return new RNPCryptoAPI();
 }
--- a/mail/extensions/openpgp/content/modules/keyRing.jsm
+++ b/mail/extensions/openpgp/content/modules/keyRing.jsm
@@ -72,20 +72,22 @@ var EnigmailKeyRing = {
    * @param  sortDirection - |number| 1 = ascending / -1 = descending
    *
    * @return keyListObj    - |object| { keyList, keySortList } (see above)
    */
   getAllKeys: function(win, sortColumn, sortDirection) {
     if (gKeyListObj.keySortList.length === 0) {
       loadKeyList(win, sortColumn, sortDirection);
       getWindows().keyManReloadKeys();
+      /* TODO: do we need something similar with TB's future trust behavior?
       if (!gKeyCheckDone) {
         gKeyCheckDone = true;
         runKeyUsabilityCheck();
       }
+      */
     }
     else {
       if (sortColumn) {
         gKeyListObj.keySortList.sort(getSortFunction(sortColumn.toLowerCase(), gKeyListObj, sortDirection));
       }
     }
 
     return gKeyListObj;
@@ -555,16 +557,25 @@ var EnigmailKeyRing = {
 
     if (isInteractive) {
       if (!(getDialog().confirmDlg(parent, EnigmailLocale.getString("importKeyConfirm"), EnigmailLocale.getString("keyMan.button.import")))) {
         errorMsgObj.value = EnigmailLocale.getString("failCancel");
         return -1;
       }
     }
 
+    if (limitedUids.length > 0) {
+      throw "importKeyAsync with limitedUids: not implemented";
+    }
+
+    if (minimizeKey) {
+      throw "importKeyAsync with minimizeKey: not implemented";
+    }
+
+    /*
     let args = EnigmailGpg.getStandardArgs(false).concat(["--no-verbose", "--status-fd", "2"]);
     if (minimizeKey) {
       args = args.concat(["--import-options", "import-minimal"]);
     }
 
     if (limitedUids.length > 0 && EnigmailGpg.getGpgFeature("export-specific-uid")) {
       let filter = limitedUids.map(i => {
         return `mbox =~ ${i}`;
@@ -573,22 +584,31 @@ var EnigmailKeyRing = {
       args.push("--import-filter");
       args.push(`keep-uid=${filter}`);
     }
     args = args.concat(["--no-auto-check-trustdb", "--import"]);
 
     const res = await EnigmailExecution.execAsync(EnigmailGpg.agentPath, args, pgpBlock);
 
     const statusMsg = res.statusMsg;
+    */
+
+    const cApi = EnigmailCryptoAPI();
+    let result = cApi.sync(cApi.importKeyBlock(pgpBlock));
 
     if (!importedKeysObj) {
       importedKeysObj = {};
     }
     importedKeysObj.value = [];
 
+    let exitCode = 0;
+
+    EnigmailKeyRing.clearCache();
+
+    /*
     let exitCode = 1;
     if (statusMsg && (statusMsg.search(/^IMPORT_RES /m) > -1)) {
       let importRes = 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 (importRes !== null) {
         let secCount = parseInt(importRes[9], 10); // number of secret keys found
         let secImported = parseInt(importRes[10], 10); // number of secret keys imported
         let secDups = parseInt(importRes[11], 10); // number of secret keys already on the keyring
@@ -612,16 +632,17 @@ var EnigmailKeyRing = {
           }
         }
 
         if (importedKeysObj.value.length > 0) {
           EnigmailKeyRing.updateKeys(importedKeysObj.value);
         }
       }
     }
+    */
 
     return exitCode;
   },
 
   isGeneratingKey: function() {
     return gKeygenProcess !== null;
   },
 
--- a/mail/extensions/openpgp/content/modules/rnp.jsm
+++ b/mail/extensions/openpgp/content/modules/rnp.jsm
@@ -43,17 +43,17 @@ var RNP = {
       this.once();
     }
 
     if (!RNP.libLoaded) {
       console.log("failed to load RNP library");
     }
   },
 
-  addKeyAttributes(handle, keyObj, is_subkey) {
+  addKeyAttributes(handle, keyObj, is_subkey, forListing) {
     let have_secret = new ctypes.bool;
     let key_id = new ctypes.char.ptr;
     let fingerprint = new ctypes.char.ptr;
     let algo = new ctypes.char.ptr;
     let bits = new ctypes.uint32_t;
     let key_creation = new ctypes.uint32_t;
     let key_expiration = new ctypes.uint32_t;
     let allowed = new ctypes.bool;
@@ -69,16 +69,19 @@ var RNP = {
     } else {
       keyObj.type = "pub";
     }
 
     if (RNPLib.rnp_key_get_keyid(handle, key_id.address())) {
       throw "rnp_key_get_keyid failed";
     }
     keyObj.keyId = key_id.readString();
+    if (forListing) {
+      keyObj.id = keyObj.keyId;
+    }
 
     if (RNPLib.rnp_key_get_fprint(handle, fingerprint.address())) {
       throw "rnp_key_get_fprint failed";
     }
     keyObj.fpr = fingerprint.readString();
 
     if (RNPLib.rnp_key_get_alg(handle, algo.address())) {
       throw "rnp_key_get_alg failed";
@@ -129,23 +132,30 @@ var RNP = {
       throw "rnp_key_allows_usage failed";
     }
     if (allowed.value) {
       keyObj.keyUseFor += "a";
     }
   },
   
   getKeys(onlyKeys = null) {
+    return this.getKeysFromFFI(RNPLib.ffi, false, onlyKeys);
+  },
+
+  /* Some consumers want a different listing of keys, and expect 
+   * slightly different attribute names...
+   * If forListing is true, we'll set those additional attributes. */
+  getKeysFromFFI(ffi, forListing, onlyKeys = null) {
     let keys = [];
     let rv;
 
     let iter = new RNPLib.rnp_identifier_iterator_t;
     let grip = new ctypes.char.ptr();
 
-    rv = RNPLib.rnp_identifier_iterator_create(RNPLib.ffi, iter.address(), "grip");
+    rv = RNPLib.rnp_identifier_iterator_create(ffi, iter.address(), "grip");
     if (rv) {
       return null;
     }
 
     while (!RNPLib.rnp_identifier_iterator_next(iter, grip.address())) {
       if (grip.isNull()) {
         break;
       }
@@ -161,17 +171,17 @@ var RNP = {
       keyObj.subKeys = [];
       keyObj.photoAvailable = false;
 
       try {
         let is_subkey = new ctypes.bool;
         let sub_count = new ctypes.size_t;
         let uid_count = new ctypes.size_t;
 
-        if (RNPLib.rnp_locate_key(RNPLib.ffi, "grip", grip, handle.address())) {
+        if (RNPLib.rnp_locate_key(ffi, "grip", grip, handle.address())) {
           throw "rnp_locate_key failed";
         }
         have_handle = true;
         if (RNPLib.rnp_key_is_sub(handle, is_subkey.address())) {
           throw "rnp_key_is_sub failed";
         }
         if (is_subkey.value) {
           let primary_grip = new ctypes.char.ptr();
@@ -187,19 +197,22 @@ var RNP = {
 
         let key_revoked = new ctypes.bool;
         if (RNPLib.rnp_key_is_revoked(handle, key_revoked.address())) {
           throw "rnp_key_is_revoked failed";
         }
 
         if (key_revoked.value) {
           keyObj.keyTrust = "r";
+          if (forListing) {
+            keyObj.revoke = true;
+          }
         }
 
-        this.addKeyAttributes(handle, keyObj, false);
+        this.addKeyAttributes(handle, keyObj, false, forListing);
 
         /* The remaining actions are done for primary keys, only. */
         if (is_subkey.value) {
           continue;
         }
         
         let primary_uid_set = false;
 
@@ -222,16 +235,19 @@ console.log("rnp_key_get_uid_count: " + 
           if (!is_revoked.value) {
             let uid_str = new ctypes.char.ptr;
             if (RNPLib.rnp_key_get_uid_at(handle, i, uid_str.address())) {
               throw "rnp_key_get_uid_at failed";
             }
             
             if (!primary_uid_set) {
               keyObj.userId = uid_str.readString();
+              if (forListing) {
+                keyObj.name = keyObj.userId;
+              }
               primary_uid_set = true;
             }
 
             let uidObj = {};
             uidObj.userId = uid_str.readString();
             uidObj.type = "uid";
             uidObj.keyTrust = "/";
             uidObj.uidFpr = "??fpr??"
@@ -251,17 +267,17 @@ console.log("rnp_key_get_subkey_count: "
         for (let i = 0; i < sub_count.value; i++) {
           let sub_handle = new RNPLib.rnp_key_handle_t;
           if (RNPLib.rnp_key_get_subkey_at(handle, i, sub_handle.address())) {
               throw "rnp_key_get_subkey_at failed";
           }
 
           let subKeyObj = {};
           subKeyObj.keyTrust = "/";
-          this.addKeyAttributes(sub_handle, subKeyObj, true);
+          this.addKeyAttributes(sub_handle, subKeyObj, true, forListing);
           keyObj.subKeys.push(subKeyObj);
 
           RNPLib.rnp_key_handle_destroy(sub_handle);
         }
       } catch (ex) {
         console.log(ex);
       } finally {
         if (have_handle) {
@@ -481,13 +497,95 @@ console.log("rnp_key_get_subkey_count: "
     RNPLib.rnp_op_generate_destroy(genOp);
     
     return newKeyId;
   },
 
   saveKeyRings() {
     RNPLib.saveKeys();
   },
+  
+  importToFFI(ffi, keyBlockStr) {
+    let input_from_memory = new RNPLib.rnp_input_t;
+
+    var tmp_array = ctypes.char.array()(keyBlockStr);
+    var key_array = ctypes.cast(
+      tmp_array,
+      ctypes.uint8_t.array(keyBlockStr.length)
+    );
+
+    RNPLib.rnp_input_from_memory(
+      input_from_memory.address(),
+      key_array,
+      keyBlockStr.length,
+      false
+    );
+
+    let jsonInfo = new ctypes.char.ptr();
+    
+    let rv = RNPLib.rnp_import_keys(ffi, input_from_memory,
+                                    RNPLib.RNP_LOAD_SAVE_PUBLIC_KEYS,
+                                    jsonInfo.address());
+
+    // TODO: parse jsonInfo and return a list of keys, 
+    // as seen in keyRing.importKeyAsync.
+    // (should prevent the incorrect popup "no keys imported".)
+
+    console.log("result key listing, rv: " + rv + ", result: " + jsonInfo.readString());
+
+    RNPLib.rnp_buffer_destroy(jsonInfo);
+    RNPLib.rnp_input_destroy(input_from_memory);
+    
+    return null;
+  },
+
+  getKeyListFromKeyBlock(keyBlockStr) {
+    // Create a separate, temporary RNP storage area (FFI),
+    // import the key block into it, then get the listing.
+
+    console.log("trying to get key listing for this data: " + keyBlockStr);
+
+    let tempFFI = new RNPLib.rnp_ffi_t;
+    if (RNPLib.rnp_ffi_create(tempFFI.address(), "GPG", "GPG")) {
+      throw new Error("Couldn't initialize librnp.");
+    }
+    
+    this.importToFFI(tempFFI, keyBlockStr);
+
+    let keys = this.getKeysFromFFI(tempFFI, true);
+
+console.log("result key array:");
+console.log(keys);
+
+    RNPLib.rnp_ffi_destroy(tempFFI);
+    return keys;
+  },
+  
+  importKeyBlock(keyBlockStr) {
+    return this.importToFFI(RNPLib.ffi, keyBlockStr);
+  },
+
+  deleteKey(keyFingerprint, deleteSecret) {
+console.log("deleting key with fingerprint: " + keyFingerprint);
+
+    let handle = new RNPLib.rnp_key_handle_t;
+    if (RNPLib.rnp_locate_key(RNPLib.ffi, "fingerprint", keyFingerprint, handle.address())) {
+      throw "rnp_locate_key failed";
+    }
+
+    let flags = RNPLib.RNP_KEY_REMOVE_PUBLIC;
+    if (deleteSecret) {
+      flags |= RNPLib.RNP_KEY_REMOVE_SECRET;
+    }
+    
+    if (RNPLib.rnp_key_remove(handle, flags)) {
+      throw ("rnp_key_remove failed");
+    }
+
+    RNPLib.rnp_key_handle_destroy(handle);
+    this.saveKeyRings();
+  },
+
 };
 
 // exports
 
 this.EXPORTED_SYMBOLS = ["RNP"];
--- a/mail/extensions/openpgp/content/modules/rnpLib.jsm
+++ b/mail/extensions/openpgp/content/modules/rnpLib.jsm
@@ -92,19 +92,16 @@ const rnp_password_cb_t = ctypes.Functio
   rnp_ffi_t,
   ctypes.void_t.ptr,
   rnp_key_handle_t,
   ctypes.char.ptr,
   ctypes.char.ptr,
   ctypes.size_t,
 ]).ptr;
 
-const RNP_LOAD_SAVE_PUBLIC_KEYS = 1;
-const RNP_LOAD_SAVE_SECRET_KEYS = 2;
-
 var RNPLib;
 
 function enableRNPLibJS() {
   // this must be delayed until after "librnp" is initialized
 
   RNPLib = {
     path: librnpPath,
 
@@ -146,24 +143,24 @@ function enableRNPLibJS() {
       let filenames = this.getFilenames();
 
       let input_from_path = new rnp_input_t;
       this.rnp_input_from_path(input_from_path.address(), filenames.pubring);
       this.rnp_load_keys(
         this.ffi,
         "GPG",
         input_from_path,
-        RNP_LOAD_SAVE_PUBLIC_KEYS
+        this.RNP_LOAD_SAVE_PUBLIC_KEYS
       );
       this.rnp_input_destroy(input_from_path);
 
       let in2 = new rnp_input_t;
 
       this.rnp_input_from_path(in2.address(), filenames.secring);
-      this.rnp_load_keys(this.ffi, "GPG", in2, RNP_LOAD_SAVE_SECRET_KEYS);
+      this.rnp_load_keys(this.ffi, "GPG", in2, this.RNP_LOAD_SAVE_SECRET_KEYS);
       this.rnp_input_destroy(in2);
 
       input_from_path = null;
       in2 = null;
 
       let pubnum = new ctypes.size_t;
       this.rnp_get_public_key_count(this.ffi, pubnum.address());
 
@@ -188,24 +185,24 @@ function enableRNPLibJS() {
 
       let rv;
       let output_to_path = new rnp_output_t;
       rv = this.rnp_output_to_path(output_to_path.address(), filenames.pubring);
       rv = this.rnp_save_keys(
         this.ffi,
         "GPG",
         output_to_path,
-        RNP_LOAD_SAVE_PUBLIC_KEYS
+        this.RNP_LOAD_SAVE_PUBLIC_KEYS
       );
       this.rnp_output_destroy(output_to_path);
 
       let out2 = new rnp_output_t;
 
       rv = this.rnp_output_to_path(out2.address(), filenames.secring);
-      rv = this.rnp_save_keys(this.ffi, "GPG", out2, RNP_LOAD_SAVE_SECRET_KEYS);
+      rv = this.rnp_save_keys(this.ffi, "GPG", out2, this.RNP_LOAD_SAVE_SECRET_KEYS);
       this.rnp_output_destroy(out2);
 
       output_to_path = null;
       out2 = null;
     },
 
     keep_password_cb_alive: null,
 
@@ -672,24 +669,51 @@ function enableRNPLibJS() {
     ),
 
     rnp_op_generate_destroy: librnp.declare(
       "rnp_op_generate_destroy",
       abi,
       rnp_result_t,
       rnp_op_generate_t,
     ),
+    
+    rnp_import_keys: librnp.declare(
+      "rnp_import_keys",
+      abi,
+      rnp_result_t,
+      rnp_ffi_t,
+      rnp_input_t,
+      ctypes.uint32_t,
+      ctypes.char.ptr.ptr
+    ),
+    
+    rnp_key_remove: librnp.declare(
+      "rnp_key_remove",
+      abi,
+      rnp_result_t,
+      rnp_key_handle_t,
+      ctypes.uint32_t
+    ),
 
     rnp_result_t,
     rnp_ffi_t,
     rnp_password_cb_t,
     rnp_input_t,
     rnp_output_t,
     rnp_key_handle_t,
     rnp_uid_handle_t,
     rnp_identifier_iterator_t,
     rnp_op_generate_t,
+    
+    RNP_LOAD_SAVE_PUBLIC_KEYS: 1,
+    
+    RNP_LOAD_SAVE_SECRET_KEYS: 2,
+    
+    RNP_KEY_REMOVE_PUBLIC: 1,
+    
+    RNP_KEY_REMOVE_SECRET: 2,
+
   };
 }
 
 // exports
 
 this.EXPORTED_SYMBOLS = ["RNPLibLoader"];
--- a/mail/extensions/openpgp/content/modules/timer.jsm
+++ b/mail/extensions/openpgp/content/modules/timer.jsm
@@ -41,9 +41,9 @@ var EnigmailTimer = {
   },
 
   /**
    * Cancel a timeout callback
    *
    * @param Number: timeoutID
    */
   clearTimeout: clearTimeout
-};
\ No newline at end of file
+};
--- a/mail/extensions/openpgp/content/strings/bond.dtd
+++ b/mail/extensions/openpgp/content/strings/bond.dtd
@@ -1,2 +1,11 @@
 <!ENTITY manageKeysOpenPGPCmd.label "OpenPGP Key Management…">
 <!ENTITY manageKeysOpenPGPCmd.accesskey "o">
+
+<!ENTITY enigmail.ctxDecryptOpen.label              "Decrypt and Open">
+<!ENTITY enigmail.ctxDecryptSave.label              "Decrypt and Save As...">
+<!ENTITY enigmail.ctxImportKey.label                "Import OpenPGP Key">
+<!ENTITY enigmail.ctxVerifyAtt.label                "Verify signature">
+<!ENTITY enigmail.ctxDecryptOpen.accesskey          "D">
+<!ENTITY enigmail.ctxDecryptSave.accesskey          "C">
+<!ENTITY enigmail.ctxImportKey.accesskey            "I">
+<!ENTITY enigmail.ctxVerifyAtt.accesskey            "V">
--- a/mail/extensions/openpgp/content/strings/enigmail.dtd
+++ b/mail/extensions/openpgp/content/strings/enigmail.dtd
@@ -308,25 +308,16 @@
 <!ENTITY enigmail.enigAttachPgpMimeEncrypt.label        "Encrypt the message as a whole and send it using PGP/MIME">
 <!ENTITY enigmail.enigAttachPgpMimeEncryptAndSign.label "Encrypt and sign the message as a whole and send it using PGP/MIME">
 <!ENTITY enigmail.enigEncryptAttachDontEncryptMsg.label        "Don't encrypt/sign the message at all">
 <!ENTITY enigmail.enigAttachDontEncryptMsgSign.label           "Don't sign the message at all">
 <!ENTITY enigmail.enigAttachDontEncryptMsgEncrypt.label        "Don't encrypt the message at all">
 <!ENTITY enigmail.enigAttachDontEncryptMsgEncryptAndSign.label "Don't encrypt and sign the message at all">
 <!ENTITY enigmail.encryptAttachSkipDlg.label        "Use the selected method for all future attachments">
 
-
-<!ENTITY enigmail.ctxDecryptOpen.label              "Decrypt and Open">
-<!ENTITY enigmail.ctxDecryptSave.label              "Decrypt and Save As...">
-<!ENTITY enigmail.ctxImportKey.label                "Import OpenPGP Key">
-<!ENTITY enigmail.ctxVerifyAtt.label                "Verify signature">
-<!ENTITY enigmail.ctxDecryptOpen.accesskey          "D">
-<!ENTITY enigmail.ctxDecryptSave.accesskey          "C">
-<!ENTITY enigmail.ctxImportKey.accesskey            "I">
-<!ENTITY enigmail.ctxVerifyAtt.accesskey            "V">
 <!ENTITY enigmail.detailsHdrButton.label            "Details">
 <!ENTITY enigmail.revealAttachmentsButton.label     "The names of the files attached are hidden. Press the 'Reveal' button to retrieve the original file names.">
 <!ENTITY enigmail.revealAttachments.button          "Reveal">
 
 <!ENTITY enigmail.exchangeGarbage.desc              "This is a broken PGP/MIME message from MS-Exchange. If you cannot access the attachments or have problems with replying or forwarding, press the 'Repair message' button to fix the message">
 <!ENTITY enigmail.exchangeGarbage.fixButton.label   "Repair message">
 <!ENTITY enigmail.exchangeGarbage.waitMessage       "Please wait ...">
 
new file mode 100644
--- /dev/null
+++ b/mail/extensions/openpgp/content/ui/attachmentItemContext.inc.xul
@@ -0,0 +1,17 @@
+    <menuseparator/>
+    <menuitem id="enigmail_ctxImportKey"
+      label="&enigmail.ctxImportKey.label;"
+      accesskey="&enigmail.ctxImportKey.accesskey;"
+      oncommand="Enigmail.msg.handleAttachmentSel('importKey');"/>
+    <menuitem id="enigmail_ctxDecryptOpen"
+      label="&enigmail.ctxDecryptOpen.label;"
+      accesskey="&enigmail.ctxDecryptOpen.accesskey;"
+      oncommand="Enigmail.msg.handleAttachmentSel('openAttachment');"/>
+    <menuitem id="enigmail_ctxDecryptSave"
+      label="&enigmail.ctxDecryptSave.label;"
+      accesskey="&enigmail.ctxDecryptSave.accesskey;"
+      oncommand="Enigmail.msg.handleAttachmentSel('saveAttachment');"/>
+    <menuitem id="enigmail_ctxVerifyAtt"
+      label="&enigmail.ctxVerifyAtt.label;"
+      accesskey="&enigmail.ctxVerifyAtt.accesskey;"
+      oncommand="Enigmail.msg.handleAttachmentSel('verifySig');"/>
--- a/mail/extensions/openpgp/content/ui/enigmailKeyManager.js
+++ b/mail/extensions/openpgp/content/ui/enigmailKeyManager.js
@@ -328,29 +328,37 @@ function enigmailDeleteKey() {
 
     if (deleteSecret) {
       if (!EnigConfirm(EnigGetString("deleteMix"), EnigGetString("dlg.button.delete"))) return;
     } else {
       if (!EnigConfirm(EnigGetString("deleteSelectedPubKey"), EnigGetString("dlg.button.delete"))) return;
     }
   }
 
+  for (let j in keyList) {
+    RNP.deleteKey(gKeyList[keyList[j]].fpr, deleteSecret);
+  }
+  clearKeyCache();
+
+
+  /*
   let fprArr = [];
   for (let j in keyList) {
     fprArr.push("0x" + gKeyList[keyList[j]].fpr);
   }
 
   EnigmailKeyEditor.deleteKey(window, fprArr.join(" "), deleteSecret,
     function(exitCode, errorMsg) {
       if (exitCode !== 0) {
         EnigAlert(EnigGetString("deleteKeyFailed") + "\n\n" + errorMsg);
         return;
       }
       refreshKeys();
     });
+  */
 }
 
 
 function enigmailEnableKey() {
   var enigmailSvc = GetEnigmailSvc();
   if (!enigmailSvc)
     return;
 
--- a/mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
+++ b/mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
@@ -104,34 +104,16 @@ Enigmail.msg = {
           catch (ex) {}
         }
         else {
           EnigmailLog.DEBUG("enigmailMessengerOverlay.js: *** UNABLE to override id=" + elementId + "\n");
         }
       }
     }
 
-    let t = document.getElementById("tabmail");
-
-    if (t) {
-      // TB >= 63
-      t.addEventListener("pageshow", function(e) {
-        if (e.type === "pageshow" && e.target.URL === "about:preferences") {
-          let Overlays = ChromeUtils.import("chrome://openpgp/content/modules/overlays.jsm", {}).Overlays;
-          Overlays.loadOverlays("Enigmail", e.target.defaultView, ["chrome://openpgp/content/ui/enigmailPrivacyOverlay.xul"]);
-        }
-      }, false);
-    }
-
-    let customizeToolbar = document.getElementById("customizeToolbarSheetIFrame");
-    customizeToolbar.addEventListener("pageshow", function(event) {
-      let Overlays = ChromeUtils.import("chrome://openpgp/content/modules/overlays.jsm", {}).Overlays;
-      Overlays.loadOverlays("Enigmail", event.target.defaultView, ["chrome://openpgp/content/ui/enigmailCustToolOverlay.xul"]);
-    }, false);
-
     Enigmail.msg.messagePane = document.getElementById("messagepane");
 
     EnigmailLog.DEBUG("enigmailMessengerOverlay.js: Startup\n");
 
     // Override SMIME ui
     overrideAttribute(["cmd_viewSecurityStatus"], "Enigmail.msg.viewSecurityInfo(null, true);", "", "");
 
     // Override print command