Browse Source

qt PasswordLineEdit: try to clear password from memory

If an attacker has access to the process' memory, it's probably already game over,
still we can make their life a bit harder.

I really tried but failed to encapsulate this logic inside PasswordLineEdit.
The destroyed signal arrives too late.
deleteLater is not called.
__del__ gets called too late.
hard-fail-on-bad-server-string
SomberNight 5 years ago
parent
commit
5259fcb6fd
No known key found for this signature in database GPG Key ID: B33B5F232C6271E9
  1. 101
      electrum/gui/qt/installwizard.py
  2. 23
      electrum/gui/qt/password_dialog.py
  3. 6
      electrum/gui/qt/util.py

101
electrum/gui/qt/installwizard.py

@ -281,51 +281,57 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
name_e.textChanged.connect(on_filename) name_e.textChanged.connect(on_filename)
name_e.setText(os.path.basename(path)) name_e.setText(os.path.basename(path))
while True: def run_user_interaction_loop():
if self.loop.exec_() != 2: # 2 = next while True:
raise UserCancelled if self.loop.exec_() != 2: # 2 = next
assert temp_storage raise UserCancelled
if temp_storage.file_exists() and not temp_storage.is_encrypted(): assert temp_storage
break if temp_storage.file_exists() and not temp_storage.is_encrypted():
if not temp_storage.file_exists(): break
break if not temp_storage.file_exists():
wallet_from_memory = get_wallet_from_daemon(temp_storage.path) break
if wallet_from_memory: wallet_from_memory = get_wallet_from_daemon(temp_storage.path)
raise WalletAlreadyOpenInMemory(wallet_from_memory) if wallet_from_memory:
if temp_storage.file_exists() and temp_storage.is_encrypted(): raise WalletAlreadyOpenInMemory(wallet_from_memory)
if temp_storage.is_encrypted_with_user_pw(): if temp_storage.file_exists() and temp_storage.is_encrypted():
password = pw_e.text() if temp_storage.is_encrypted_with_user_pw():
try: password = pw_e.text()
temp_storage.decrypt(password) try:
break temp_storage.decrypt(password)
except InvalidPassword as e: break
self.show_message(title=_('Error'), msg=str(e)) except InvalidPassword as e:
continue self.show_message(title=_('Error'), msg=str(e))
except BaseException as e: continue
self.logger.exception('') except BaseException as e:
self.show_message(title=_('Error'), msg=repr(e)) self.logger.exception('')
raise UserCancelled() self.show_message(title=_('Error'), msg=repr(e))
elif temp_storage.is_encrypted_with_hw_device(): raise UserCancelled()
try: elif temp_storage.is_encrypted_with_hw_device():
self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET, storage=temp_storage) try:
except InvalidPassword as e: self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET, storage=temp_storage)
self.show_message(title=_('Error'), except InvalidPassword as e:
msg=_('Failed to decrypt using this hardware device.') + '\n' + self.show_message(title=_('Error'),
_('If you use a passphrase, make sure it is correct.')) msg=_('Failed to decrypt using this hardware device.') + '\n' +
self.reset_stack() _('If you use a passphrase, make sure it is correct.'))
return self.select_storage(path, get_wallet_from_daemon) self.reset_stack()
except (UserCancelled, GoBack): return self.select_storage(path, get_wallet_from_daemon)
raise except (UserCancelled, GoBack):
except BaseException as e: raise
self.logger.exception('') except BaseException as e:
self.show_message(title=_('Error'), msg=repr(e)) self.logger.exception('')
raise UserCancelled() self.show_message(title=_('Error'), msg=repr(e))
if temp_storage.is_past_initial_decryption(): raise UserCancelled()
break if temp_storage.is_past_initial_decryption():
break
else:
raise UserCancelled()
else: else:
raise UserCancelled() raise Exception('Unexpected encryption version')
else:
raise Exception('Unexpected encryption version') try:
run_user_interaction_loop()
finally:
pw_e.clear()
return temp_storage.path, (temp_storage if temp_storage.file_exists() else None) return temp_storage.path, (temp_storage if temp_storage.file_exists() else None)
@ -482,8 +488,11 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
playout = PasswordLayout(msg=msg, kind=kind, OK_button=self.next_button, playout = PasswordLayout(msg=msg, kind=kind, OK_button=self.next_button,
force_disable_encrypt_cb=force_disable_encrypt_cb) force_disable_encrypt_cb=force_disable_encrypt_cb)
playout.encrypt_cb.setChecked(True) playout.encrypt_cb.setChecked(True)
self.exec_layout(playout.layout()) try:
return playout.new_password(), playout.encrypt_cb.isChecked() self.exec_layout(playout.layout())
return playout.new_password(), playout.encrypt_cb.isChecked()
finally:
playout.clear_password_fields()
@wizard_dialog @wizard_dialog
def request_password(self, run_next, force_disable_encrypt_cb=False): def request_password(self, run_next, force_disable_encrypt_cb=False):

23
electrum/gui/qt/password_dialog.py

@ -25,6 +25,7 @@
import re import re
import math import math
from functools import partial
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from PyQt5.QtGui import QPixmap from PyQt5.QtGui import QPixmap
@ -165,6 +166,10 @@ class PasswordLayout(object):
pw = None pw = None
return pw return pw
def clear_password_fields(self):
for field in [self.pw, self.new_pw, self.conf_pw]:
field.clear()
class PasswordLayoutForHW(object): class PasswordLayoutForHW(object):
@ -258,9 +263,12 @@ class ChangePasswordDialogForSW(ChangePasswordDialogBase):
force_disable_encrypt_cb=not wallet.can_have_keystore_encryption()) force_disable_encrypt_cb=not wallet.can_have_keystore_encryption())
def run(self): def run(self):
if not self.exec_(): try:
return False, None, None, None if not self.exec_():
return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked() return False, None, None, None
return True, self.playout.old_password(), self.playout.new_password(), self.playout.encrypt_cb.isChecked()
finally:
self.playout.clear_password_fields()
class ChangePasswordDialogForHW(ChangePasswordDialogBase): class ChangePasswordDialogForHW(ChangePasswordDialogBase):
@ -301,6 +309,9 @@ class PasswordDialog(WindowModalDialog):
run_hook('password_dialog', pw, grid, 1) run_hook('password_dialog', pw, grid, 1)
def run(self): def run(self):
if not self.exec_(): try:
return if not self.exec_():
return self.pw.text() return
return self.pw.text()
finally:
self.pw.clear()

6
electrum/gui/qt/util.py

@ -753,6 +753,12 @@ class PasswordLineEdit(QLineEdit):
QLineEdit.__init__(self, *args, **kwargs) QLineEdit.__init__(self, *args, **kwargs)
self.setEchoMode(QLineEdit.Password) self.setEchoMode(QLineEdit.Password)
def clear(self):
# Try to actually overwrite the memory.
# This is really just a best-effort thing...
self.setText(len(self.text()) * " ")
super().clear()
class TaskThread(QThread): class TaskThread(QThread):
'''Thread that runs background tasks. Callbacks are guaranteed '''Thread that runs background tasks. Callbacks are guaranteed

Loading…
Cancel
Save