Browse Source

Kivy: use the same password for all wallets

When the app is started, the password is checked against all
wallets in the directory.

If the test passes:
 - subsequent wallet creations will use the same password
 - subsequent password updates will be performed on all wallets
 - wallets that are not storage encrypted will encrypted
   on the next password update (even if they are watching-only)

This behaviour is restricted on Android, with a 'single_password' config variable.
Wallet creation without password is disabled if single_password is set
patch-4
ThomasV 4 years ago
parent
commit
1e4fa83098
  1. 30
      electrum/gui/kivy/main_window.py
  2. 5
      electrum/gui/kivy/uix/dialogs/installwizard.py
  3. 20
      electrum/gui/kivy/uix/dialogs/password_dialog.py
  4. 2
      electrum/gui/kivy/uix/dialogs/settings.py
  5. 8
      electrum/gui/kivy/uix/dialogs/wallets.py
  6. 55
      electrum/wallet.py
  7. 1
      run_electrum

30
electrum/gui/kivy/main_window.py

@ -12,6 +12,8 @@ from typing import TYPE_CHECKING, Optional, Union, Callable, Sequence
from electrum.storage import WalletStorage, StorageReadWriteError
from electrum.wallet_db import WalletDB
from electrum.wallet import Wallet, InternalAddressCorruption, Abstract_Wallet
from electrum.wallet import check_password_for_directory, update_password_for_directory
from electrum.plugin import run_hook
from electrum import util
from electrum.util import (profiler, InvalidPassword, send_exception_to_crash_reporter,
@ -367,6 +369,7 @@ class ElectrumWindow(App, Logger):
self.pause_time = 0
self.asyncio_loop = asyncio.get_event_loop()
self.password = None
self._use_single_password = False
App.__init__(self)#, **kwargs)
Logger.__init__(self)
@ -634,6 +637,9 @@ class ElectrumWindow(App, Logger):
def on_wizard_success(self, storage, db, password):
self.password = password
if self.electrum_config.get('single_password'):
self._use_single_password = check_password_for_directory(self.electrum_config, password)
self.logger.info(f'use single password: {self._use_single_password}')
wallet = Wallet(db, storage, config=self.electrum_config)
wallet.start_network(self.daemon.network)
self.daemon.add_wallet(wallet)
@ -649,6 +655,12 @@ class ElectrumWindow(App, Logger):
return
if self.wallet and self.wallet.storage.path == path:
return
if self.password and self._use_single_password:
storage = WalletStorage(path)
# call check_password to decrypt
storage.check_password(self.password)
self.on_open_wallet(self.password, storage)
return
d = OpenWalletDialog(self, path, self.on_open_wallet)
d.open()
@ -724,10 +736,13 @@ class ElectrumWindow(App, Logger):
if self._channels_dialog:
Clock.schedule_once(lambda dt: self._channels_dialog.update())
def is_wallet_creation_disabled(self):
return bool(self.electrum_config.get('single_password')) and self.password is None
def wallets_dialog(self):
from .uix.dialogs.wallets import WalletDialog
dirname = os.path.dirname(self.electrum_config.get_wallet_path())
d = WalletDialog(dirname, self.load_wallet_by_name)
d = WalletDialog(dirname, self.load_wallet_by_name, self.is_wallet_creation_disabled())
d.open()
def popup_dialog(self, name):
@ -1219,9 +1234,18 @@ class ElectrumWindow(App, Logger):
def change_password(self, cb):
def on_success(old_password, new_password):
self.wallet.update_password(old_password, new_password)
# called if old_password works on self.wallet
self.password = new_password
self.show_info(_("Your password was updated"))
if self._use_single_password:
path = self.wallet.storage.path
self.stop_wallet()
update_password_for_directory(self.electrum_config, old_password, new_password)
self.load_wallet_by_name(path)
msg = _("Password updated successfully")
else:
self.wallet.update_password(old_password, new_password)
msg = _("Password updated for {}").format(os.path.basename(self.wallet.storage.path))
self.show_info(msg)
on_failure = lambda: self.show_error(_("Password not updated"))
d = ChangePasswordDialog(self, self.wallet, on_success, on_failure)
d.open()

5
electrum/gui/kivy/uix/dialogs/installwizard.py

@ -1149,9 +1149,8 @@ class InstallWizard(BaseWizard, Widget):
Clock.schedule_once(lambda dt: self.app.show_error(msg))
def request_password(self, run_next, force_disable_encrypt_cb=False):
if force_disable_encrypt_cb:
# do not request PIN for watching-only wallets
run_next(None, False)
if self.app.password is not None:
run_next(self.app.password, True)
return
def on_success(old_pw, pw):
assert old_pw is None

20
electrum/gui/kivy/uix/dialogs/password_dialog.py

@ -29,6 +29,7 @@ Builder.load_string('''
message: ''
basename:''
is_change: False
hide_wallet_label: False
require_password: True
BoxLayout:
size_hint: 1, 1
@ -45,13 +46,15 @@ Builder.load_string('''
font_size: '20dp'
text: _('Wallet') + ': ' + root.basename
text_size: self.width, None
disabled: root.hide_wallet_label
opacity: 0 if root.hide_wallet_label else 1
IconButton:
size_hint: 0.15, None
height: '40dp'
icon: f'atlas://{KIVY_GUI_PATH}/theming/light/btn_create_account'
on_release: root.select_file()
disabled: root.is_change
opacity: 0 if root.is_change else 1
disabled: root.hide_wallet_label or root.is_change
opacity: 0 if root.hide_wallet_label or root.is_change else 1
Widget:
size_hint: 1, 0.05
Label:
@ -267,6 +270,7 @@ class PasswordDialog(AbstractPasswordDialog):
def __init__(self, app, **kwargs):
AbstractPasswordDialog.__init__(self, app, **kwargs)
self.hide_wallet_label = app._use_single_password
def clear_password(self):
self.ids.textinput_generic_password.text = ''
@ -320,6 +324,7 @@ class ChangePasswordDialog(PasswordDialog):
class OpenWalletDialog(PasswordDialog):
"""This dialog will let the user choose another wallet file if they don't remember their the password"""
def __init__(self, app, path, callback):
self.app = app
@ -331,7 +336,7 @@ class OpenWalletDialog(PasswordDialog):
def select_file(self):
dirname = os.path.dirname(self.app.electrum_config.get_wallet_path())
d = WalletDialog(dirname, self.init_storage_from_path)
d = WalletDialog(dirname, self.init_storage_from_path, self.app.is_wallet_creation_disabled())
d.open()
def init_storage_from_path(self, path):
@ -343,9 +348,14 @@ class OpenWalletDialog(PasswordDialog):
elif self.storage.is_encrypted():
if not self.storage.is_encrypted_with_user_pw():
raise Exception("Kivy GUI does not support this type of encrypted wallet files.")
self.require_password = True
self.pw_check = self.storage.check_password
self.message = self.enter_pw_message
if self.app.password and self.check_password(self.app.password):
self.pw = self.app.password # must be set so that it is returned in callback
self.require_password = False
self.message = _('Press Next to open')
else:
self.require_password = True
self.message = self.enter_pw_message
else:
# it is a bit wasteful load the wallet here and load it again in main_window,
# but that is fine, because we are progressively enforcing storage encryption.

2
electrum/gui/kivy/uix/dialogs/settings.py

@ -87,7 +87,7 @@ Builder.load_string('''
CardSeparator
SettingsItem:
title: _('Password')
description: _("Change wallet password.")
description: _('Change your password') if app._use_single_password else _("Change your password for this wallet.")
action: root.change_password
CardSeparator
SettingsItem:

8
electrum/gui/kivy/uix/dialogs/wallets.py

@ -16,6 +16,7 @@ Builder.load_string('''
title: _('Wallets')
id: popup
path: ''
disable_new: True
BoxLayout:
orientation: 'vertical'
padding: '10dp'
@ -33,7 +34,8 @@ Builder.load_string('''
cols: 3
size_hint_y: 0.1
Button:
id: open_button
id: new_button
disabled: root.disable_new
size_hint: 0.1, None
height: '48dp'
text: _('New')
@ -53,12 +55,14 @@ Builder.load_string('''
class WalletDialog(Factory.Popup):
def __init__(self, path, callback):
def __init__(self, path, callback, disable_new):
Factory.Popup.__init__(self)
self.path = path
self.callback = callback
self.disable_new = disable_new
def new_wallet(self, dirname):
assert self.disable_new is False
def cb(filename):
if not filename:
return

55
electrum/wallet.py

@ -2951,12 +2951,63 @@ def restore_wallet_from_text(text, *, path, config: SimpleConfig,
if gap_limit is not None:
db.put('gap_limit', gap_limit)
wallet = Wallet(db, storage, config=config)
assert not storage.file_exists(), "file was created too soon! plaintext keys might have been written to disk"
wallet.update_password(old_pw=None, new_pw=password, encrypt_storage=encrypt_file)
wallet.synchronize()
msg = ("This wallet was restored offline. It may contain more addresses than displayed. "
"Start a daemon and use load_wallet to sync its history.")
wallet.save_db()
return {'wallet': wallet, 'msg': msg}
def check_password_for_directory(config, old_password, new_password=None):
"""Checks password against all wallets and returns True if they can all be updated.
If new_password is not None, update all wallet passwords to new_password.
"""
dirname = os.path.dirname(config.get_wallet_path())
failed = []
for filename in os.listdir(dirname):
path = os.path.join(dirname, filename)
basename = os.path.basename(path)
storage = WalletStorage(path)
if not storage.is_encrypted():
# it is a bit wasteful load the wallet here, but that is fine
# because we are progressively enforcing storage encryption.
db = WalletDB(storage.read(), manual_upgrades=False)
wallet = Wallet(db, storage, config=config)
if wallet.has_keystore_encryption():
try:
wallet.check_password(old_password)
except:
failed.append(basename)
continue
if new_password:
wallet.update_password(old_password, new_password)
else:
if new_password:
wallet.update_password(None, new_password)
continue
if not storage.is_encrypted_with_user_pw():
failed.append(basename)
continue
try:
storage.check_password(old_password)
except:
failed.append(basename)
continue
db = WalletDB(storage.read(), manual_upgrades=False)
wallet = Wallet(db, storage, config=config)
try:
wallet.check_password(old_password)
except:
failed.append(basename)
continue
if new_password:
wallet.update_password(old_password, new_password)
return failed == []
def update_password_for_directory(config, old_password, new_password) -> bool:
assert new_password is not None
assert check_password_for_directory(config, old_password, None)
return check_password_for_directory(config, old_password, new_password)

1
run_electrum

@ -317,6 +317,7 @@ def main():
'verbosity': '*' if build_config.DEBUG else '',
'cmd': 'gui',
'gui': 'kivy',
'single_password':True,
}
else:
config_options = args.__dict__

Loading…
Cancel
Save