Bug 1642795 - Ability to mark key pairs as "accepted personal keys" or not. r=PatrickBrunschwig a=wsmwk
authorKai Engert <kaie@kuix.de>
Wed, 10 Jun 2020 13:38:40 +0200
changeset 39454 b116b868d07f7114e9f559d41ce7172934bb3472
parent 39453 41f48cb292a0922d81aa27e90c4167eab01fb66c
child 39455 e766fd4f3eb4b31babda7cecea1626751aa78cd6
push id402
push userclokep@gmail.com
push dateMon, 29 Jun 2020 20:48:04 +0000
reviewersPatrickBrunschwig, wsmwk
bugs1642795
Bug 1642795 - Ability to mark key pairs as "accepted personal keys" or not. r=PatrickBrunschwig a=wsmwk Differential Revision: https://phabricator.services.mozilla.com/D80649
mail/extensions/am-e2e/am-e2e.js
mail/extensions/openpgp/content/modules/RNP.jsm
mail/extensions/openpgp/content/modules/autoSetup.jsm
mail/extensions/openpgp/content/modules/autocrypt.jsm
mail/extensions/openpgp/content/modules/encryption.jsm
mail/extensions/openpgp/content/modules/keyObj.jsm
mail/extensions/openpgp/content/modules/keyRing.jsm
mail/extensions/openpgp/content/modules/sqliteDb.jsm
mail/extensions/openpgp/content/modules/windows.jsm
mail/extensions/openpgp/content/strings/enigmail.ftl
mail/extensions/openpgp/content/strings/oneRecipientStatus.ftl
mail/extensions/openpgp/content/ui/composeKeyStatus.js
mail/extensions/openpgp/content/ui/enigmailMsgComposeOverlay.js
mail/extensions/openpgp/content/ui/keyDetailsDlg.js
mail/extensions/openpgp/content/ui/keyDetailsDlg.xhtml
mail/extensions/openpgp/content/ui/oneRecipientStatus.js
--- a/mail/extensions/am-e2e/am-e2e.js
+++ b/mail/extensions/am-e2e/am-e2e.js
@@ -215,17 +215,17 @@ function e2eInitializeFields() {
  * Initialize the OpenPGP settings, apply strings, and load the key radio UI.
  */
 async function initOpenPgpSettings() {
   if (!MailConstants.MOZ_OPENPGP || !BondOpenPGP.allDependenciesLoaded()) {
     return;
   }
 
   let result = {};
-  EnigmailKeyRing.getAllSecretKeysByEmail(gIdentity.email, result);
+  await EnigmailKeyRing.getAllSecretKeysByEmail(gIdentity.email, result, true);
 
   document.l10n.setAttributes(
     document.getElementById("openPgpgDescription"),
     "openpgp-description",
     {
       count: result.all.length,
       identity: gIdentity.email,
     }
@@ -597,17 +597,17 @@ function closeNotification() {
   document.getElementById("openPgpNotification").collapsed = true;
 }
 
 /**
  * Refresh the UI on init or after a successful OpenPGP Key generation.
  */
 async function reloadOpenPgpUI() {
   let result = {};
-  EnigmailKeyRing.getAllSecretKeysByEmail(gIdentity.email, result);
+  await EnigmailKeyRing.getAllSecretKeysByEmail(gIdentity.email, result, true);
 
   // Show the radiogroup only if the current identity has keys.
   document.getElementById("openPgpKeyList").collapsed = !result.all.length;
 
   // Interrupt and udpate the UI accordingly if no Key is associated with the
   // current identity.
   if (!result.all.length) {
     // Hide the selection status.
@@ -752,17 +752,17 @@ async function reloadOpenPgpUI() {
     let typeValueContainer = document.createXULElement("hbox");
     typeValueContainer.classList.add("input-container");
     typeValueContainer.setAttribute("flex", "1");
 
     let typeValue = document.createElement("input");
     typeValue.setAttribute("type", "text");
     typeValue.classList.add("plain");
     typeValue.setAttribute("readonly", "readonly");
-    typeValue.value = await document.l10n.formatValue("key-type-pair-2");
+    typeValue.value = await document.l10n.formatValue("key-type-pair");
 
     typeValueContainer.appendChild(typeValue);
 
     grid.appendChild(typeImage);
     grid.appendChild(typeLabel);
     grid.appendChild(typeValueContainer);
 
     // Key fingerprint.
--- a/mail/extensions/openpgp/content/modules/RNP.jsm
+++ b/mail/extensions/openpgp/content/modules/RNP.jsm
@@ -68,51 +68,36 @@ var RNP = {
     return RNP.libLoaded;
   },
 
   allDependenciesLoaded() {
     return RNP.libLoaded;
   },
 
   addKeyAttributes(handle, meta, 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();
 
-    if (RNPLib.rnp_key_have_secret(handle, have_secret.address())) {
-      throw new Error("rnp_key_have_secret failed");
-    }
-
-    keyObj.secretAvailable = have_secret.value;
+    keyObj.secretAvailable = this.getSecretAvailableFromHandle(handle);
 
     if (is_subkey) {
       keyObj.type = "sub";
     } else {
       keyObj.type = "pub";
     }
 
-    if (RNPLib.rnp_key_get_keyid(handle, key_id.address())) {
-      throw new Error("rnp_key_get_keyid failed");
-    }
-    keyObj.keyId = key_id.readString();
+    keyObj.keyId = this.getKeyIDFromHandle(handle);
     if (forListing) {
       keyObj.id = keyObj.keyId;
     }
-    RNPLib.rnp_buffer_destroy(key_id);
 
-    if (RNPLib.rnp_key_get_fprint(handle, fingerprint.address())) {
-      throw new Error("rnp_key_get_fprint failed");
-    }
-    keyObj.fpr = fingerprint.readString();
-    RNPLib.rnp_buffer_destroy(fingerprint);
+    keyObj.fpr = this.getFingerprintFromHandle(handle);
 
     if (RNPLib.rnp_key_get_alg(handle, algo.address())) {
       throw new Error("rnp_key_get_alg failed");
     }
     keyObj.algoSym = algo.readString();
     RNPLib.rnp_buffer_destroy(algo);
 
     if (RNPLib.rnp_key_get_bits(handle, bits.address())) {
@@ -165,40 +150,51 @@ var RNP = {
     }
     if (allowed.value) {
       keyObj.keyUseFor += "a";
       meta.a = true;
     }
   },
 
   async getKeys(onlyKeys = null) {
-    return this.getKeysFromFFI(RNPLib.ffi, false, onlyKeys);
+    return this.getKeysFromFFI(RNPLib.ffi, false, onlyKeys, false);
+  },
+
+  async getSecretKeys(onlyKeys = null) {
+    return this.getKeysFromFFI(RNPLib.ffi, false, onlyKeys, true);
   },
 
   /* Some consumers want a different listing of keys, and expect
    * slightly different attribute names...
    * If forListing is true, we'll set those additional attributes
    * If onlyKeys is given: only returns keys in that array
    */
-  async getKeysFromFFI(ffi, forListing, onlyKeys = null) {
+  async getKeysFromFFI(ffi, forListing, onlyKeys = null, onlySecret = false) {
+    if (!!onlyKeys && onlySecret) {
+      throw new Error(
+        "filtering by both white list and only secret keys isn't supported"
+      );
+    }
+
     let keys = [];
 
     if (onlyKeys) {
       for (let ki = 0; ki < onlyKeys.length; ki++) {
         let handle = await this.getKeyHandleByIdentifier(ffi, onlyKeys[ki]);
 
         let keyObj = {};
         try {
-          // Parameter false: skip if this is a primary key, it will be processed together with primary key later.
+          // Skip if it is a primary key, it will be processed together with primary key later.
           let ok = this.getKeyInfoFromHandle(
             ffi,
             handle,
             keyObj,
             false,
-            forListing
+            forListing,
+            false
           );
           if (!ok) {
             continue;
           }
         } catch (ex) {
           console.log(ex);
         } finally {
           RNPLib.rnp_key_handle_destroy(handle);
@@ -229,23 +225,24 @@ var RNP = {
 
         if (RNPLib.rnp_locate_key(ffi, "grip", grip, handle.address())) {
           throw new Error("rnp_locate_key failed");
         }
         have_handle = true;
 
         let keyObj = {};
         try {
-          // Parameter false: skip if this is a primary key, it will be processed together with primary key later.
+          // Skip if it is a primary key, it will be processed together with primary key later.
           let ok = this.getKeyInfoFromHandle(
             ffi,
             handle,
             keyObj,
             false,
-            forListing
+            forListing,
+            onlySecret
           );
           if (!ok) {
             continue;
           }
         } catch (ex) {
           console.log(ex);
         } finally {
           if (have_handle) {
@@ -259,18 +256,53 @@ var RNP = {
       }
 
       RNPLib.rnp_identifier_iterator_destroy(iter);
     }
 
     return keys;
   },
 
+  getFingerprintFromHandle(handle) {
+    let fingerprint = new ctypes.char.ptr();
+    if (RNPLib.rnp_key_get_fprint(handle, fingerprint.address())) {
+      throw new Error("rnp_key_get_fprint failed");
+    }
+    let result = fingerprint.readString();
+    RNPLib.rnp_buffer_destroy(fingerprint);
+    return result;
+  },
+
+  getKeyIDFromHandle(handle) {
+    let ctypes_key_id = new ctypes.char.ptr();
+    if (RNPLib.rnp_key_get_keyid(handle, ctypes_key_id.address())) {
+      throw new Error("rnp_key_get_keyid failed");
+    }
+    let result = ctypes_key_id.readString();
+    RNPLib.rnp_buffer_destroy(ctypes_key_id);
+    return result;
+  },
+
+  getSecretAvailableFromHandle(handle) {
+    let have_secret = new ctypes.bool();
+    if (RNPLib.rnp_key_have_secret(handle, have_secret.address())) {
+      throw new Error("rnp_key_have_secret failed");
+    }
+    return have_secret.value;
+  },
+
   // return false if handle refers to subkey and should be ignored
-  getKeyInfoFromHandle(ffi, handle, keyObj, usePrimaryIfSubkey, forListing) {
+  getKeyInfoFromHandle(
+    ffi,
+    handle,
+    keyObj,
+    usePrimaryIfSubkey,
+    forListing,
+    onlyIfSecret
+  ) {
     keyObj.ownerTrust = null;
     keyObj.userId = null;
     keyObj.userIds = [];
     keyObj.subKeys = [];
     keyObj.photoAvailable = false;
 
     let is_subkey = new ctypes.bool();
     let sub_count = new ctypes.size_t();
@@ -299,25 +331,36 @@ var RNP = {
             throw new Error("rnp_locate_key failed");
           }
           // recursively call ourselves to get primary key info
           rv = this.getKeyInfoFromHandle(
             ffi,
             newHandle,
             keyObj,
             false,
-            forListing
+            forListing,
+            onlyIfSecret
           );
           RNPLib.rnp_key_handle_destroy(newHandle);
         }
         RNPLib.rnp_buffer_destroy(primary_grip);
         return rv;
       }
     }
 
+    if (onlyIfSecret) {
+      let have_secret = new ctypes.bool();
+      if (RNPLib.rnp_key_have_secret(handle, have_secret.address())) {
+        throw new Error("rnp_key_have_secret failed");
+      }
+      if (!have_secret.value) {
+        return false;
+      }
+    }
+
     let meta = {
       a: false,
       s: false,
       c: false,
       e: false,
     };
     this.addKeyAttributes(handle, meta, keyObj, false, forListing);
 
@@ -436,17 +479,24 @@ var RNP = {
   },
 
   getKeySignatures(keyId, ignoreUnknownUid) {
     let handle = this.getKeyHandleByKeyIdOrFingerprint(
       RNPLib.ffi,
       "0x" + keyId
     );
     let mainKeyObj = {};
-    this.getKeyInfoFromHandle(RNPLib.ffi, handle, mainKeyObj, false, true);
+    this.getKeyInfoFromHandle(
+      RNPLib.ffi,
+      handle,
+      mainKeyObj,
+      false,
+      true,
+      false
+    );
 
     let rList = {};
 
     try {
       let uid_count = new ctypes.size_t();
       if (RNPLib.rnp_key_get_uid_count(handle, uid_count.address())) {
         throw new Error("rnp_key_get_uid_count failed");
       }
@@ -811,17 +861,17 @@ var RNP = {
 
     if (query_signer) {
       let key = new RNPLib.rnp_key_handle_t();
       if (RNPLib.rnp_op_verify_signature_get_key(sig, key.address())) {
         throw new Error("rnp_op_verify_signature_get_key");
       }
 
       let keyInfo = {};
-      let ok = this.getKeyInfoFromHandle(ffi, key, keyInfo, true, false);
+      let ok = this.getKeyInfoFromHandle(ffi, key, keyInfo, true, false, false);
       if (!ok) {
         throw new Error("getKeyInfoFromHandle failed");
       }
 
       let fromMatchesAnyUid = false;
       let fromLower = fromAddr ? fromAddr.toLowerCase() : "";
 
       for (let uid of keyInfo.userIds) {
@@ -836,17 +886,20 @@ var RNP = {
             break;
           }
         }
       }
 
       let useUndecided = true;
 
       if (keyInfo.secretAvailable) {
-        if (fromMatchesAnyUid) {
+        let isPersonal = await PgpSqliteDb2.isAcceptedAsPersonalKey(
+          keyInfo.fpr
+        );
+        if (isPersonal && fromMatchesAnyUid) {
           result.extStatusFlags |= EnigmailConstants.EXT_SELF_IDENTITY;
           useUndecided = false;
         } else {
           result.statusFlags |= EnigmailConstants.INVALID_RECIPIENT;
           useUndecided = true;
         }
       } else if (result.statusFlags & EnigmailConstants.GOOD_SIGNATURE) {
         if (!fromMatchesAnyUid) {
@@ -968,18 +1021,19 @@ var RNP = {
 
     RNPLib.rnp_input_destroy(input_from_memory);
     RNPLib.rnp_input_destroy(input_from_file);
     RNPLib.rnp_op_verify_destroy(verify_op);
 
     return result;
   },
 
-  genKey(userId, keyType, keyBits, expiryDays, passphrase) {
+  async genKey(userId, keyType, keyBits, expiryDays, passphrase) {
     let newKeyId = "";
+    let newKeyFingerprint = "";
 
     let primaryKeyType;
     let primaryKeyBits = 0;
     let subKeyType;
     let subKeyBits = 0;
     let primaryKeyCurve = null;
     let subKeyCurve = null;
     let expireSeconds = 0;
@@ -1040,22 +1094,18 @@ var RNP = {
 
     let primaryKey = new RNPLib.rnp_key_handle_t();
     if (RNPLib.rnp_op_generate_get_key(genOp, primaryKey.address())) {
       throw new Error("rnp_op_generate_get_key primary failed");
     }
 
     RNPLib.rnp_op_generate_destroy(genOp);
 
-    let ctypes_key_id = new ctypes.char.ptr();
-    if (RNPLib.rnp_key_get_keyid(primaryKey, ctypes_key_id.address())) {
-      throw new Error("rnp_key_get_keyid failed");
-    }
-    newKeyId = ctypes_key_id.readString();
-    RNPLib.rnp_buffer_destroy(ctypes_key_id);
+    newKeyFingerprint = this.getFingerprintFromHandle(primaryKey);
+    newKeyId = this.getKeyIDFromHandle(primaryKey);
 
     if (
       RNPLib.rnp_op_generate_subkey_create(
         genOp.address(),
         RNPLib.ffi,
         primaryKey,
         subKeyType
       )
@@ -1103,16 +1153,19 @@ var RNP = {
         lockFailure = true;
       }
     }
     if (lockFailure) {
       throw new Error("rnp_key_lock failed");
     }
 
     RNPLib.rnp_op_generate_destroy(genOp);
+    RNPLib.rnp_key_handle_destroy(primaryKey);
+
+    await PgpSqliteDb2.acceptAsPersonalKey(newKeyFingerprint);
 
     return newKeyId;
   },
 
   saveKeyRings() {
     RNPLib.saveKeys();
   },
 
@@ -1659,16 +1712,31 @@ var RNP = {
     }
 
     let senderKey = null;
     if (args.sign || args.encryptToSender) {
       senderKey = await this.getKeyHandleByIdentifier(RNPLib.ffi, args.sender);
       if (!senderKey || senderKey.isNull()) {
         return null;
       }
+      let isPersonal = false;
+      let senderKeySecretAvailable = this.getSecretAvailableFromHandle(
+        senderKey
+      );
+      if (senderKeySecretAvailable) {
+        let senderFpr = this.getFingerprintFromHandle(senderKey);
+        isPersonal = await PgpSqliteDb2.isAcceptedAsPersonalKey(senderFpr);
+      }
+      if (!isPersonal) {
+        throw new Error(
+          "configured sender key " +
+            args.sender +
+            " isn't accepted as a personal key"
+        );
+      }
       if (args.encryptToSender) {
         this.addSuitableEncryptKey(senderKey, op);
       }
       if (args.sign) {
         let use_sub = null;
         if (!this.isKeyUsableFor(senderKey, str_sign)) {
           use_sub = this.getSuitableSubkey(senderKey, str_sign);
           if (!use_sub) {
--- a/mail/extensions/openpgp/content/modules/autoSetup.jsm
+++ b/mail/extensions/openpgp/content/modules/autoSetup.jsm
@@ -411,37 +411,16 @@ var EnigmailAutoSetup = {
       } catch (ex) {
         EnigmailLog.DEBUG(
           "autoSetup.jsm: createAutocryptKey: error: " + ex.message
         );
         resolve(null);
       }
     });
   },
-
-  /**
-   * Configure Enigmail to use existing keys
-   */
-  applyExistingKeys() {
-    for (let id of MailServices.accounts.allIdentities) {
-      if (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
--- a/mail/extensions/openpgp/content/modules/autocrypt.jsm
+++ b/mail/extensions/openpgp/content/modules/autocrypt.jsm
@@ -25,30 +25,30 @@ const { EnigmailMime } = ChromeUtils.imp
 );
 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 = {
@@ -827,63 +827,16 @@ var EnigmailAutocrypt = {
   /**
    * Determine if a message id was self-created (only during same TB session)
    */
   isSelfCreatedSetupMessage(messageId) {
     return gCreatedSetupIds.includes(messageId);
   },
 
   /**
-   * 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(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)
    */
   async deleteUser(email, type) {
     EnigmailLog.DEBUG(`autocrypt.jsm: deleteUser(${email})\n`);
     let conn = await EnigmailSqliteDb.openDatabase();
 
     let updateStr =
--- a/mail/extensions/openpgp/content/modules/encryption.jsm
+++ b/mail/extensions/openpgp/content/modules/encryption.jsm
@@ -45,16 +45,19 @@ const { EnigmailKeyRing } = ChromeUtils.
   "chrome://openpgp/content/modules/keyRing.jsm"
 );
 const { EnigmailConstants } = ChromeUtils.import(
   "chrome://openpgp/content/modules/constants.jsm"
 );
 const { EnigmailCryptoAPI } = ChromeUtils.import(
   "chrome://openpgp/content/modules/cryptoAPI.jsm"
 );
+var { PgpSqliteDb2 } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/sqliteDb.jsm"
+);
 
 const gMimeHashAlgorithms = [
   null,
   "sha1",
   "ripemd160",
   "sha256",
   "sha384",
   "sha512",
@@ -334,91 +337,78 @@ var EnigmailEncryption = {
     console.debug(result);
     return result;
   },
 
   /**
    * 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
+   * @param fromKeyId:    String  - the sender 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(sendFlags, fromMailAddr) {
+  async determineOwnKeyUsability(sendFlags, fromKeyId) {
     EnigmailLog.DEBUG(
       "encryption.jsm: determineOwnKeyUsability: sendFlags=" +
         sendFlags +
         ", sender=" +
-        fromMailAddr +
+        fromKeyId +
         "\n"
     );
 
-    let keyList = [];
+    let foundKey = null;
     let ret = {
-      keyId: null,
       errorMsg: null,
     };
 
-    if (!fromMailAddr) {
+    if (!fromKeyId) {
       return ret;
     }
 
     let sign = !!(sendFlags & EnigmailConstants.SEND_SIGNED);
     let encrypt = !!(sendFlags & EnigmailConstants.SEND_ENCRYPTED);
 
-    if (fromMailAddr.search(/^(0x)?[A-Z0-9]+$/) === 0) {
+    if (fromKeyId.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);
+      foundKey = EnigmailKeyRing.getKeyById(fromKeyId);
     }
 
-    if (keyList.length === 0) {
-      ret.errorMsg = EnigmailLocale.getString(
-        "errorOwnKeyUnusable",
-        fromMailAddr
+    let isPersonalAndHasSecret = false;
+    if (foundKey && foundKey.secretAvailable) {
+      isPersonalAndHasSecret = await PgpSqliteDb2.isAcceptedAsPersonalKey(
+        foundKey.fpr
       );
+    }
+
+    if (!foundKey || !isPersonalAndHasSecret) {
+      ret.errorMsg = "key " + fromKeyId + " isn't usable as a personal key";
       return ret;
     }
 
+    let canSign = false;
+    let canEncrypt = false;
     if (sign) {
-      keyList = keyList.reduce(function(p, keyObj) {
-        if (keyObj && keyObj.getSigningValidity().keyValid) {
-          p.push(keyObj);
-        }
-        return p;
-      }, []);
+      if (foundKey && foundKey.getSigningValidity().keyValid) {
+        canSign = true;
+      }
     }
 
     if (encrypt) {
-      keyList = keyList.reduce(function(p, keyObj) {
-        if (keyObj && keyObj.getEncryptionValidity().keyValid) {
-          p.push(keyObj);
-        }
-        return p;
-      }, []);
+      if (foundKey && foundKey.getEncryptionValidity().keyValid) {
+        canEncrypt = true;
+      }
     }
 
-    if (keyList.length === 0) {
-      if (sign) {
-        ret.errorMsg = EnigmailErrorHandling.determineInvSignReason(
-          fromMailAddr
-        );
-      } else {
-        ret.errorMsg = EnigmailErrorHandling.determineInvRcptReason(
-          fromMailAddr
-        );
-      }
-    } else {
-      ret.keyId = keyList[0].fpr;
+    if (sign && !canSign) {
+      ret.errorMsg = EnigmailErrorHandling.determineInvSignReason(fromKeyId);
+    } else if (encrypt && !canEncrypt) {
+      ret.errorMsg = EnigmailErrorHandling.determineInvRcptReason(fromKeyId);
     }
 
     return ret;
   },
 
   encryptMessageStart(
     win,
     uiFlags,
@@ -440,30 +430,20 @@ var EnigmailEncryption = {
         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;
-    }
+    // This code used to call determineOwnKeyUsability, and return on
+    // failure. But now determineOwnKeyUsability is an async function,
+    // and calling it from here with await results in a deadlock.
+    // Instead we perform this check in Enigmail.msg.prepareSendMsg.
 
     var hashAlgo =
       gMimeHashAlgorithms[EnigmailPrefs.getPref("mimeHashAlgorithm")];
 
     if (hashAlgorithm) {
       hashAlgo = hashAlgorithm;
     }
 
--- a/mail/extensions/openpgp/content/modules/keyObj.jsm
+++ b/mail/extensions/openpgp/content/modules/keyObj.jsm
@@ -201,52 +201,37 @@ class EnigmailKeyObj {
     let f = EnigmailKey.formatFpr(this.fpr);
     if (f.length === 0) {
       f = this.fpr;
     }
     return f;
   }
 
   /**
-   * Is the function to set owner trust available for the key?
-   * Requirements: The key is signed with at least medium validity level,
-   * or the secret key is available.
-   *
-   * @return Boolean true if yes
-   */
-  isOwnerTrustUseful() {
-    if (this.secretAvailable) {
-      return true;
-    }
-    if (this.keyTrust.search(/^[fu]/) === 0) {
-      return true;
-    }
-
-    return false;
-  }
-
-  /**
    * Determine if the public key is valid. If not, return a description why it's not
    *
    * @return Object:
    *   - keyValid: Boolean (true if key is valid)
    *   - reason: String (explanation of invalidity)
    */
-  getPubKeyValidity() {
+  getPubKeyValidity(exceptionReason = null) {
     let retVal = {
       keyValid: false,
       reason: "",
     };
     if (this.keyTrust.search(/r/i) >= 0) {
       // public key revoked
       retVal.reason = l10n.formatValueSync("key-ring-pub-key-revoked", {
         userId: this.userId,
         keyId: "0x" + this.keyId,
       });
-    } else if (this.keyTrust.search(/e/i) >= 0) {
+    } else if (
+      exceptionReason != "ignoreExpired" &&
+      this.keyTrust.search(/e/i) >= 0
+    ) {
       // public key expired
       retVal.reason = l10n.formatValueSync("key-ring-pub-key-expired", {
         userId: this.userId,
         keyId: "0x" + this.keyId,
       });
     } else if (
       this.keyTrust.search(/d/i) >= 0 ||
       this.keyUseFor.search(/D/i) >= 0
@@ -271,18 +256,18 @@ class EnigmailKeyObj {
 
   /**
    * Check whether a key can be used for signing and return a description of why not
    *
    * @return Object:
    *   - keyValid: Boolean (true if key is valid)
    *   - reason: String (explanation of invalidity)
    */
-  getSigningValidity() {
-    let retVal = this.getPubKeyValidity();
+  getSigningValidity(exceptionReason = null) {
+    let retVal = this.getPubKeyValidity(exceptionReason);
 
     if (!retVal.keyValid) {
       return retVal;
     }
 
     if (!this.secretAvailable) {
       retVal.keyValid = false;
       retVal.reason = l10n.formatValueSync("key-ring-no-secret-key", {
@@ -317,17 +302,17 @@ class EnigmailKeyObj {
             } else {
               // found subkey usable
               ++found;
             }
           }
         }
 
         if (!found) {
-          if (expired) {
+          if (exceptionReason != "ignoreExpired" && expired) {
             retVal.reason = l10n.formatValueSync(
               "key-ring-sign-sub-keys-expired",
               {
                 userId: this.userId,
                 keyId: "0x" + this.keyId,
               }
             );
           } else if (revoked) {
@@ -367,18 +352,18 @@ class EnigmailKeyObj {
 
   /**
    * Check whether a key can be used for encryption and return a description of why not
    *
    * @return Object:
    *   - keyValid: Boolean (true if key is valid)
    *   - reason: String (explanation of invalidity)
    */
-  getEncryptionValidity() {
-    let retVal = this.getPubKeyValidity();
+  getEncryptionValidity(exceptionReason = null) {
+    let retVal = this.getPubKeyValidity(exceptionReason);
     if (!retVal.keyValid) {
       return retVal;
     }
 
     if (this.keyUseFor.search(/E/) < 0) {
       retVal.keyValid = false;
 
       if (this.keyTrust.search(/u/i) < 0) {
@@ -408,17 +393,17 @@ class EnigmailKeyObj {
             } else {
               // found subkey usable
               ++found;
             }
           }
         }
 
         if (!found) {
-          if (expired) {
+          if (exceptionReason != "ignoreExpired" && expired) {
             retVal.reason = l10n.formatValueSync(
               "key-ring-enc-sub-keys-expired",
               {
                 userId: this.userId,
                 keyId: "0x" + this.keyId,
               }
             );
           } else if (revoked) {
--- a/mail/extensions/openpgp/content/modules/keyRing.jsm
+++ b/mail/extensions/openpgp/content/modules/keyRing.jsm
@@ -100,62 +100,16 @@ var EnigmailKeyRing = {
         getSortFunction(sortColumn.toLowerCase(), gKeyListObj, sortDirection)
       );
     }
 
     return gKeyListObj;
   },
 
   /**
-   * get a list of all (valid, usable) keys that have a secret key
-   *
-   * @param Boolean onlyValidKeys: if true, only filter valid usable keys
-   *
-   * @return Array of KeyObjects containing the found keys (sorted by userId)
-   **/
-
-  getAllSecretKeys(onlyValidKeys = false) {
-    EnigmailLog.DEBUG("keyRing.jsm: getAllSecretKeys()\n");
-
-    let res = [];
-
-    this.getAllKeys(); // ensure keylist is loaded;
-
-    if (!onlyValidKeys) {
-      for (let key of gKeyListObj.keyList) {
-        if (key.secretAvailable) {
-          res.push(key);
-        }
-      }
-    } else {
-      for (let key of gKeyListObj.keyList) {
-        if (key.secretAvailable && key.keyUseFor.search(/D/) < 0) {
-          // key is not disabled and _usable_ for encryption signing and certification
-          if (
-            key.keyUseFor.search(/E/) >= 0 &&
-            key.keyUseFor.search(/S/) >= 0 &&
-            key.keyUseFor.search(/C/) >= 0
-          ) {
-            res.push(key);
-          }
-        }
-      }
-    }
-
-    res.sort(function(a, b) {
-      if (a.userId == b.userId) {
-        return a.keyId < b.keyId ? -1 : 1;
-      }
-      return a.userId.toLowerCase() < b.userId.toLowerCase() ? -1 : 1;
-    });
-
-    return res;
-  },
-
-  /**
    * get 1st key object that matches a given key ID or subkey ID
    *
    * @param keyId      - String: key Id with 16 characters (preferred) or 8 characters),
    *                             or fingerprint (40 or 32 characters).
    *                             Optionally preceeded with "0x"
    * @param noLoadKeys - Boolean [optional]: do not try to load the key list first
    *
    * @return Object - found KeyObject or null if key not found
@@ -189,17 +143,17 @@ var EnigmailKeyRing = {
    *
    * @param searchTerm   - String: a regular expression to match against all UIDs of the keys.
    *                               The search is always performed case-insensitively
    *                               An empty string will return no result
    * @param onlyValidUid - Boolean: if true (default), invalid (e.g. revoked) UIDs are not matched
    *
    * @return Array of KeyObjects with the found keys (array length is 0 if no key found)
    */
-  getKeysByUserId(searchTerm, onlyValidUid = true) {
+  getKeysByUserId(searchTerm, onlyValidUid = true, allowExpired = false) {
     EnigmailLog.DEBUG("keyRing.jsm: getKeysByUserId: '" + searchTerm + "'\n");
     let s = new RegExp(searchTerm, "i");
 
     let res = [];
 
     this.getAllKeys(); // ensure keylist is loaded;
 
     if (searchTerm === "") {
@@ -207,123 +161,117 @@ var EnigmailKeyRing = {
     }
     for (let i in gKeyListObj.keyList) {
       let k = gKeyListObj.keyList[i];
 
       for (let j in k.userIds) {
         if (k.userIds[j].type === "uid" && k.userIds[j].userId.search(s) >= 0) {
           if (
             !onlyValidUid ||
-            !EnigmailTrust.isInvalid(k.userIds[j].keyTrust)
+            !EnigmailTrust.isInvalid(k.userIds[j].keyTrust) ||
+            (allowExpired && k.userIds[j].keyTrust == "e")
           ) {
             res.push(k);
-            continue;
+            break;
           }
         }
       }
     }
     return res;
   },
 
   /**
-   * Specialized function for getSecretKeyByUserId() that takes into account
+   * Specialized function that takes into account
    * the specifics of email addresses in UIDs.
    *
    * @param emailAddr: String - email address to search for without any angulars
    *                            or names
    *
    * @return KeyObject with the found key, or null if no key found
    */
-  getSecretKeyByEmail(emailAddr) {
+  async getSecretKeyByEmail(emailAddr) {
     let result = {};
-    this.getSecretKeysByEmail(emailAddr, result);
+    await this.getAllSecretKeysByEmail(emailAddr, result, true);
     return result.best;
   },
 
-  getSecretKeysByEmail(emailAddr, result) {
-    // sanitize email address
-    emailAddr = emailAddr.replace(/([\.\[\]\-\\])/g, "\\$1");
-
-    let searchTerm =
-      "(<" + emailAddr + ">| " + emailAddr + "$|^" + emailAddr + "$)";
-
-    this.getAllSecretKeysByUserId(searchTerm, result);
-  },
-
   /**
    * Return the full unfiltered list of keys that are specifics of email
    * addresses in UIDs.
    *
    * @param {String} emailAddr - email address to search for without any
    *   angulars or names.
    *
    * @return {Object | null} - Object with the found keys, or null.
    */
-  getAllSecretKeysByEmail(emailAddr, result) {
-    // Sanitize email address.
+  async getAllSecretKeysByEmail(emailAddr, result, allowExpired) {
+    // sanitize email address
     emailAddr = emailAddr.replace(/([\.\[\]\-\\])/g, "\\$1");
 
     let searchTerm =
       "(<" + emailAddr + ">| " + emailAddr + "$|^" + emailAddr + "$)";
 
-    result.all = this.getKeysByUserId(searchTerm, false);
+    await this.getAllSecretKeysByUserId(searchTerm, result, allowExpired);
   },
 
-  /**
-   * get the "best" possible secret key for a given user ID
-   *
-   * @param searchTerm   - String: a regular expression to match against all UIDs of the keys.
-   *                               The search is always performed case-insensitively
-   * @return KeyObject with the found key, or null if no key found
-   */
-  getSecretKeyByUserId(searchTerm) {
-    let result = {};
-    this.getAllSecretKeysByUserId(searchTerm, result);
-    return result.best;
-  },
-
-  getAllSecretKeysByUserId(searchTerm, result) {
+  async getAllSecretKeysByUserId(searchTerm, result, allowExpired) {
     EnigmailLog.DEBUG(
-      "keyRing.jsm: getSecretKeyByUserId: '" + searchTerm + "'\n"
+      "keyRing.jsm: getAllSecretKeysByUserId: '" + searchTerm + "'\n"
     );
-    let keyList = this.getKeysByUserId(searchTerm, true);
+    let keyList = this.getKeysByUserId(searchTerm, true, true);
 
     result.all = [];
     result.best = null;
 
     var nowDate = new Date();
     var nowSecondsSinceEpoch = nowDate.valueOf() / 1000;
+    let bestIsExpired = false;
 
     for (let key of keyList) {
+      if (!key.secretAvailable) {
+        continue;
+      }
+      let isPersonal = await PgpSqliteDb2.isAcceptedAsPersonalKey(key.fpr);
+      if (!isPersonal) {
+        continue;
+      }
       if (
-        key.secretAvailable &&
-        key.getEncryptionValidity().keyValid &&
-        key.getSigningValidity().keyValid
+        key.getEncryptionValidity("ignoreExpired").keyValid &&
+        key.getSigningValidity("ignoreExpired").keyValid
       ) {
-        if (key.expiryTime != 0 && key.expiryTime < nowSecondsSinceEpoch) {
+        let thisIsExpired =
+          key.expiryTime != 0 && key.expiryTime < nowSecondsSinceEpoch;
+        if (!allowExpired && thisIsExpired) {
           continue;
         }
         result.all.push(key);
         if (!result.best) {
           result.best = key;
+          bestIsExpired = thisIsExpired;
         } else if (
           result.best.algoSym === key.algoSym &&
           result.best.keySize === key.keySize
         ) {
           if (!key.expiryTime || key.expiryTime > result.best.expiryTime) {
             result.best = key;
           }
-        } else if (
-          result.best.algoSym.search(/^(DSA|RSA)$/) < 0 &&
-          key.algoSym.search(/^(DSA|RSA)$/) === 0
-        ) {
-          // prefer RSA or DSA over ECC (long-term: change this once ECC keys are widely supported)
-          result.best = key;
-        } else if (key.getVirtualKeySize() > result.best.getVirtualKeySize()) {
-          result.best = key;
+        } else if (bestIsExpired && !thisIsExpired) {
+          if (
+            result.best.algoSym.search(/^(DSA|RSA)$/) < 0 &&
+            key.algoSym.search(/^(DSA|RSA)$/) === 0
+          ) {
+            // prefer RSA or DSA over ECC (long-term: change this once ECC keys are widely supported)
+            result.best = key;
+            bestIsExpired = thisIsExpired;
+          } else if (
+            key.getVirtualKeySize() > result.best.getVirtualKeySize()
+          ) {
+            result.best = key;
+            bestIsExpired = thisIsExpired;
+          }
         }
       }
     }
   },
 
   /**
    * get a list of keys for a given set of (sub-) key IDs
    *
@@ -852,17 +800,22 @@ var EnigmailKeyRing = {
       // key valid for encryption?
       if (!keyObj.keyUseFor.includes("E")) {
         //EnigmailLog.DEBUG("keyRing.jsm: getValidKeyForRecipient():  skip key " + keyObj.keyId + " (not provided for encryption)\n");
         continue; // not valid for encryption => **** CONTINUE the LOOP
       }
 
       let acceptanceLevel;
       if (keyObj.secretAvailable) {
-        acceptanceLevel = 3;
+        let isPersonal = await PgpSqliteDb2.isAcceptedAsPersonalKey(keyObj.fpr);
+        if (isPersonal) {
+          acceptanceLevel = 3;
+        } else {
+          acceptanceLevel = -1; // rejected
+        }
       } else {
         acceptanceLevel = await this.getKeyAcceptanceLevelForEmail(
           keyObj,
           emailAddr
         );
       }
 
       if (acceptanceLevel < 1) {
@@ -932,16 +885,17 @@ var EnigmailKeyRing = {
           break;
       }
     }
     return acceptanceLevel;
   },
 
   async getKeyAcceptanceForEmail(keyObj, email) {
     let acceptanceResult = {};
+
     try {
       await PgpSqliteDb2.getAcceptance(keyObj.fpr, email, acceptanceResult);
     } catch (ex) {
       console.debug("getAcceptance failed: " + ex);
       return null;
     }
 
     if (acceptanceResult.emailDecided) {
@@ -1016,17 +970,17 @@ var EnigmailKeyRing = {
             errMsg = "ProblemNoKey";
           }
           var detailsElem = {};
           detailsElem.addr = addr;
           detailsElem.msg = errMsg;
           details.errArray.push(detailsElem);
         }
         EnigmailLog.DEBUG(
-          'keyRing.jsm: doValidKeysForAllRecipients(): return null (no single valid key found for="' +
+          'keyRing.jsm: getValidKeysForAllRecipients(): return null (no single valid key found for="' +
             addr +
             '")\n'
         );
       }
     }
     return keyMissing;
   },
 
--- a/mail/extensions/openpgp/content/modules/sqliteDb.jsm
+++ b/mail/extensions/openpgp/content/modules/sqliteDb.jsm
@@ -165,40 +165,56 @@ var PgpSqliteDb2 = {
         );
 
         /* A key might contain multiple user IDs with the same email
          * address. We add each email only once. */
         let alreadyAdded = new Set();
         let insertObj = {
           fpr: fingerprint,
         };
-        for (let email of emailArray) {
-          if (!email) {
-            continue;
+        if (emailArray) {
+          for (let email of emailArray) {
+            if (!email) {
+              continue;
+            }
+            insertObj.email = email.toLowerCase();
+            if (alreadyAdded.has(insertObj.email)) {
+              continue;
+            }
+            alreadyAdded.add(insertObj.email);
+            await conn.execute(
+              "insert into acceptance_email values (:fpr, :email)",
+              insertObj
+            );
           }
-          insertObj.email = email.toLowerCase();
-          if (alreadyAdded.has(insertObj.email)) {
-            continue;
-          }
-          alreadyAdded.add(insertObj.email);
-          await conn.execute(
-            "insert into acceptance_email values (:fpr, :email)",
-            insertObj
-          );
         }
       }
       await conn.execute("commit transaction");
       await conn.close();
     } catch (ex) {
       console.debug(ex);
       if (conn) {
         await conn.close();
       }
     }
   },
+
+  async acceptAsPersonalKey(fingerprint) {
+    this.updateAcceptance(fingerprint, null, "personal");
+  },
+
+  async deletePersonalKeyAcceptance(fingerprint) {
+    this.deleteAcceptance(fingerprint);
+  },
+
+  async isAcceptedAsPersonalKey(fingerprint) {
+    let result = { fingerprintAcceptance: "" };
+    await this.getFingerprintAcceptance(null, fingerprint, result);
+    return result.fingerprintAcceptance === "personal";
+  },
 };
 
 var EnigmailSqliteDb = {
   /**
    * Provide an sqlite conection object asynchronously, retrying if needed
    *
    * @return {Promise<Sqlite Connection>}: the Sqlite database object
    */
--- a/mail/extensions/openpgp/content/modules/windows.jsm
+++ b/mail/extensions/openpgp/content/modules/windows.jsm
@@ -414,17 +414,17 @@ var EnigmailWindows = {
    *
    * @win        - |object| holding the parent window for the dialog
    * @keyId      - |string| containing the key ID (eg. "0x12345678")
    * @refresh    - |boolean| if true, cache is cleared and the key data is loaded from GnuPG
    *
    * @return  Boolean - true:  keylist needs to be refreshed
    *                  - false: no need to refresh keylist
    */
-  openKeyDetails(win, keyId, refresh) {
+  async openKeyDetails(win, keyId, refresh) {
     if (!win) {
       win = this.getBestParentWin();
     }
 
     keyId = keyId.replace(/^0x/, "");
 
     if (refresh) {
       EnigmailKeyRing.clearCache();
--- a/mail/extensions/openpgp/content/strings/enigmail.ftl
+++ b/mail/extensions/openpgp/content/strings/enigmail.ftl
@@ -182,16 +182,24 @@ openpgp-acceptance-label =
 openpgp-acceptance-rejected-label =
     .label = No, reject this key.
 openpgp-acceptance-undecided-label =
     .label = Not yet, maybe later.
 openpgp-acceptance-unverified-label =
     .label = Yes, but I have not verified that it is the correct key.
 openpgp-acceptance-verified-label =
     .label = Yes, I've verified in person this key has the correct fingerprint.
+key-accept-personal =
+    For this key, you have both the public and the secret part. You may use it as a personal key.
+    If this key was given to you by someone else, then don't use it as a personal key.
+key-personal-warning = Did you create this key yourself, and the displayed key ownership refers to yourself?
+openpgp-personal-no-label =
+    .label = No, don't use it as my personal key.
+openpgp-personal-yes-label =
+    .label = Yes, treat this key as a personal key.
 
 openpgp-copy-cmd-label =
     .label = Copy
 
 ## e2e encryption settings
 
 #   $count (Number) - the number of configured keys associated with the current identity
 #   $identity (String) - the email address of the currently selected identity
@@ -243,26 +251,26 @@ openpgp-key-revoke-title = Revoke Key
 openpgp-key-edit-title = Change OpenPGP Key
 
 openpgp-key-edit-date-title = Extend Expiration Date
 
 # Strings in keyDetailsDlg.xhtml
 key-type-public = public key
 key-type-primary = primary key
 key-type-subkey = subkey
-key-type-pair-2 = personal key (secret key and public key)
+key-type-pair = key pair (secret key and public key)
 key-expiry-never = never
 key-usage-encrypt = Encrypt
 key-usage-sign = Sign
 key-usage-certify = Certify
 key-usage-authentication = Authentication
 key-does-not-expire = Key does not expire
 key-expired = Key expired on { $keyExpiry }
+key-expired-simple = Key has expired
 key-revoked = Key was revoked
-key-auto-accept-personal = You accept this key for all uses, because it is one of your personal keys. You have the secret key.
 key-do-you-accept = Do you accept this key for verifying digital signatures and for encrypting messages?
 key-accept-warning = Avoid accepting a rogue key. Use a communication channel other than email to verify the fingerprint of your correspondent's key.
 
 # Strings enigmailMsgComposeOverlay.js
 cannot-use-own-key-because = Unable to send the message, because there is a problem with your personal key. { $problem }
 cannot-encrypt-because-missing = Unable to send this message with end-to-end encryption, because there are problems with the keys of the following recipients: { $problem }
 
 # Strings in mimeDecrypt.jsm
--- a/mail/extensions/openpgp/content/strings/oneRecipientStatus.ftl
+++ b/mail/extensions/openpgp/content/strings/oneRecipientStatus.ftl
@@ -16,14 +16,15 @@ openpgp-one-recipient-status-open-detail
     .label = Open details and edit acceptance…
 openpgp-one-recipient-status-discover =
     .label = Discover new or updated key
 
 openpgp-one-recipient-status-instruction1 = To send an end-to-end encrypted message to a recipient, you need to obtain their OpenPGP public key and mark it as accepted.
 openpgp-one-recipient-status-instruction2 = To obtain their public key, import them from email they have sent to you and that includes it. Alternatively, you can try to discover their public key on a directory.
 
 openpgp-key-own = Accepted (personal key)
+openpgp-key-secret-not-personal = Not usable
 openpgp-key-verified = Accepted (verified)
 openpgp-key-unverified = Accepted (unverifed)
 openpgp-key-undecided = Not accepted (undecided)
 openpgp-key-rejected = Not accepted (rejected)
 
 openpgp-intro = Available public keys for { $key }
--- a/mail/extensions/openpgp/content/ui/composeKeyStatus.js
+++ b/mail/extensions/openpgp/content/ui/composeKeyStatus.js
@@ -10,16 +10,19 @@ var EnigmailKeyRing = ChromeUtils.import
   "chrome://openpgp/content/modules/keyRing.jsm"
 ).EnigmailKeyRing;
 var { EnigmailWindows } = ChromeUtils.import(
   "chrome://openpgp/content/modules/windows.jsm"
 );
 var { EnigmailKey } = ChromeUtils.import(
   "chrome://openpgp/content/modules/key.jsm"
 );
+const { PgpSqliteDb2 } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/sqliteDb.jsm"
+);
 
 var gListBox;
 var gViewButton;
 
 var gEmailAddresses = [];
 var gRowToEmail = [];
 var gMapAddressToKeyObjs = null;
 
@@ -45,18 +48,22 @@ async function setListEntries() {
   for (let addr of gEmailAddresses) {
     let emailStatus = null;
 
     let foundKeys = gMapAddressToKeyObjs.get(addr);
     if (!foundKeys || !foundKeys.length) {
       emailStatus = "openpgp-recip-missing";
     } else {
       for (let keyObj of foundKeys) {
+        let goodPersonal = false;
+        if (keyObj.secretAvailable) {
+          goodPersonal = await PgpSqliteDb2.isAcceptedAsPersonalKey(keyObj.fpr);
+        }
         if (
-          keyObj.secretAvailable ||
+          goodPersonal ||
           keyObj.acceptance == "verified" ||
           keyObj.acceptance == "unverified"
         ) {
           emailStatus = "openpgp-recip-good";
           break;
         }
       }
       if (!emailStatus) {
--- a/mail/extensions/openpgp/content/ui/enigmailMsgComposeOverlay.js
+++ b/mail/extensions/openpgp/content/ui/enigmailMsgComposeOverlay.js
@@ -2223,17 +2223,17 @@ Enigmail.msg = {
         ? "cannotSendEncBecauseNoOwnKey"
         : "cannotSendSigBecauseNoOwnKey";
       let fullAlert = EnigmailLocale.getString(msgId, [this.identity.email]);
       EnigmailDialog.alert(window, fullAlert);
       return false;
     }
 
     if (senderKeyId) {
-      let senderKeyUsable = EnigmailEncryption.determineOwnKeyUsability(
+      let senderKeyUsable = await EnigmailEncryption.determineOwnKeyUsability(
         sendFlags,
         senderKeyId
       );
       if (senderKeyUsable.errorMsg) {
         let fullAlert = await document.l10n.formatValue(
           "cannot-use-own-key-because",
           {
             problem: senderKeyUsable.errorMsg,
--- a/mail/extensions/openpgp/content/ui/keyDetailsDlg.js
+++ b/mail/extensions/openpgp/content/ui/keyDetailsDlg.js
@@ -21,186 +21,207 @@
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 var { uidHelper } = ChromeUtils.import(
   "chrome://openpgp/content/modules/uidHelper.jsm"
 );
 
 var l10n = new Localization(["messenger/openpgp/enigmail.ftl"], true);
 
+var gModePersonal = false;
+
 var gKeyId = null;
 var gUserId = null;
 var gKeyList = null;
 var gTreeFuncs = null;
 
 var gAllEmails = [];
 var gFingerprint = "";
 
 var gAcceptanceRadio = null;
+var gPersonalRadio = null;
+
 var gOriginalAcceptance;
+var gOriginalPersonal;
 var gUpdateAllowed = false;
 
 async function onLoad() {
   if (window.arguments[1]) {
     window.arguments[1].refresh = false;
   }
 
   gAcceptanceRadio = document.getElementById("acceptanceRadio");
+  gPersonalRadio = document.getElementById("personalRadio");
 
   gKeyId = window.arguments[0].keyId;
 
   let accept = document
     .getElementById("enigmailKeyDetailsDlg")
     .getButton("accept");
   accept.focus();
 
-  await reloadData();
+  await reloadData(true);
 }
 
 /***
  * Set the label text of a HTML element
  */
 function setLabel(elementId, label) {
   let node = document.getElementById(elementId);
   node.setAttribute("value", label);
 }
 
-async function reloadData() {
+async function reloadData(firstLoad) {
+  // TODO: once we support firstLoad==false, be sure to change the
+  // code below, and don't update the "original" variables on
+  // subsequent calls.
+
   var enigmailSvc = GetEnigmailSvc();
   if (!enigmailSvc) {
     throw new Error("GetEnigmailSvc failed");
   }
 
   gUserId = null;
 
   var treeChildren = document.getElementById("keyListChildren");
   var uidList = document.getElementById("additionalUid");
 
   // clean lists
   EnigCleanGuiList(treeChildren);
   EnigCleanGuiList(uidList);
 
   let keyObj = EnigmailKeyRing.getKeyById(gKeyId);
-  if (keyObj) {
-    let keyIsExpired =
-      keyObj.expiryTime && keyObj.expiryTime < Math.floor(Date.now() / 1000);
+  if (!keyObj) {
+    return;
+  }
+
+  let acceptanceIntro1Text = "";
+  let acceptanceIntro2Text = "";
+
+  if (keyObj.fpr) {
+    gFingerprint = keyObj.fpr;
+    setLabel("fingerprint", EnigmailKey.formatFpr(keyObj.fpr));
+  }
 
-    let acceptanceIntroText = "";
-    let acceptanceWarningText = "";
-    if (keyObj.secretAvailable) {
-      document.getElementById("ownKeyCommands").removeAttribute("hidden");
-      acceptanceIntroText = "key-auto-accept-personal";
-      let value = await document.l10n.formatValue("key-type-pair-2");
-      setLabel("keyType", value);
-    } else {
-      document.getElementById("ownKeyCommands").setAttribute("hidden", "true");
-      let value = await document.l10n.formatValue("key-type-public");
-      setLabel("keyType", value);
+  if (keyObj.hasSubUserIds()) {
+    document.getElementById("alsoknown").removeAttribute("collapsed");
+    createUidData(uidList, keyObj);
+  } else {
+    document.getElementById("alsoknown").setAttribute("collapsed", "true");
+  }
+
+  if (keyObj.signatures) {
+    let sigListViewObj = new SigListView(keyObj);
+    let tree = document.getElementById("signatures_tree");
+    tree.view = sigListViewObj;
+    gTreeFuncs = EnigmailCompat.getTreeCompatibleFuncs(tree, sigListViewObj);
+  }
+
+  let subkeyListViewObj = new SubkeyListView(keyObj);
+  document.getElementById("subkeyList").view = subkeyListViewObj;
+
+  gUserId = keyObj.userId;
 
-      let isStillValid = !(
-        keyObj.keyTrust == "r" ||
-        keyObj.keyTrust == "e" ||
-        keyIsExpired
-      );
-      if (isStillValid) {
-        document
-          .getElementById("acceptanceRadio")
-          .setAttribute("hidden", "false");
-        acceptanceIntroText = "key-do-you-accept";
-        acceptanceWarningText = "key-accept-warning";
-        gUpdateAllowed = true;
+  let splitUid = {};
+  uidHelper.getPartsFromUidStr(keyObj.userId, splitUid);
+  if (splitUid.email) {
+    gAllEmails.push(splitUid.email);
+  }
+
+  setLabel("userId", gUserId);
+  setLabel("keyCreated", keyObj.created);
+
+  let keyIsExpired =
+    keyObj.expiryTime && keyObj.expiryTime < Math.floor(Date.now() / 1000);
 
-        let acceptanceResult = {};
-        await PgpSqliteDb2.getFingerprintAcceptance(
-          null,
-          keyObj.fpr,
-          acceptanceResult
-        );
-
-        if (
-          "fingerprintAcceptance" in acceptanceResult &&
-          acceptanceResult.fingerprintAcceptance != "undecided"
-        ) {
-          gOriginalAcceptance = acceptanceResult.fingerprintAcceptance;
-        } else {
-          gOriginalAcceptance = "undecided";
-        }
-        gAcceptanceRadio.value = gOriginalAcceptance;
-      }
-    }
-
-    if (keyObj.hasSubUserIds()) {
-      document.getElementById("alsoknown").removeAttribute("collapsed");
-      createUidData(uidList, keyObj);
-    } else {
-      document.getElementById("alsoknown").setAttribute("collapsed", "true");
-    }
+  let expiryInfo;
+  let expireArgument = null;
+  let expiryInfoKey = "";
+  if (keyObj.keyTrust == "r") {
+    expiryInfoKey = "key-revoked";
+  } else if (keyObj.keyTrust == "e" || keyIsExpired) {
+    expiryInfoKey = "key-expired";
+    expireArgument = keyObj.expiry;
+  } else if (keyObj.expiry.length === 0) {
+    expiryInfoKey = "key-does-not-expire";
+  } else {
+    expiryInfo = keyObj.expiry;
+  }
+  if (expiryInfoKey) {
+    expiryInfo = l10n.formatValueSync(expiryInfoKey, {
+      keyExpiry: expireArgument,
+    });
+  }
+  setLabel("keyExpiry", expiryInfo);
 
-    if (keyObj.signatures) {
-      let sigListViewObj = new SigListView(keyObj);
-      let tree = document.getElementById("signatures_tree");
-      tree.view = sigListViewObj;
-      gTreeFuncs = EnigmailCompat.getTreeCompatibleFuncs(tree, sigListViewObj);
-    }
-
-    let subkeyListViewObj = new SubkeyListView(keyObj);
-    document.getElementById("subkeyList").view = subkeyListViewObj;
+  gModePersonal = keyObj.secretAvailable;
+  if (gModePersonal) {
+    gPersonalRadio.removeAttribute("hidden");
+    gAcceptanceRadio.setAttribute("hidden", "true");
+    document.getElementById("ownKeyCommands").removeAttribute("hidden");
+    acceptanceIntro1Text = "key-accept-personal";
+    acceptanceIntro2Text = "key-personal-warning";
+    let value = l10n.formatValueSync("key-type-pair");
+    setLabel("keyType", value);
 
-    gUserId = keyObj.userId;
-
-    let splitUid = {};
-    uidHelper.getPartsFromUidStr(keyObj.userId, splitUid);
-    if (splitUid.email) {
-      gAllEmails.push(splitUid.email);
-    }
-
-    setLabel("userId", gUserId);
-    setLabel("keyCreated", keyObj.created);
+    gUpdateAllowed = true;
+    gOriginalPersonal = await PgpSqliteDb2.isAcceptedAsPersonalKey(keyObj.fpr);
+    gPersonalRadio.value = gOriginalPersonal ? "personal" : "not_personal";
+  } else {
+    gPersonalRadio.setAttribute("hidden", "true");
+    document.getElementById("ownKeyCommands").setAttribute("hidden", "true");
+    let value = l10n.formatValueSync("key-type-public");
+    setLabel("keyType", value);
 
-    let expiryInfo;
-    let expireArgument = null;
-    let l10nUse = true;
-    if (keyObj.keyTrust == "r") {
-      expiryInfo = "key-revoked";
-      acceptanceIntroText = expiryInfo;
-    } else if (keyObj.keyTrust == "e" || keyIsExpired) {
-      expiryInfo = "key-expired";
-      expireArgument = keyObj.expiry;
-      acceptanceIntroText = expiryInfo;
-    } else if (keyObj.expiry.length === 0) {
-      expiryInfo = "key-does-not-expire";
+    let isStillValid = !(
+      keyObj.keyTrust == "r" ||
+      keyObj.keyTrust == "e" ||
+      keyIsExpired
+    );
+    if (!isStillValid) {
+      gAcceptanceRadio.setAttribute("hidden", "true");
+      if (keyObj.keyTrust == "r") {
+        acceptanceIntro1Text = "key-revoked";
+      } else if (keyObj.keyTrust == "e" || keyIsExpired) {
+        acceptanceIntro1Text = "key-expired-simple";
+      }
     } else {
-      expiryInfo = keyObj.expiry;
-      l10nUse = false;
-    }
+      gAcceptanceRadio.removeAttribute("hidden");
+      acceptanceIntro1Text = "key-do-you-accept";
+      acceptanceIntro2Text = "key-accept-warning";
+      gUpdateAllowed = true;
+
+      //await RNP.calculateAcceptance(keyObj.keyId, null);
 
-    if (acceptanceIntroText) {
-      let acceptanceIntro = document.getElementById("acceptanceIntro");
-      document.l10n.setAttributes(acceptanceIntro, acceptanceIntroText);
+      let acceptanceResult = {};
+      await PgpSqliteDb2.getFingerprintAcceptance(
+        null,
+        keyObj.fpr,
+        acceptanceResult
+      );
+
+      if (
+        "fingerprintAcceptance" in acceptanceResult &&
+        acceptanceResult.fingerprintAcceptance != "undecided"
+      ) {
+        gOriginalAcceptance = acceptanceResult.fingerprintAcceptance;
+      } else {
+        gOriginalAcceptance = "undecided";
+      }
+      gAcceptanceRadio.value = gOriginalAcceptance;
     }
-
-    if (acceptanceWarningText) {
-      let acceptanceExplanation = document.getElementById(
-        "acceptanceExplanation"
-      );
-      document.l10n.setAttributes(acceptanceExplanation, acceptanceWarningText);
-    }
+  }
+  if (acceptanceIntro1Text) {
+    let acceptanceIntro1 = document.getElementById("acceptanceIntro1");
+    document.l10n.setAttributes(acceptanceIntro1, acceptanceIntro1Text);
+  }
 
-    if (l10nUse) {
-      let keyExpiryNode = document.getElementById("keyExpiry");
-      document.l10n.setAttributes(keyExpiryNode, expiryInfo, {
-        keyExpiry: expireArgument,
-      });
-    } else {
-      setLabel("keyExpiry", expiryInfo);
-    }
-    if (keyObj.fpr) {
-      gFingerprint = keyObj.fpr;
-      setLabel("fingerprint", EnigmailKey.formatFpr(keyObj.fpr));
-    }
+  if (acceptanceIntro2Text) {
+    let acceptanceIntro2 = document.getElementById("acceptanceIntro2");
+    document.l10n.setAttributes(acceptanceIntro2, acceptanceIntro2Text);
   }
 }
 
 function createUidData(listNode, keyDetails) {
   for (let i = 1; i < keyDetails.userIds.length; i++) {
     if (keyDetails.userIds[i].type === "uid") {
       let item = listNode.appendItem(keyDetails.userIds[i].userId);
       item.setAttribute("label", keyDetails.userIds[i].userId);
@@ -212,23 +233,25 @@ function createUidData(listNode, keyDeta
       uidHelper.getPartsFromUidStr(keyDetails.userIds[i].userId, splitUid);
       if (splitUid.email) {
         gAllEmails.push(splitUid.email);
       }
     }
   }
 }
 
+/*
 function getTrustLabel(trustCode) {
   var trustTxt = EnigGetTrustLabel(trustCode);
   if (trustTxt == "-" || trustTxt.length === 0) {
     return l10n.formatValueSync("key-valid-unknown");
   }
   return trustTxt;
 }
+*/
 
 function setAttr(attribute, value) {
   var elem = document.getElementById(attribute);
   if (elem) {
     elem.value = value;
   }
 }
 
@@ -237,24 +260,24 @@ function enableRefresh() {
 }
 
 // ------------------ onCommand Functions  -----------------
 
 /*
 function signKey() {
   if (EnigSignKey(gUserId, gKeyId, null)) {
     enableRefresh();
-    reloadData();
+    reloadData(false);
   }
 }
 
 function changeExpirationDate() {
   if (EnigEditKeyExpiry([gUserId], [gKeyId])) {
     enableRefresh();
-    reloadData();
+    reloadData(false);
   }
 }
 */
 
 /*
 function manageUids() {
   let keyObj = EnigmailKeyRing.getKeyById(gKeyId);
 
@@ -270,33 +293,33 @@ function manageUids() {
     "chrome://openpgp/content/ui/enigmailManageUidDlg.xhtml",
     "",
     "dialog,modal,centerscreen,resizable=yes",
     inputObj,
     resultObj
   );
   if (resultObj.refresh) {
     enableRefresh();
-    reloadData();
+    reloadData(false);
   }
 }
 */
 
 /*
 function changePassword() {
   EnigChangeKeyPwd(gKeyId, gUserId);
 }
 */
 
 async function revokeKey() {
   /*
   EnigRevokeKey(gKeyId, gUserId, function(success) {
     if (success) {
       enableRefresh();
-      await reloadData();
+      await reloadData(false);
     }
   });
   */
 }
 
 function genRevocationCert() {
   EnigCreateRevokeCert(gKeyId, gUserId);
 }
@@ -629,27 +652,36 @@ SubkeyListView.prototype = {
   },
 
   toggleOpenState(row) {},
 };
 
 function sigHandleDblClick(event) {}
 
 async function onAccept() {
-  if (gUpdateAllowed && gAcceptanceRadio.value != gOriginalAcceptance) {
-    enableRefresh();
+  if (gModePersonal) {
+    if (gUpdateAllowed && gPersonalRadio.value != gOriginalPersonal) {
+      enableRefresh();
 
-    const cApi = EnigmailCryptoAPI();
-    cApi.sync(
-      PgpSqliteDb2.updateAcceptance(
+      if (gPersonalRadio.value == "personal") {
+        await PgpSqliteDb2.acceptAsPersonalKey(gFingerprint);
+      } else {
+        await PgpSqliteDb2.deletePersonalKeyAcceptance(gFingerprint);
+      }
+    }
+  } else if (gUpdateAllowed) {
+    if (gAcceptanceRadio.value != gOriginalAcceptance) {
+      enableRefresh();
+
+      await PgpSqliteDb2.updateAcceptance(
         gFingerprint,
         gAllEmails,
         gAcceptanceRadio.value
-      )
-    );
+      );
+    }
   }
   return true;
 }
 
 document.addEventListener("dialogaccept", async function(event) {
   let result = await onAccept();
   if (!result) {
     event.preventDefault();
--- a/mail/extensions/openpgp/content/ui/keyDetailsDlg.xhtml
+++ b/mail/extensions/openpgp/content/ui/keyDetailsDlg.xhtml
@@ -12,17 +12,17 @@
 <!DOCTYPE window [
 <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
 %brandDTD;
 ]>
 
 <window data-l10n-id="openpgp-key-details-title"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml"
-        style="width:60em;"
+        style="min-width:60em; max-width:90em; min-height:40em"
         persist="width height"
         onload="onLoad();">
 <dialog id="enigmailKeyDetailsDlg"
         buttons="accept"
         data-l10n-id="openpgp-card-details-close-window-label">
 
   <script type="application/x-javascript" src="chrome://openpgp/content/ui/enigmailCommon.js"/>
   <script type="application/x-javascript" src="chrome://openpgp/content/ui/keyDetailsDlg.js"/>
@@ -87,31 +87,37 @@
     <tabs id="mainTabBox">
       <tab id="acceptanceTab" data-l10n-id="openpgp-acceptance-label"/>
       <tab id="signaturesTab" data-l10n-id="openpgp-key-details-signatures-tab"/>
       <tab id="structureTab" data-l10n-id="openpgp-key-details-structure-tab"/>
     </tabs>
 
     <tabpanels flex="1" id="mainTabPanel">
        <!-- Acceptance Tab -->
-      <vbox id="acceptancePanel">
-        <description id="acceptanceIntro"/>
+      <vbox id="acceptancePanel" flex="1">
+        <description id="acceptanceIntro1"/>
         <separator class="thin"/>
-        <description id="acceptanceExplanation"/>
+        <description id="acceptanceIntro2"/>
         <separator class="thin"/>
         <radiogroup id="acceptanceRadio" hidden="true">
           <radio id="acceptRejected" value="rejected"
                  data-l10n-id="openpgp-acceptance-rejected-label"/>
           <radio id="acceptUndecided" value="undecided"
                  data-l10n-id="openpgp-acceptance-undecided-label"/>
           <radio id="acceptUnverified" value="unverified"
                  data-l10n-id="openpgp-acceptance-unverified-label"/>
           <radio id="acceptVerified" value="verified"
                  data-l10n-id="openpgp-acceptance-verified-label"/>
         </radiogroup>
+        <radiogroup id="personalRadio" hidden="true">
+          <radio id="notPersonal" value="not_personal"
+                 data-l10n-id="openpgp-personal-no-label"/>
+          <radio id="yesPersonal" value="personal"
+                 data-l10n-id="openpgp-personal-yes-label"/>
+        </radiogroup>
       </vbox>
 
       <!-- certifications tab -->
       <vbox id="signaturesPanel">
         <tree id="signatures_tree" flex="1"
               hidecolumnpicker="true"
               ondblclick="sigHandleDblClick(event)">
 
--- a/mail/extensions/openpgp/content/ui/oneRecipientStatus.js
+++ b/mail/extensions/openpgp/content/ui/oneRecipientStatus.js
@@ -13,16 +13,19 @@ var { EnigmailWindows } = ChromeUtils.im
   "chrome://openpgp/content/modules/windows.jsm"
 );
 var { EnigmailKey } = ChromeUtils.import(
   "chrome://openpgp/content/modules/key.jsm"
 );
 var KeyLookupHelper = ChromeUtils.import(
   "chrome://openpgp/content/modules/keyLookupHelper.jsm"
 ).KeyLookupHelper;
+const { PgpSqliteDb2 } = ChromeUtils.import(
+  "chrome://openpgp/content/modules/sqliteDb.jsm"
+);
 
 var gListBox;
 var gViewButton;
 
 var gAddr;
 var gRowToKey = [];
 
 async function setListEntries(keys = null) {
@@ -38,17 +41,21 @@ async function setListEntries(keys = nul
     let keyId = document.createXULElement("label");
     keyId.setAttribute("value", "0x" + keyObj.keyId);
     keyId.setAttribute("crop", "end");
     keyId.setAttribute("style", "width: var(--keyWidth)");
     listitem.appendChild(keyId);
 
     let acceptanceText;
     if (keyObj.secretAvailable) {
-      acceptanceText = "openpgp-key-own";
+      if (await PgpSqliteDb2.isAcceptedAsPersonalKey(keyObj.fpr)) {
+        acceptanceText = "openpgp-key-own";
+      } else {
+        acceptanceText = "openpgp-key-secret-not-personal";
+      }
     } else {
       if (!("acceptance" in keyObj)) {
         throw new Error(
           "expected getMultValidKeysForMultRecipients to set acceptance"
         );
       }
       switch (keyObj.acceptance) {
         case "rejected":