Bug 1599031 - Protect keyring using an automatic password that's protected with the master password. r=patrick DONTBUILD
authorKai Engert <kaie@kuix.de>
Wed, 27 Nov 2019 11:17:34 +0100
changeset 37611 5629275db993996f84626ca8ddbb5296fb42f8f5
parent 37610 01810b29d5ec858b90d01ce47999cccae993ae3e
child 37612 388b8b71c9cf8b7a0e50812c2acfc3d0d9f864da
push id396
push userclokep@gmail.com
push dateMon, 06 Jan 2020 23:11:57 +0000
reviewerspatrick
bugs1599031
Bug 1599031 - Protect keyring using an automatic password that's protected with the master password. r=patrick DONTBUILD
mail/extensions/openpgp/content/modules/decryption.jsm
mail/extensions/openpgp/content/modules/masterpass.jsm
mail/extensions/openpgp/content/modules/rnp.jsm
mail/extensions/openpgp/content/modules/rnpLib.jsm
mail/extensions/openpgp/content/ui/enigmailCommon.js
mail/extensions/openpgp/content/ui/enigmailKeygen.js
mail/extensions/openpgp/content/ui/enigmailKeygen.xul
--- a/mail/extensions/openpgp/content/modules/decryption.jsm
+++ b/mail/extensions/openpgp/content/modules/decryption.jsm
@@ -225,16 +225,17 @@ var EnigmailDecryption = {
 
     // do not return anything if gpg signales DECRYPTION_FAILED
     // (which could be possible in case of MDC errors)
     if ((uiFlags & EnigmailConstants.UI_IGNORE_MDC_ERROR) &&
       (result.statusFlags & EnigmailConstants.MISSING_MDC)) {
       EnigmailLog.DEBUG("decryption.jsm: decryptMessage: ignoring MDC error\n");
     }
     else if (result.statusFlags & EnigmailConstants.DECRYPTION_FAILED) {
+      EnigmailLog.DEBUG("decryption.jsm: decryptMessage: failed\n");
       plainText = "";
     }
 
     userIdObj.value = result.userId;
     keyIdObj.value = result.keyId;
     sigDetailsObj.value = result.sigDetails;
     if (encToDetailsObj) {
       encToDetailsObj.value = result.encToDetails;
--- a/mail/extensions/openpgp/content/modules/masterpass.jsm
+++ b/mail/extensions/openpgp/content/modules/masterpass.jsm
@@ -46,16 +46,18 @@ var OpenPGPMasterpass = {
     } catch (ex) {
       EnigmailLog.writeException("masterpass.jsm", ex);
       throw ex;
     }
     EnigmailLog.DEBUG("masterpass.jsm: ensureMasterPassword(): ok\n");
   },
 
   generatePassword: function() {
+    // TODO: Patrick suggested to replace with
+    //       EnigmailRNG.getRandomString(numChars)
     const random_bytes = new Uint8Array(32);
     crypto.getRandomValues(random_bytes);
     let result = "";
     for (let i = 0; i < 32; i++) {
       result += (random_bytes[i] % 16).toString(16);
     }
     return result;
   },
--- a/mail/extensions/openpgp/content/modules/rnp.jsm
+++ b/mail/extensions/openpgp/content/modules/rnp.jsm
@@ -342,16 +342,20 @@ console.log("rnp_key_get_subkey_count: "
         ).contents;
 
         result.statusFlags |= EnigmailConstants.DECRYPTION_OKAY;
         result.decryptedData = char_array.readString();
         console.log(result.decryptedData);
       }
     }
 
