Bug 1637508 - Allow key import in RNP's permissive mode. r=PatrickBrunschwig a=wsmwk
authorKai Engert <kaie@kuix.de>
Fri, 05 Jun 2020 00:04:26 +0200
changeset 39396 0e39b8506d47c922ce6dc9ef7cbfdb7e831d5722
parent 39395 abe3463441023c7eeb1cbd5929eaeb6a89b11f2d
child 39397 2f809d3fe53bb2fc7eed975af9560df429f8e72b
push id402
push userclokep@gmail.com
push dateMon, 29 Jun 2020 20:48:04 +0000
reviewersPatrickBrunschwig, wsmwk
bugs1637508
Bug 1637508 - Allow key import in RNP's permissive mode. r=PatrickBrunschwig a=wsmwk Differential Revision: https://phabricator.services.mozilla.com/D78856
mail/extensions/openpgp/content/modules/RNP.jsm
mail/extensions/openpgp/content/modules/RNPLib.jsm
mail/extensions/openpgp/content/modules/autocrypt.jsm
mail/extensions/openpgp/content/modules/cryptoAPI/GnuPGCryptoAPI.jsm
mail/extensions/openpgp/content/modules/cryptoAPI/RNPCryptoAPI.jsm
mail/extensions/openpgp/content/modules/cryptoAPI/interface.js
mail/extensions/openpgp/content/modules/decryption.jsm
mail/extensions/openpgp/content/modules/key.jsm
mail/extensions/openpgp/content/modules/keyLookupHelper.jsm
mail/extensions/openpgp/content/modules/keyRing.jsm
mail/extensions/openpgp/content/modules/keyserver.jsm
mail/extensions/openpgp/content/strings/enigmail.properties
mail/extensions/openpgp/content/ui/enigmailKeyManager.js
mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
--- a/mail/extensions/openpgp/content/modules/RNP.jsm
+++ b/mail/extensions/openpgp/content/modules/RNP.jsm
@@ -1111,31 +1111,32 @@ var RNP = {
 
     return newKeyId;
   },
 
   saveKeyRings() {
     RNPLib.saveKeys();
   },
 
