kivy: use password + pin_code
- password is per wallet, is retained in memory - pin code is saved in config
This commit is contained in:
@@ -31,6 +31,7 @@ from kivy.clock import Clock
|
|||||||
from kivy.factory import Factory
|
from kivy.factory import Factory
|
||||||
from kivy.metrics import inch
|
from kivy.metrics import inch
|
||||||
from kivy.lang import Builder
|
from kivy.lang import Builder
|
||||||
|
from .uix.dialogs.password_dialog import PasswordDialog
|
||||||
|
|
||||||
## lazy imports for factory so that widgets can be used in kv
|
## lazy imports for factory so that widgets can be used in kv
|
||||||
#Factory.register('InstallWizard', module='electrum.gui.kivy.uix.dialogs.installwizard')
|
#Factory.register('InstallWizard', module='electrum.gui.kivy.uix.dialogs.installwizard')
|
||||||
@@ -326,7 +327,7 @@ class ElectrumWindow(App):
|
|||||||
self.wallet = None # type: Optional[Abstract_Wallet]
|
self.wallet = None # type: Optional[Abstract_Wallet]
|
||||||
self.pause_time = 0
|
self.pause_time = 0
|
||||||
self.asyncio_loop = asyncio.get_event_loop()
|
self.asyncio_loop = asyncio.get_event_loop()
|
||||||
self.pin_code = None
|
self.password = None
|
||||||
|
|
||||||
App.__init__(self)#, **kwargs)
|
App.__init__(self)#, **kwargs)
|
||||||
|
|
||||||
@@ -623,10 +624,12 @@ class ElectrumWindow(App):
|
|||||||
if wallet.has_password():
|
if wallet.has_password():
|
||||||
def on_success(x):
|
def on_success(x):
|
||||||
# save pin_code so that we can create backups
|
# save pin_code so that we can create backups
|
||||||
self.pin_code = x
|
self.password = x
|
||||||
self.load_wallet(wallet)
|
self.load_wallet(wallet)
|
||||||
self.password_dialog(wallet=wallet, msg=_('Enter PIN code'),
|
self.password_dialog(
|
||||||
on_success=on_success, on_failure=self.stop)
|
check_password=wallet.check_password,
|
||||||
|
on_success=on_success,
|
||||||
|
on_failure=self.stop)
|
||||||
else:
|
else:
|
||||||
self.load_wallet(wallet)
|
self.load_wallet(wallet)
|
||||||
else:
|
else:
|
||||||
@@ -642,11 +645,13 @@ class ElectrumWindow(App):
|
|||||||
if not storage.is_encrypted_with_user_pw():
|
if not storage.is_encrypted_with_user_pw():
|
||||||
raise Exception("Kivy GUI does not support this type of encrypted wallet files.")
|
raise Exception("Kivy GUI does not support this type of encrypted wallet files.")
|
||||||
def on_password(pw):
|
def on_password(pw):
|
||||||
self.pin_code = pw
|
self.password = pw
|
||||||
storage.decrypt(pw)
|
storage.decrypt(pw)
|
||||||
self._on_decrypted_storage(storage)
|
self._on_decrypted_storage(storage)
|
||||||
self.password_dialog(wallet=storage, msg=_('Enter PIN code'),
|
self.password_dialog(
|
||||||
on_success=on_password, on_failure=self.stop)
|
check_password=storage.check_password,
|
||||||
|
on_success=on_password,
|
||||||
|
on_failure=self.stop)
|
||||||
return
|
return
|
||||||
self._on_decrypted_storage(storage)
|
self._on_decrypted_storage(storage)
|
||||||
if not ask_if_wizard:
|
if not ask_if_wizard:
|
||||||
@@ -940,7 +945,7 @@ class ElectrumWindow(App):
|
|||||||
def on_resume(self):
|
def on_resume(self):
|
||||||
now = time.time()
|
now = time.time()
|
||||||
if self.wallet and self.wallet.has_password() and now - self.pause_time > 60:
|
if self.wallet and self.wallet.has_password() and now - self.pause_time > 60:
|
||||||
self.password_dialog(wallet=self.wallet, msg=_('Enter PIN'), on_success=None, on_failure=self.stop)
|
self.password_dialog(check_password=self.check_pin_code, on_success=None, on_failure=self.stop, is_password=False)
|
||||||
if self.nfcscanner:
|
if self.nfcscanner:
|
||||||
self.nfcscanner.nfc_enable()
|
self.nfcscanner.nfc_enable()
|
||||||
|
|
||||||
@@ -1102,12 +1107,12 @@ class ElectrumWindow(App):
|
|||||||
def on_fee(self, event, *arg):
|
def on_fee(self, event, *arg):
|
||||||
self.fee_status = self.electrum_config.get_fee_status()
|
self.fee_status = self.electrum_config.get_fee_status()
|
||||||
|
|
||||||
def protected(self, msg, f, args):
|
def protected(self, f, args):
|
||||||
if self.wallet.has_password():
|
if self.electrum_config.get('pin_code'):
|
||||||
on_success = lambda pw: f(*(args + (pw,)))
|
on_success = lambda pw: f(*(args + (self.password,)))
|
||||||
self.password_dialog(wallet=self.wallet, msg=msg, on_success=on_success, on_failure=lambda: None)
|
self.password_dialog(check_password=self.check_pin_code, on_success=on_success, on_failure=lambda: None, is_password=False)
|
||||||
else:
|
else:
|
||||||
f(*(args + (None,)))
|
f(*(args + (self.password,)))
|
||||||
|
|
||||||
def toggle_lightning(self):
|
def toggle_lightning(self):
|
||||||
if self.wallet.has_lightning():
|
if self.wallet.has_lightning():
|
||||||
@@ -1167,59 +1172,64 @@ class ElectrumWindow(App):
|
|||||||
self.load_wallet_by_name(new_path)
|
self.load_wallet_by_name(new_path)
|
||||||
|
|
||||||
def show_seed(self, label):
|
def show_seed(self, label):
|
||||||
self.protected(_("Enter your PIN code in order to decrypt your seed"), self._show_seed, (label,))
|
self.protected(self._show_seed, (label,))
|
||||||
|
|
||||||
def _show_seed(self, label, password):
|
def _show_seed(self, label, password):
|
||||||
if self.wallet.has_password() and password is None:
|
if self.wallet.has_password() and password is None:
|
||||||
return
|
return
|
||||||
keystore = self.wallet.keystore
|
keystore = self.wallet.keystore
|
||||||
try:
|
seed = keystore.get_seed(password)
|
||||||
seed = keystore.get_seed(password)
|
passphrase = keystore.get_passphrase(password)
|
||||||
passphrase = keystore.get_passphrase(password)
|
|
||||||
except:
|
|
||||||
self.show_error("Invalid PIN")
|
|
||||||
return
|
|
||||||
label.data = seed
|
label.data = seed
|
||||||
if passphrase:
|
if passphrase:
|
||||||
label.data += '\n\n' + _('Passphrase') + ': ' + passphrase
|
label.data += '\n\n' + _('Passphrase') + ': ' + passphrase
|
||||||
|
|
||||||
def password_dialog(self, *, wallet: Union[Abstract_Wallet, WalletStorage],
|
def has_pin_code(self):
|
||||||
msg: str, on_success: Callable = None, on_failure: Callable = None):
|
return bool(self.electrum_config.get('pin_code'))
|
||||||
from .uix.dialogs.password_dialog import PasswordDialog
|
|
||||||
|
def check_pin_code(self, pin):
|
||||||
|
if pin != self.electrum_config.get('pin_code'):
|
||||||
|
raise InvalidPassword
|
||||||
|
|
||||||
|
def password_dialog(self, *, check_password: Callable = None,
|
||||||
|
on_success: Callable = None, on_failure: Callable = None,
|
||||||
|
is_password=True):
|
||||||
if self._password_dialog is None:
|
if self._password_dialog is None:
|
||||||
self._password_dialog = PasswordDialog()
|
self._password_dialog = PasswordDialog()
|
||||||
self._password_dialog.init(self, wallet=wallet, msg=msg,
|
self._password_dialog.init(
|
||||||
on_success=on_success, on_failure=on_failure)
|
self, check_password = check_password,
|
||||||
|
on_success=on_success, on_failure=on_failure,
|
||||||
|
is_password=is_password)
|
||||||
self._password_dialog.open()
|
self._password_dialog.open()
|
||||||
|
|
||||||
def change_password(self, cb):
|
def change_password(self, cb):
|
||||||
from .uix.dialogs.password_dialog import PasswordDialog
|
|
||||||
if self._password_dialog is None:
|
if self._password_dialog is None:
|
||||||
self._password_dialog = PasswordDialog()
|
self._password_dialog = PasswordDialog()
|
||||||
message = _("Changing PIN code.") + '\n' + _("Enter your current PIN:")
|
|
||||||
def on_success(old_password, new_password):
|
def on_success(old_password, new_password):
|
||||||
self.wallet.update_password(old_password, new_password)
|
self.wallet.update_password(old_password, new_password)
|
||||||
self.show_info(_("Your PIN code was updated"))
|
self.password = new_password
|
||||||
on_failure = lambda: self.show_error(_("PIN codes do not match"))
|
self.show_info(_("Your password was updated"))
|
||||||
self._password_dialog.init(self, wallet=self.wallet, msg=message,
|
on_failure = lambda: self.show_error(_("Password not updated"))
|
||||||
on_success=on_success, on_failure=on_failure, is_change=True)
|
self._password_dialog.init(
|
||||||
|
self, check_password = self.wallet.check_password,
|
||||||
|
on_success=on_success, on_failure=on_failure,
|
||||||
|
is_change=True, is_password=True,
|
||||||
|
has_password=self.wallet.has_password())
|
||||||
self._password_dialog.open()
|
self._password_dialog.open()
|
||||||
|
|
||||||
def change_backup_password(self):
|
def change_pin_code(self, cb):
|
||||||
from .uix.dialogs.password_dialog import PasswordDialog
|
|
||||||
from electrum.util import get_backup_dir
|
|
||||||
from electrum.storage import WalletStorage
|
|
||||||
if self._password_dialog is None:
|
if self._password_dialog is None:
|
||||||
self._password_dialog = PasswordDialog()
|
self._password_dialog = PasswordDialog()
|
||||||
message = _("Create backup.") + '\n' + _("Enter your current PIN:")
|
|
||||||
def on_success(old_password, new_password):
|
def on_success(old_password, new_password):
|
||||||
backup_pubkey = WalletStorage.get_eckey_from_password(new_password).get_public_key_hex()
|
self.electrum_config.set_key('pin_code', new_password)
|
||||||
# TODO: use a unique PIN for all wallets
|
cb()
|
||||||
self.electrum_config.set_key('backup_pubkey', backup_pubkey)
|
self.show_info(_("PIN updated") if new_password else _('PIN disabled'))
|
||||||
self.show_info(_("Backup password set"))
|
on_failure = lambda: self.show_error(_("PIN not updated"))
|
||||||
on_failure = lambda: self.show_error(_("Passwords do not match"))
|
self._password_dialog.init(
|
||||||
self._password_dialog.init(self, wallet=self.wallet, msg=message,
|
self, check_password=self.check_pin_code,
|
||||||
on_success=on_success, on_failure=on_failure, is_change=True, is_backup=True)
|
on_success=on_success, on_failure=on_failure,
|
||||||
|
is_change=True, is_password=False,
|
||||||
|
has_password = self.has_pin_code())
|
||||||
self._password_dialog.open()
|
self._password_dialog.open()
|
||||||
|
|
||||||
def save_backup(self):
|
def save_backup(self):
|
||||||
@@ -1238,7 +1248,7 @@ class ElectrumWindow(App):
|
|||||||
request_permissions([Permission.WRITE_EXTERNAL_STORAGE], cb)
|
request_permissions([Permission.WRITE_EXTERNAL_STORAGE], cb)
|
||||||
|
|
||||||
def _save_backup(self):
|
def _save_backup(self):
|
||||||
new_path = self.wallet.save_backup(pin_code=self.pin_code)
|
new_path = self.wallet.save_backup()
|
||||||
if new_path:
|
if new_path:
|
||||||
self.show_info(_("Backup saved:") + f"\n{new_path}")
|
self.show_info(_("Backup saved:") + f"\n{new_path}")
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -112,44 +112,48 @@ Builder.load_string('''
|
|||||||
class PasswordDialog(Factory.Popup):
|
class PasswordDialog(Factory.Popup):
|
||||||
|
|
||||||
def init(self, app: 'ElectrumWindow', *,
|
def init(self, app: 'ElectrumWindow', *,
|
||||||
wallet: Union['Abstract_Wallet', 'WalletStorage'] = None,
|
check_password = None,
|
||||||
msg: str, on_success: Callable = None, on_failure: Callable = None,
|
on_success: Callable = None, on_failure: Callable = None,
|
||||||
is_change: bool = False, is_backup: bool = False):
|
is_change: bool = False,
|
||||||
|
is_password: bool = False,
|
||||||
|
has_password: bool = False):
|
||||||
self.app = app
|
self.app = app
|
||||||
self.is_backup = is_backup
|
self.pw_check = check_password
|
||||||
self.wallet = wallet
|
self.message = ''
|
||||||
self.message = msg
|
|
||||||
self.on_success = on_success
|
self.on_success = on_success
|
||||||
self.on_failure = on_failure
|
self.on_failure = on_failure
|
||||||
self.success = False
|
self.success = False
|
||||||
self.is_change = is_change
|
self.is_change = is_change
|
||||||
self.pw = None
|
self.pw = None
|
||||||
self.new_password = None
|
self.new_password = None
|
||||||
self.title = 'Electrum' + (' - ' + self.wallet.basename() if self.wallet else '')
|
self.title = 'Electrum'
|
||||||
self.level = 1 if is_backup else 0
|
self.level = 1 if is_change and not has_password else 0
|
||||||
self.is_generic = self.is_backup
|
self.is_generic = is_password
|
||||||
self.update_screen()
|
self.update_screen()
|
||||||
|
|
||||||
def update_screen(self):
|
def update_screen(self):
|
||||||
self.ids.kb.password = ''
|
self.ids.kb.password = ''
|
||||||
self.ids.textinput_generic_password.text = ''
|
self.ids.textinput_generic_password.text = ''
|
||||||
if self.level == 0:
|
if self.level == 0:
|
||||||
self.message = _('Enter your PIN')
|
self.message = _('Enter your password') if self.is_generic else _('Enter your PIN')
|
||||||
elif self.level == 1:
|
elif self.level == 1:
|
||||||
self.message = _('Enter a strong password for your backup') if self.is_backup else _('Enter new PIN')
|
self.message = _('Enter new password') if self.is_generic else _('Enter new PIN')
|
||||||
elif self.level == 2:
|
elif self.level == 2:
|
||||||
self.message = _('Confirm backup password') if self.is_backup else _('Confirm new PIN')
|
self.message = _('Confirm new password') if self.is_generic else _('Confirm new PIN')
|
||||||
|
|
||||||
def check_password(self, password):
|
def check_password(self, password):
|
||||||
if self.level > 0:
|
if self.level > 0:
|
||||||
return True
|
return True
|
||||||
try:
|
try:
|
||||||
self.wallet.check_password(password)
|
self.pw_check(password)
|
||||||
return True
|
return True
|
||||||
except InvalidPassword as e:
|
except InvalidPassword as e:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def on_dismiss(self):
|
def on_dismiss(self):
|
||||||
|
if self.level == 1 and not self.is_generic and self.on_success:
|
||||||
|
self.on_success(self.pw, None)
|
||||||
|
return False
|
||||||
if not self.success:
|
if not self.success:
|
||||||
if self.on_failure:
|
if self.on_failure:
|
||||||
self.on_failure()
|
self.on_failure()
|
||||||
@@ -195,7 +199,6 @@ class PasswordDialog(Factory.Popup):
|
|||||||
self.update_screen()
|
self.update_screen()
|
||||||
elif self.level == 2:
|
elif self.level == 2:
|
||||||
self.success = pw == self.new_password
|
self.success = pw == self.new_password
|
||||||
self.is_generic = False
|
|
||||||
self.dismiss()
|
self.dismiss()
|
||||||
else:
|
else:
|
||||||
self.app.show_error(_('Wrong PIN'))
|
self.app.show_error(_('Wrong PIN'))
|
||||||
|
|||||||
@@ -18,7 +18,8 @@ Builder.load_string('''
|
|||||||
<SettingsDialog@Popup>
|
<SettingsDialog@Popup>
|
||||||
id: settings
|
id: settings
|
||||||
title: _('Electrum Settings')
|
title: _('Electrum Settings')
|
||||||
disable_pin: False
|
disable_password: False
|
||||||
|
has_pin_code: False
|
||||||
use_encryption: False
|
use_encryption: False
|
||||||
BoxLayout:
|
BoxLayout:
|
||||||
orientation: 'vertical'
|
orientation: 'vertical'
|
||||||
@@ -36,10 +37,10 @@ Builder.load_string('''
|
|||||||
action: partial(root.language_dialog, self)
|
action: partial(root.language_dialog, self)
|
||||||
CardSeparator
|
CardSeparator
|
||||||
SettingsItem:
|
SettingsItem:
|
||||||
disabled: root.disable_pin
|
status: 'ON' if root.has_pin_code else 'OFF'
|
||||||
title: _('PIN code')
|
title: _('PIN code') + ': ' + self.status
|
||||||
description: _("Change your PIN code.")
|
description: _("Change your PIN code.") if root.has_pin_code else _("Add PIN code")
|
||||||
action: partial(root.change_password, self)
|
action: partial(root.change_pin_code, self)
|
||||||
CardSeparator
|
CardSeparator
|
||||||
SettingsItem:
|
SettingsItem:
|
||||||
bu: app.base_unit
|
bu: app.base_unit
|
||||||
@@ -84,9 +85,10 @@ Builder.load_string('''
|
|||||||
action: partial(root.boolean_dialog, 'use_change', _('Use change addresses'), self.message)
|
action: partial(root.boolean_dialog, 'use_change', _('Use change addresses'), self.message)
|
||||||
CardSeparator
|
CardSeparator
|
||||||
SettingsItem:
|
SettingsItem:
|
||||||
title: _('Backups')
|
disabled: root.disable_password
|
||||||
description: _("Set password for encrypted backups.")
|
title: _('Password')
|
||||||
action: root.change_backup_password
|
description: _("Change wallet password.")
|
||||||
|
action: root.change_password
|
||||||
|
|
||||||
# disabled: there is currently only one coin selection policy
|
# disabled: there is currently only one coin selection policy
|
||||||
#CardSeparator
|
#CardSeparator
|
||||||
@@ -117,17 +119,18 @@ class SettingsDialog(Factory.Popup):
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
self.wallet = self.app.wallet
|
self.wallet = self.app.wallet
|
||||||
self.disable_pin = self.wallet.is_watching_only() if self.wallet else True
|
self.disable_password = self.wallet.is_watching_only() if self.wallet else True
|
||||||
self.use_encryption = self.wallet.has_password() if self.wallet else False
|
self.use_encryption = self.wallet.has_password() if self.wallet else False
|
||||||
|
self.has_pin_code = self.app.has_pin_code()
|
||||||
|
|
||||||
def get_language_name(self):
|
def get_language_name(self):
|
||||||
return languages.get(self.config.get('language', 'en_UK'), '')
|
return languages.get(self.config.get('language', 'en_UK'), '')
|
||||||
|
|
||||||
def change_password(self, item, dt):
|
def change_password(self, dt):
|
||||||
self.app.change_password(self.update)
|
self.app.change_password(self.update)
|
||||||
|
|
||||||
def change_backup_password(self, dt):
|
def change_pin_code(self, label, dt):
|
||||||
self.app.change_backup_password()
|
self.app.change_pin_code(self.update)
|
||||||
|
|
||||||
def language_dialog(self, item, dt):
|
def language_dialog(self, item, dt):
|
||||||
if self._language_dialog is None:
|
if self._language_dialog is None:
|
||||||
|
|||||||
@@ -263,24 +263,15 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
if self.storage:
|
if self.storage:
|
||||||
self.db.write(self.storage)
|
self.db.write(self.storage)
|
||||||
|
|
||||||
def save_backup(self, pin_code=None):
|
def save_backup(self):
|
||||||
new_db = WalletDB(self.db.dump(), manual_upgrades=False)
|
new_db = WalletDB(self.db.dump(), manual_upgrades=False)
|
||||||
new_db.put('is_backup', True)
|
new_db.put('is_backup', True)
|
||||||
new_path = os.path.join(get_backup_dir(self.config), self.basename() + '.backup')
|
new_path = os.path.join(get_backup_dir(self.config), self.basename() + '.backup')
|
||||||
if new_path is None:
|
if new_path is None:
|
||||||
return
|
return
|
||||||
new_storage = WalletStorage(new_path)
|
new_storage = WalletStorage(new_path)
|
||||||
if pin_code:
|
new_storage._encryption_version = self.storage._encryption_version
|
||||||
backup_pubkey = self.config.get('backup_pubkey')
|
new_storage.pubkey = self.storage.pubkey
|
||||||
if backup_pubkey is None:
|
|
||||||
return
|
|
||||||
w2 = Wallet(new_db, None, config=self.config)
|
|
||||||
w2.update_password(pin_code, None)
|
|
||||||
new_storage._encryption_version = StorageEncryptionVersion.USER_PASSWORD
|
|
||||||
new_storage.pubkey = backup_pubkey
|
|
||||||
else:
|
|
||||||
new_storage._encryption_version = self.storage._encryption_version
|
|
||||||
new_storage.pubkey = self.storage.pubkey
|
|
||||||
new_db.set_modified(True)
|
new_db.set_modified(True)
|
||||||
new_db.write(new_storage)
|
new_db.write(new_storage)
|
||||||
return new_path
|
return new_path
|
||||||
|
|||||||
Reference in New Issue
Block a user