+    if (!(result.statusFlags & EnigmailConstants.DECRYPTION_OKAY)) {
+      result.statusFlags |= EnigmailConstants.DECRYPTION_FAILED;
+    }
+
     RNPLib.rnp_input_destroy(input_from_memory);
     RNPLib.rnp_output_destroy(output_to_memory);
 
     return result;
   },
   
   genKey(userId, keyType, keyBits, expiryDays, passphrase) {
     
--- a/mail/extensions/openpgp/content/modules/rnpLib.jsm
+++ b/mail/extensions/openpgp/content/modules/rnpLib.jsm
@@ -5,16 +5,19 @@
 const { ctypes } = ChromeUtils.import("resource://gre/modules/ctypes.jsm");
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 var systemOS = Services.appinfo.OS.toLowerCase();
 const { OS } = ChromeUtils.import("resource://gre/modules/osfile.jsm");
 var abi = ctypes.default_abi;
 const EnigmailApp = ChromeUtils.import(
   "chrome://openpgp/content/modules/app.jsm"
 ).EnigmailApp;
+var OpenPGPMasterpass = ChromeUtils.import(
+  "chrome://openpgp/content/modules/masterpass.jsm"
+).OpenPGPMasterpass;
 
 // Open librnp. Determine the path to the chrome directory and look for it
 // there first. If not, fallback to searching the standard locations.
 var librnp, librnpPath;
 
 function tryLoadRNP(name, suffix) {
   let filename = ctypes.libraryName(name) + suffix;
   let binPath = Services.dirsvc.get("XpcomLib", Ci.nsIFile).path;
@@ -205,27 +208,34 @@ function enableRNPLibJS() {
     },
 
     keep_password_cb_alive: null,
 
     password_cb(ffi, app_ctx, key, pgp_context, buf, buf_len) {
       console.log(
         "in RNPLib.password_cb, context: " + pgp_context.readString()
       );
-      //console.log("max_len: " + buf_len);
-      //console.log(buf);
+      console.log("max_len: " + buf_len);
+      
+      let pass = OpenPGPMasterpass.retrieveOpenPGPPassword();
+      var passCTypes = ctypes.char.array()(pass); // UTF-8
+      let passLen = passCTypes.length;
 
-      /*
+      if (buf_len < passLen) {
+        return false;
+      }
+
       let char_array = ctypes.cast(buf, ctypes.char.array(buf_len).ptr)
         .contents;
-      */
-      //char_array[0] = 0;
-      //console.log(char_array.readString());
 
-      //buf[0] = 0;
+      let i;
+      for (i = 0; i < passLen; ++i) {
+        char_array[i] = passCTypes[i];
+      }
+      char_array[passLen] = 0;
       return true;
     },
 
     // Get a RNP library handle.
     rnp_ffi_create: librnp.declare(
       "rnp_ffi_create",
       abi,
       rnp_result_t,
--- a/mail/extensions/openpgp/content/ui/enigmailCommon.js
+++ b/mail/extensions/openpgp/content/ui/enigmailCommon.js
@@ -42,16 +42,17 @@ var EnigmailConstants = ChromeUtils.impo
 var EnigmailErrorHandling = ChromeUtils.import("chrome://openpgp/content/modules/errorHandling.jsm").EnigmailErrorHandling;
 var EnigmailKeyServer = ChromeUtils.import("chrome://openpgp/content/modules/keyserver.jsm").EnigmailKeyServer;
 var EnigmailEvents = ChromeUtils.import("chrome://openpgp/content/modules/events.jsm").EnigmailEvents;
 var EnigmailGpg = ChromeUtils.import("chrome://openpgp/content/modules/gpg.jsm").EnigmailGpg;
 var EnigmailGpgAgent = ChromeUtils.import("chrome://openpgp/content/modules/gpgAgent.jsm").EnigmailGpgAgent;
 var EnigmailStreams = ChromeUtils.import("chrome://openpgp/content/modules/streams.jsm").EnigmailStreams;
 var EnigmailCryptoAPI = ChromeUtils.import("chrome://openpgp/content/modules/cryptoAPI.jsm").EnigmailCryptoAPI;
 
+var OpenPGPMasterpass = ChromeUtils.import("chrome://openpgp/content/modules/masterpass.jsm").OpenPGPMasterpass;
 var RNP = ChromeUtils.import("chrome://openpgp/content/modules/rnp.jsm").RNP;
 
 
 // The compatible Enigmime version
 var gEnigmailSvc;
 var gEnigPromptSvc;
 
 
--- a/mail/extensions/openpgp/content/ui/enigmailKeygen.js
+++ b/mail/extensions/openpgp/content/ui/enigmailKeygen.js
@@ -41,47 +41,33 @@ const KEYGEN_CANCELLED = "cancelled";
 
 function enigmailKeygenLoad() {
   EnigmailLog.DEBUG("enigmailKeygen.js: Load\n");
 
   gUserIdentityList = document.getElementById("userIdentity");
   gUserIdentityListPopup = document.getElementById("userIdentityPopup");
   gUseForSigning = document.getElementById("useForSigning");
 
-  var noPassphrase = document.getElementById("noPassphrase");
-
-  /*
-  if (!EnigmailGpg.getGpgFeature("keygen-passphrase")) {
-    document.getElementById("passphraseRow").setAttribute("collapsed", "true");
-    noPassphrase.setAttribute("collapsed", "true");
-  }
-  */
-
   //if (EnigmailGpg.getGpgFeature("supports-ecc-keys"))
   let eccElem = document.getElementById("keyType_ecc");
   eccElem.removeAttribute("hidden");
   updateKeySizeSel(eccElem);
   document.getElementById("keyType").selectedItem = eccElem;
 
   if (gUserIdentityListPopup) {
     fillIdentityListPopup();
   }
   gUserIdentityList.focus();
 
   // restore safe setting, which you ALWAYS explicitly have to overrule,
   // if you don't want them:
-  // - specify passphrase
   // - specify expiry date
-  noPassphrase.checked = false;
-  EnigSetPref("noPassphrase", noPassphrase.checked);
   var noExpiry = document.getElementById("noExpiry");
   noExpiry.checked = false;
 
-  enigmailKeygenUpdate(true, false);
-
   var enigmailSvc = GetEnigmailSvc();
   if (!enigmailSvc) {
     EnigAlert(EnigGetString("accessError"));
   }
 
   if (EnigmailGpgAgent.agentType != "gpg") {
     EnigAlert(EnigGetString("onlyGPG"));
     return;
@@ -106,35 +92,16 @@ function enigmailOnClose() {
 }
 
 function enigmailKeygenUnload() {
   EnigmailLog.DEBUG("enigmailKeygen.js: Unload\n");
 
   enigmailKeygenCloseRequest();
 }
 
-
-function enigmailKeygenUpdate(getPrefs, setPrefs) {
-  EnigmailLog.DEBUG("enigmailKeygen.js: Update: " + getPrefs + ", " + setPrefs + "\n");
-
-  var noPassphrase = document.getElementById("noPassphrase");
-  var noPassphraseChecked = getPrefs ? EnigGetPref("noPassphrase") : noPassphrase.checked;
-
-  if (setPrefs) {
-    EnigSetPref("noPassphrase", noPassphraseChecked);
-  }
-
-  noPassphrase.checked = noPassphraseChecked;
-
-  var passphrase1 = document.getElementById("passphrase");
-  var passphrase2 = document.getElementById("passphraseRepeat");
-  passphrase1.disabled = noPassphraseChecked;
-  passphrase2.disabled = noPassphraseChecked;
-}
-
 function enigmailKeygenTerminate(exitCode) {
   EnigmailLog.DEBUG("enigmailKeygen.js: Terminate:\n");
 
   var curId = gUsedId;
 
   gKeygenRequest = null;
 
   if ((!gGeneratedKey) || gGeneratedKey == KEYGEN_CANCELLED) {
@@ -147,18 +114,16 @@ function enigmailKeygenTerminate(exitCod
   progMeter.setAttribute("value", 100);
 
   if (gGeneratedKey) {
     if (gUseForSigning.checked) {
       curId.setBoolAttribute("enablePgp", true);
       curId.setIntAttribute("pgpKeyMode", 1);
       curId.setCharAttribute("pgpkeyId", "0x" + gGeneratedKey);
 
-      enigmailKeygenUpdate(false, true);
-
       EnigSavePrefs();
 
       EnigmailWindows.keyManReloadKeys();
 
       if (EnigConfirm(EnigGetString("keygenComplete", curId.email) + "\n\n" + EnigGetString("revokeCertRecommended"), EnigGetString("keyMan.button.generateCert"))) {
         EnigCreateRevokeCert(gGeneratedKey, curId.email, closeAndReset);
       } else {
         closeAndReset();
@@ -247,43 +212,16 @@ function enigmailKeygenCloseRequest() {
 
   if (gKeygenRequest) {
     var p = gKeygenRequest;
     gKeygenRequest = null;
     p.kill(false);
   }
 }
 
-function enigmailCheckPassphrase() {
-  var passphraseElement = document.getElementById("passphrase");
-  var passphrase2Element = document.getElementById("passphraseRepeat");
-
-  var passphrase = passphraseElement.value;
-
-  if (passphrase != passphrase2Element.value) {
-    EnigAlert(EnigGetString("passNoMatch"));
-    return null;
-  }
-
-  if (passphrase.search(/[^\x20-\x7E]/) >= 0) {
-    if (!EnigmailDialog.confirmDlg(window, EnigmailLocale.getString("keygen.passCharProblem"),
-        EnigmailLocale.getString("dlg.button.ignore"), EnigmailLocale.getString("dlg.button.cancel"))) {
-      return null;
-    }
-  }
-  if ((passphrase.search(/^\s/) === 0) || (passphrase.search(/\s$/) >= 0)) {
-    EnigAlert(EnigGetString("passSpaceProblem"));
-    return null;
-  }
-
-  return passphrase;
-}
-
-
-
 function enigmailKeygenStart() {
   EnigmailLog.DEBUG("enigmailKeygen.js: Start\n");
 
   if (gKeygenRequest) {
     let req = gKeygenRequest.QueryInterface(Components.interfaces.nsIRequest);
     if (req.isPending()) {
       EnigmailDialog.info(window, EnigGetString("genGoing"));
       return;
@@ -294,33 +232,16 @@ function enigmailKeygenStart() {
   gAllData = "";
 
   var enigmailSvc = GetEnigmailSvc();
   if (!enigmailSvc) {
     EnigAlert(EnigGetString("accessError"));
     return;
   }
 
-  var passphrase = "";
-  // gpg >= 2.1 queries passphrase using gpg-agent only
-  //if (EnigmailGpg.getGpgFeature("keygen-passphrase"))
-
-  var noPassphraseElement = document.getElementById("noPassphrase");
-  var passphraseElement = document.getElementById("passphrase");
-
-  if (!noPassphraseElement.checked) {
-    if (passphraseElement.value.trim() === "") {
-      EnigmailDialog.info(window, EnigGetString("passCheckBox"));
-      return;
-    }
-
-    passphrase = enigmailCheckPassphrase();
-    if (passphrase === null) return;
-  }
-
   var noExpiry = document.getElementById("noExpiry");
   var expireInput = document.getElementById("expireInput");
   var timeScale = document.getElementById("timeScale");
 
   var expiryTime = 0;
   if (!noExpiry.checked) {
     expiryTime = Number(expireInput.value) * Number(timeScale.value);
     if (expiryTime > 36500) {
@@ -353,17 +274,18 @@ function enigmailKeygenStart() {
   var confirmMsg = EnigGetString("keyConfirm", idString);
 
   if (!EnigConfirm(confirmMsg, EnigGetString("keyMan.button.generateKey"))) {
     return;
   }
 
   try {
     const cApi = EnigmailCryptoAPI();
-    let newId = cApi.sync(cApi.genKey(idString, keyType, keySize, expiryTime, passphrase));
+    let newId = cApi.sync(cApi.genKey(idString, keyType, keySize, expiryTime,
+                                      OpenPGPMasterpass.retrieveOpenPGPPassword()));
     console.log("created new key with id: " + newId);
   } catch(ex) {
     console.log(ex);
   } 
 
   EnigmailWindows.keyManReloadKeys();
   closeAndReset();
 
--- a/mail/extensions/openpgp/content/ui/enigmailKeygen.xul
+++ b/mail/extensions/openpgp/content/ui/enigmailKeygen.xul
@@ -38,39 +38,16 @@
     </menulist>
   </hbox>
 
   <checkbox id="useForSigning"
             label="&enigmail.useForSigning.label;"
             checked="true" />
 </vbox>
 
-<checkbox id="noPassphrase"
-          label="&enigmail.keyNoPassphrase.label;"
-          oncommand="enigmailKeygenUpdate(false, false);" />
-
-<grid>
-  <columns>
-    <column />
-    <column flex="1"/>
-  </columns>
-
-  <rows>
-    <row id="passphraseRow">
-      <hbox id="passphraseBox" align="center">
-        <label control="passphrase" value="&enigmail.keyPassphrase.label;" />
-      </hbox>
-      <hbox align="center">
-        <html:input id="passphrase" type="password" />
-        <label control="passphraseRepeat" value="&enigmail.keyPassphraseRepeat.label;" />
-        <html:input id="passphraseRepeat" type="password" />
-      </hbox>
-    </row>
-  </rows>
-</grid>
 
 <tabbox flex="1">
   <tabs id="settingsTabBox">
     <tab id="basicTab"    label="&enigmail.keyGen.expiry.title;"/>
     <tab id="advancedTab" label="&enigmail.advancedPrefsButton.label;"/>
   </tabs>
 
   <tabpanels flex="1">