-  importToFFI(ffi, keyBlockStr, usePublic, useSecret) {
+  importToFFI(ffi, keyBlockStr, usePublic, useSecret, permissive) {
     let input_from_memory = new RNPLib.rnp_input_t();
 
     if (!keyBlockStr) {
       throw new Error("no keyBlockStr parameter in importToFFI");
     }
 
     if (typeof keyBlockStr != "string") {
       throw new Error(
         "keyBlockStr of unepected type importToFFI: %o",
         keyBlockStr
       );
     }
 
     let arr = [];
+    arr.length = keyBlockStr.length;
     for (let i = 0; i < keyBlockStr.length; i++) {
       arr[i] = keyBlockStr.charCodeAt(i);
     }
     var key_array = ctypes.uint8_t.array()(arr);
 
     if (
       RNPLib.rnp_input_from_memory(
         input_from_memory.address(),
@@ -1152,16 +1153,20 @@ var RNP = {
     let flags = 0;
     if (usePublic) {
       flags |= RNPLib.RNP_LOAD_SAVE_PUBLIC_KEYS;
     }
     if (useSecret) {
       flags |= RNPLib.RNP_LOAD_SAVE_SECRET_KEYS;
     }
 
+    if (permissive) {
+      flags |= RNPLib.RNP_LOAD_SAVE_PERMISSIVE;
+    }
+
     let rv = RNPLib.rnp_import_keys(
       ffi,
       input_from_memory,
       flags,
       jsonInfo.address()
     );
 
     // TODO: parse jsonInfo and return a list of keys,
@@ -1175,42 +1180,47 @@ var RNP = {
     RNPLib.rnp_buffer_destroy(jsonInfo);
     RNPLib.rnp_input_destroy(input_from_memory);
 
     return rv;
   },
 
   maxImportKeyBlockSize: 5000000,
 
-  async getKeyListFromKeyBlock(keyBlockStr, pubkey = true, seckey = false) {
+  async getKeyListFromKeyBlockImpl(
+    keyBlockStr,
+    pubkey = true,
+    seckey = false,
+    permissive = true
+  ) {
     if (keyBlockStr.length > RNP.maxImportKeyBlockSize) {
       throw new Error("rejecting big keyblock");
     }
 
     let tempFFI = new RNPLib.rnp_ffi_t();
     if (RNPLib.rnp_ffi_create(tempFFI.address(), "GPG", "GPG")) {
       throw new Error("Couldn't initialize librnp.");
     }
 
     let keyList = null;
-    if (!this.importToFFI(tempFFI, keyBlockStr, pubkey, seckey)) {
+    if (!this.importToFFI(tempFFI, keyBlockStr, pubkey, seckey, permissive)) {
       keyList = await this.getKeysFromFFI(tempFFI, true);
     }
 
     RNPLib.rnp_ffi_destroy(tempFFI);
     return keyList;
   },
 
-  async importKeyBlock(
+  async importKeyBlockImpl(
     win,
     passCB,
     keyBlockStr,
     pubkey,
     seckey,
-    password = null
+    permissive = false
   ) {
     if (keyBlockStr.length > RNP.maxImportKeyBlockSize) {
       throw new Error("rejecting big keyblock");
     }
 
     /*
      * Import strategy:
      * - import file into a temporary space, in-memory only (ffi)
@@ -1229,18 +1239,19 @@ var RNP = {
     result.errorMsg = "";
 
     let tempFFI = new RNPLib.rnp_ffi_t();
     if (RNPLib.rnp_ffi_create(tempFFI.address(), "GPG", "GPG")) {
       throw new Error("Couldn't initialize librnp.");
     }
 
     // TODO: check result
-    if (this.importToFFI(tempFFI, keyBlockStr, pubkey, seckey)) {
-      throw new Error("RNP.importToFFI failed");
+    if (this.importToFFI(tempFFI, keyBlockStr, pubkey, seckey, permissive)) {
+      result.errorMsg = "RNP.importToFFI failed";
+      return result;
     }
 
     let keys = await this.getKeysFromFFI(tempFFI, true);
 
     let recentPass = "";
 
     // Prior to importing, ensure we can unprotect all keys
     for (let ki = 0; ki < keys.length; ki++) {
@@ -1360,16 +1371,19 @@ var RNP = {
 
         let importFlags = 0;
         if (pubkey) {
           importFlags |= RNPLib.RNP_LOAD_SAVE_PUBLIC_KEYS;
         }
         if (seckey) {
           importFlags |= RNPLib.RNP_LOAD_SAVE_SECRET_KEYS;
         }
+        if (permissive) {
+          importFlags |= RNPLib.RNP_LOAD_SAVE_PERMISSIVE;
+        }
 
         if (
           RNPLib.rnp_import_keys(
             RNPLib.ffi,
             input_from_memory,
             importFlags,
             null
           )
--- a/mail/extensions/openpgp/content/modules/RNPLib.jsm
+++ b/mail/extensions/openpgp/content/modules/RNPLib.jsm
@@ -1149,18 +1149,18 @@ function enableRNPLibJS() {
     rnp_op_encrypt_t,
     rnp_op_sign_t,
     rnp_op_sign_signature_t,
     rnp_op_verify_t,
     rnp_op_verify_signature_t,
     rnp_signature_handle_t,
 
     RNP_LOAD_SAVE_PUBLIC_KEYS: 1,
-
     RNP_LOAD_SAVE_SECRET_KEYS: 2,
+    RNP_LOAD_SAVE_PERMISSIVE: 256,
 
     RNP_KEY_REMOVE_PUBLIC: 1,
 
     RNP_KEY_REMOVE_SECRET: 2,
 
     RNP_KEY_EXPORT_ARMORED: 1,
     RNP_KEY_EXPORT_PUBLIC: 2,
     RNP_KEY_EXPORT_SECRET: 4,
--- a/mail/extensions/openpgp/content/modules/autocrypt.jsm
+++ b/mail/extensions/openpgp/content/modules/autocrypt.jsm
@@ -21,34 +21,38 @@ const { EnigmailFuncs } = ChromeUtils.im
   "chrome://openpgp/content/modules/funcs.jsm"
 );
 const { EnigmailMime } = ChromeUtils.import(
   "chrome://openpgp/content/modules/mime.jsm"
 );
 const { EnigmailSqliteDb } = ChromeUtils.import(
   "chrome://openpgp/content/modules/sqliteDb.jsm"
 );
+/*
 const { PromiseUtils } = ChromeUtils.import(
   "resource://gre/modules/PromiseUtils.jsm"
 );
+*/
 const { EnigmailKeyRing } = ChromeUtils.import(
   "chrome://openpgp/content/modules/keyRing.jsm"
 );
 const { EnigmailStreams } = ChromeUtils.import(
   "chrome://openpgp/content/modules/streams.jsm"
 );
 const { EnigmailArmor } = ChromeUtils.import(
   "chrome://openpgp/content/modules/armor.jsm"
 );
 const { EnigmailStdlib } = ChromeUtils.import(
   "chrome://openpgp/content/modules/stdlib.jsm"
 );
+/*
 const { EnigmailCryptoAPI } = ChromeUtils.import(
   "chrome://openpgp/content/modules/cryptoAPI.jsm"
 );
+*/
 
 var gCreatedSetupIds = [];
 
 var EnigmailAutocrypt = {
   getKeyFromHeader(fromAddr, headerDataArr) {
     // critical parameters: {param: mandatory}
     const CRITICAL = {
       addr: true,
@@ -972,16 +976,17 @@ async function findUserRecord(connection
  * 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) {
@@ -1009,30 +1014,32 @@ async function appendUser(connection, pa
           EnigmailLog.DEBUG("autocrypt.jsm: appendUser - OK\n");
           resolve();
         })
         .catch(function() {
           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];
   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";
@@ -1109,59 +1116,64 @@ async function updateUser(connection, pa
     await updateKeyIfNeeded(
       paramsArr.addr.toLowerCase(),
       paramsArr.keydata,
       paramsArr.fpr,
       paramsArr.type,
       paramsArr["prefer-encrypt"]
     );
   }
+  */
 }
 
 /**
  * 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) {
   await EnigmailAutocrypt.applyKeyFromKeydata(
     atob(keydata),
     email,
     autocryptState,
     keyType
   );
 
   return true;
 }
+*/
 
 /**
  * Set the fpr attribute for a given key parameter object
  */
+/*
 async function getFprForKey(paramsArr) {
   let keyData = atob(paramsArr.keydata);
 
   const cApi = EnigmailCryptoAPI();
 
   try {
-    let keyArr = await cApi.getKeyListFromKeyBlock(keyData);
+    let keyArr = await cApi.getKeyListFromKeyBlockAPI(keyData, ... ?);
     if (!keyArr) {
       // callers can handle empty string for paramsArr.fpr
       return;
     }
 
     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() {
--- a/mail/extensions/openpgp/content/modules/cryptoAPI/GnuPGCryptoAPI.jsm
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/GnuPGCryptoAPI.jsm
@@ -99,33 +99,33 @@ 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) {
+  async importKeyBlockAPI(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
    *   - {String}          errorMsg:        human readable error message
    *   - {Number}          importSum:       total number of processed keys
    *   - {Number}          importUnchanged: number of unchanged keys
    */
-  async importKeyFromFile(inputFile) {
+  async importKeyFromFileAPI(inputFile) {
     let keys = await GnuPG_importKeyFromFile(inputFile);
     return keys;
   }
 
   /**
    * Export secret key(s) to a file
    *
    * @param {String}  keyId      Specification by fingerprint or keyID
@@ -252,17 +252,17 @@ class GnuPGCryptoAPI extends CryptoAPI {
 
     options.noOutput = true;
     options.verifyOnly = true;
     options.uiFlags = EnigmailConstants.UI_PGP_MIME;
 
     return this.decrypt(signed, options);
   }
 
-  async getKeyListFromKeyBlock(keyBlockStr) {
+  async getKeyListFromKeyBlockAPI(keyBlockStr) {
     let res;
     res = await getGpgKeyData(keyBlockStr);
     return res;
   }
 
   async genKey(userId, keyType, keySize, expiryTime, passphrase) {
     throw new Error("GnuPG genKey() not implemented");
   }
--- a/mail/extensions/openpgp/content/modules/cryptoAPI/RNPCryptoAPI.jsm
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/RNPCryptoAPI.jsm
@@ -84,36 +84,57 @@ class RNPCryptoAPI extends CryptoAPI {
    * @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) {
     throw new Error("Not implemented");
   }
 
-  async importKeyBlock(keyBlock, pubkey, seckey) {
+  async importKeyBlockAPI(keyBlock, pubkey, seckey, permissive) {
     // TODO: get status results
-    let res = await RNP.importKeyBlock(null, null, keyBlock, pubkey, seckey);
+    let res = await RNP.importKeyBlockImpl(
+      null,
+      null,
+      keyBlock,
+      pubkey,
+      seckey,
+      permissive
+    );
     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
    *   - {String}          errorMsg:        human readable error message
    */
-  async importKeyFromFile(win, passCB, inputFile, pubkey, seckey) {
+  async importKeyFromFileAPI(
+    win,
+    passCB,
+    inputFile,
+    pubkey,
+    seckey,
+    permissive
+  ) {
     var contents = EnigmailFiles.readFile(inputFile);
-    return RNP.importKeyBlock(win, passCB, contents, pubkey, seckey);
+    return RNP.importKeyBlockImpl(
+      win,
+      passCB,
+      contents,
+      pubkey,
+      seckey,
+      permissive
+    );
   }
 
   /**
    * 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
    *
@@ -233,18 +254,23 @@ class RNPCryptoAPI extends CryptoAPI {
     //options.uiFlags = EnigmailConstants.UI_PGP_MIME;
 
     if (!options.mimeSignatureFile) {
       throw new Error("inline verify not yet implemented");
     }
     return RNP.verifyDetached(signed, options);
   }
 
-  async getKeyListFromKeyBlock(keyBlockStr, pubkey, seckey) {
-    return RNP.getKeyListFromKeyBlock(keyBlockStr, pubkey, seckey);
+  async getKeyListFromKeyBlockAPI(keyBlockStr, pubkey, seckey, permissive) {
+    return RNP.getKeyListFromKeyBlockImpl(
+      keyBlockStr,
+      pubkey,
+      seckey,
+      permissive
+    );
   }
 
   async genKey(userId, keyType, keySize, expiryTime, passphrase) {
     let id = RNP.genKey(userId, keyType, keySize, expiryTime, passphrase);
     RNP.saveKeyRings();
     return id;
   }
 
--- a/mail/extensions/openpgp/content/modules/cryptoAPI/interface.js
+++ b/mail/extensions/openpgp/content/modules/cryptoAPI/interface.js
@@ -118,21 +118,21 @@ class CryptoAPI {
    *
    * @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) {
+  async importKeyFromFileAPI(inputFile) {
     return null;
   }
 
-  async importKeyBlock(keyBlock) {
+  async importKeyBlockAPI(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
@@ -249,17 +249,17 @@ class CryptoAPI {
    * @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) {
+  async getKeyListFromKeyBlockAPI(keyBlockStr) {
     return null;
   }
 
   /**
    * Create a new private key pair, including appropriate sub key pair,
    * and store the new keys in the default keyrings.
    *
    * @param {String} userId     User ID string, with name and email.
--- a/mail/extensions/openpgp/content/modules/decryption.jsm
+++ b/mail/extensions/openpgp/content/modules/decryption.jsm
@@ -554,17 +554,23 @@ var EnigmailDecryption = {
       if (
         EnigmailDialog.confirmDlg(
           parent,
           EnigmailLocale.getString("attachmentPgpKey", [displayName]),
           EnigmailLocale.getString("keyMan.button.import"),
           EnigmailLocale.getString("dlg.button.view")
         )
       ) {
-        let preview = EnigmailKey.getKeyListFromKeyBlock(byteData, errorMsgObj);
+        let preview = EnigmailKey.getKeyListFromKeyBlock(
+          byteData,
+          errorMsgObj,
+          true,
+          true,
+          false
+        );
         exitCodeObj.keyList = preview;
         let exitStatus = 0;
 
         if (preview && errorMsgObj.value === "") {
           if (preview.length > 0) {
             if (preview.length == 1) {
               exitStatus = EnigmailDialog.confirmDlg(
                 parent,
--- a/mail/extensions/openpgp/content/modules/key.jsm
+++ b/mail/extensions/openpgp/content/modules/key.jsm
@@ -140,17 +140,17 @@ var EnigmailKey = {
     const cApi = EnigmailCryptoAPI();
     let keyList;
     let key = {};
     let blocks;
     errorMsgObj.value = "";
 
     try {
       keyList = cApi.sync(
-        cApi.getKeyListFromKeyBlock(keyBlockStr, pubkey, seckey)
+        cApi.getKeyListFromKeyBlockAPI(keyBlockStr, pubkey, seckey, true)
       );
     } catch (ex) {
       errorMsgObj.value = ex.toString();
       return null;
     }
 
     if (!keyList) {
       return null;
--- a/mail/extensions/openpgp/content/modules/keyLookupHelper.jsm
+++ b/mail/extensions/openpgp/content/modules/keyLookupHelper.jsm
@@ -82,16 +82,18 @@ var KeyLookupHelper = {
     let somethingWasImported = false;
     let wkdKeys = await EnigmailWkdLookup.downloadKey(email);
     if (!wkdKeys) {
       console.debug("searchKeysOnInternet no wkd data for " + email);
     } else {
       let keyList = EnigmailKey.getKeyListFromKeyBlock(
         wkdKeys.keyData,
         {},
+        false,
+        true,
         false
       );
       if (!keyList) {
         EnigmailDialog.alert(window, EnigmailLocale.getString("previewFailed"));
       } else {
         somethingWasImported = EnigmailKeyRing.importKeyDataWithConfirmation(
           window,
           keyList,
--- a/mail/extensions/openpgp/content/modules/keyRing.jsm
+++ b/mail/extensions/openpgp/content/modules/keyRing.jsm
@@ -345,23 +345,49 @@ var EnigmailKeyRing = {
     seckey
   ) {
     EnigmailLog.DEBUG(
       "keyRing.jsm: EnigmailKeyRing.importKeyFromFile: fileName=" +
         inputFile.path +
         "\n"
     );
     const cApi = EnigmailCryptoAPI();
-    let res = cApi.sync(
-      cApi.importKeyFromFile(win, passCB, inputFile, pubkey, seckey)
-    );
+    let res;
+    let tryAgain;
+    let permissive = false;
+    do {
+      // strict on first attempt, permissive on optional second attempt
+      res = cApi.sync(
+        cApi.importKeyFromFileAPI(
+          win,
+          passCB,
+          inputFile,
+          pubkey,
+          seckey,
+          permissive
+        )
+      );
+
+      tryAgain = false;
+      let failed = res.exitCode || !res.importedKeys.length;
+      if (failed && !permissive) {
+        let agreed = getDialog().confirmDlg(
+          win,
+          EnigmailLocale.getString("confirmPermissiveImport")
+        );
+        if (agreed) {
+          permissive = true;
+          tryAgain = true;
+        }
+      }
+    } while (tryAgain);
+
     if (importedKeysObj) {
       importedKeysObj.keys = res.importedKeys;
     }
-
     if (!res) {
       return 1;
     }
 
     if (res.importedKeys.length > 0) {
       EnigmailKeyRing.updateKeys(res.importedKeys);
     }
     EnigmailKeyRing.clearCache();
@@ -624,32 +650,51 @@ var EnigmailKeyRing = {
       );
     }
 
     if (minimizeKey) {
       throw new Error("importKeyAsync with minimizeKey: not implemented");
     }
 
     const cApi = EnigmailCryptoAPI();
-    if (isBinary) {
-      cApi.sync(cApi.importKeyBlock(keyBlock, true, false)); // public only
-    } else {
-      cApi.sync(cApi.importKeyBlock(pgpBlock, true, false)); // public only
+    let result;
+    let tryAgain;
+    let permissive = false;
+    do {
+      // strict on first attempt, permissive on optional second attempt
+      if (isBinary) {
+        result = cApi.sync(
+          cApi.importKeyBlockAPI(keyBlock, true, false, permissive)
+        ); // public only
+      } else {
+        result = cApi.sync(
+          cApi.importKeyBlockAPI(pgpBlock, true, false, permissive)
+        ); // public only
+      }
+
+      tryAgain = false;
+      let failed = result.exitCode || !result.importedKeys.length;
+      if (failed && isInteractive && !permissive) {
+        let agreed = getDialog().confirmDlg(
+          parent,
+          EnigmailLocale.getString("confirmPermissiveImport")
+        );
+        if (agreed) {
+          permissive = true;
+          tryAgain = true;
+        }
+      }
+    } while (tryAgain);
+
+    if (importedKeysObj) {
+      importedKeysObj.value = result.importedKeys;
     }
 
-    if (!importedKeysObj) {
-      importedKeysObj = {};
-    }
-    importedKeysObj.value = [];
-
-    let exitCode = 0;
-
     EnigmailKeyRing.clearCache();
-
-    return exitCode;
+    return result.exitCode;
   },
 
   importKeyDataWithConfirmation(window, preview, keyData, isBinary) {
     let somethingWasImported = false;
     if (preview.length > 0) {
       let exitStatus;
       if (preview.length == 1) {
         exitStatus = getDialog().confirmDlg(
--- a/mail/extensions/openpgp/content/modules/keyserver.jsm
+++ b/mail/extensions/openpgp/content/modules/keyserver.jsm
@@ -1320,17 +1320,22 @@ const accessVksServer = {
         let r = await this.accessKeyServer(
           EnigmailConstants.SEARCH_KEY,
           keyserver,
           searchArr[i],
           listener
         );
 
         const cApi = EnigmailCryptoAPI();
-        let keyList = await cApi.getKeyListFromKeyBlock(r);
+        let keyList = await cApi.getKeyListFromKeyBlockAPI(
+          r,
+          true,
+          false,
+          true
+        );
         if (!keyList) {
           retObj.result = -1;
           // TODO: should we set retObj.errorDetails to a string?
           return retObj;
         }
 
         for (let k in keyList) {
           key = {
--- a/mail/extensions/openpgp/content/strings/enigmail.properties
+++ b/mail/extensions/openpgp/content/strings/enigmail.properties
@@ -628,8 +628,13 @@ importSettings.button.abortImport=&Abort
 
 cannotUseOwnKeyBecause=Unable to send the message, because there is a problem with your personal key. %S
 cannotEncryptBecauseMissing=Unable to send this message with end-to-end encryption, because there are problems with the keys of the following recipients: %S
 
 
 
 
 fileToBigToImport=This file is too big. Please don't import a large set of keys at once.
+
+
+
+
+confirmPermissiveImport=Import failed. The key you are trying to import might be corrupt or use unknown attributes. Would you like to attempt to import the parts that are correct? This might result in the import of incomplete and unusable keys.
--- a/mail/extensions/openpgp/content/ui/enigmailKeyManager.js
+++ b/mail/extensions/openpgp/content/ui/enigmailKeyManager.js
@@ -728,17 +728,24 @@ function enigmailImportFromClipbrd() {
       EnigGetString("keyMan.button.import")
     )
   ) {
     return;
   }
 
   var cBoardContent = enigGetClipboard();
   var errorMsgObj = {};
-  var preview = EnigmailKey.getKeyListFromKeyBlock(cBoardContent, errorMsgObj);
+  var preview = EnigmailKey.getKeyListFromKeyBlock(
+    cBoardContent,
+    errorMsgObj,
+    true,
+    true,
+    false
+  );
+  // should we allow importing secret keys?
   var exitStatus = -1;
 
   if (preview && preview.length > 0) {
     if (preview.length == 1) {
       exitStatus = EnigmailDialog.confirmDlg(
         window,
         EnigmailLocale.getString("doImportOne", [
           preview[0].name,
@@ -1053,17 +1060,24 @@ function enigmailImportKeysFromUrl() {
   if (
     EnigmailDialog.promptValue(window, EnigGetString("importFromUrl"), value)
   ) {
     var p = new Promise(function(resolve, reject) {
       var cbFunc = function(data) {
         EnigmailLog.DEBUG("enigmailImportKeysFromUrl: _cbFunc()\n");
         var errorMsgObj = {};
 
-        var preview = EnigmailKey.getKeyListFromKeyBlock(data, errorMsgObj);
+        var preview = EnigmailKey.getKeyListFromKeyBlock(
+          data,
+          errorMsgObj,
+          true,
+          true,
+          false
+        );
+        // should we allow importing secret keys?
         var exitStatus = -1;
 
         if (preview && preview.length > 0) {
           if (preview.length == 1) {
             exitStatus = EnigmailDialog.confirmDlg(
               window,
               EnigmailLocale.getString("doImportOne", [
                 preview[0].name,
@@ -1445,16 +1459,19 @@ var gKeyListView = {
   },
 
   getCellText(row, col) {
     let r = this.getFilteredRow(row);
     if (!r) {
       return "";
     }
     let keyObj = gKeyList[r.keyNum];
+    if (!keyObj) {
+      return "???";
+    }
 
     switch (r.rowType) {
       case "key":
         switch (col.id) {
           case "enigUserNameCol":
             return keyObj.userId;
           case "keyCol":
             return keyObj.keyId;
--- a/mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
+++ b/mail/extensions/openpgp/content/ui/enigmailMessengerOverlay.js
@@ -1772,17 +1772,23 @@ Enigmail.msg = {
     let keyDataB64 = document
       .getElementById("openpgpKeyBox")
       .getAttribute("keydata");
     if (!keyDataB64) {
       return;
     }
     let keyData = EnigmailData.decodeBase64(keyDataB64);
     let errorMsgObj = {};
-    let preview = EnigmailKey.getKeyListFromKeyBlock(keyData, errorMsgObj);
+    let preview = EnigmailKey.getKeyListFromKeyBlock(
+      keyData,
+      errorMsgObj,
+      true,
+      true,
+      false
+    );
     if (preview && errorMsgObj.value === "") {
       EnigmailKeyRing.importKeyDataWithConfirmation(
         window,
         preview,
         keyData,
         true
       );
     } else {
@@ -1817,17 +1823,23 @@ Enigmail.msg = {
     );
     if (!blockType || blockType !== "PUBLIC KEY BLOCK") {
       return;
     }
 
     let keyData = msgData.substring(beginIndexObj.value, endIndexObj.value);
 
     let errorMsgObj = {};
-    let preview = EnigmailKey.getKeyListFromKeyBlock(keyData, errorMsgObj);
+    let preview = EnigmailKey.getKeyListFromKeyBlock(
+      keyData,
+      errorMsgObj,
+      true,
+      true,
+      false
+    );
     if (preview && errorMsgObj.value === "") {
       EnigmailKeyRing.importKeyDataWithConfirmation(
         window,
         preview,
         keyData,
         false
       );
     } else {
@@ -2771,17 +2783,20 @@ Enigmail.msg = {
         errorMsgObj.value = EnigmailLocale.getString("noTempDir");
         return;
       }
     }
 
     if (callbackArg.actionType == "importKey") {
       var preview = EnigmailKey.getKeyListFromKeyBlock(
         callbackArg.data,
-        errorMsgObj
+        errorMsgObj,
+        true,
+        true,
+        false
       );
 
       if (errorMsgObj.value !== "" || !preview || preview.length === 0) {
         // try decrypting the attachment
         exitStatus = EnigmailDecryption.decryptAttachment(
           window,
           outFile,
           EnigmailMsgRead.getAttachmentName(callbackArg.attachment),
@@ -2790,17 +2805,20 @@ Enigmail.msg = {
           statusFlagsObj,
           errorMsgObj
         );
         if (exitStatus && exitCodeObj.value === 0) {
           // success decrypting, let's try again
           callbackArg.data = EnigmailFiles.readBinaryFile(outFile);
           preview = EnigmailKey.getKeyListFromKeyBlock(
             callbackArg.data,
-            errorMsgObj
+            errorMsgObj,
+            true,
+            true,
+            false
           );
         }
       }
 
       if (preview && errorMsgObj.value === "") {
         EnigmailKeyRing.importKeyDataWithConfirmation(
           window,
           preview,