commit 730cbefeb15aa708b955d12088ce3b26d603c55f
parent dfef56491b7af5ded0b646f145a84a5b2fbe909d
Author: ThomasV <thomasv@electrum.org>
Date: Sat, 1 Oct 2016 11:45:43 +0200
Trustedcoin: add Google Authenticator reset
Diffstat:
2 files changed, 77 insertions(+), 25 deletions(-)
diff --git a/plugins/trustedcoin/qt.py b/plugins/trustedcoin/qt.py
@@ -237,11 +237,9 @@ class Plugin(TrustedCoinPlugin):
window.set_main_layout(vbox, next_enabled=False)
next_button.setText(prior_button_text)
-
return str(email_e.text())
-
- def setup_google_auth(self, window, _id, otp_secret):
+ def request_otp_dialog(self, window, _id, otp_secret):
vbox = QVBoxLayout()
if otp_secret is not None:
uri = "otpauth://totp/%s?secret=%s"%('trustedcoin.com', otp_secret)
@@ -255,7 +253,7 @@ class Plugin(TrustedCoinPlugin):
label = QLabel(
"This wallet is already registered with Trustedcoin. "
"To finalize wallet creation, please enter your Google Authenticator Code. "
- "If you do not have this code, delete the wallet file and start a new registration")
+ )
label.setWordWrap(1)
vbox.addWidget(label)
msg = _('Google Authenticator code:')
@@ -268,18 +266,20 @@ class Plugin(TrustedCoinPlugin):
hbox.addWidget(pw)
vbox.addLayout(hbox)
+ cb_lost = QCheckBox(_("I have lost my Google Authenticator account"))
+ cb_lost.setToolTip(_("Check this box to request a new secret. You will need to retype your seed."))
+ vbox.addWidget(cb_lost)
+ cb_lost.setVisible(otp_secret is None)
+
def set_enabled():
- window.next_button.setEnabled(len(pw.text()) == 6)
+ b = True if cb_lost.isChecked() else len(pw.text()) == 6
+ window.next_button.setEnabled(b)
+
pw.textChanged.connect(set_enabled)
+ cb_lost.toggled.connect(set_enabled)
+
+ window.set_main_layout(vbox, next_enabled=False,
+ raise_on_cancel=False)
+ return pw.get_amount(), cb_lost.isChecked()
+
- while True:
- if not window.set_main_layout(vbox, next_enabled=False,
- raise_on_cancel=False):
- return False
- otp = pw.get_amount()
- try:
- server.auth(_id, otp)
- return True
- except:
- window.show_message(_('Incorrect password'))
- pw.setText('')
diff --git a/plugins/trustedcoin/trustedcoin.py b/plugins/trustedcoin/trustedcoin.py
@@ -74,9 +74,9 @@ class TrustedCoinException(Exception):
self.status_code = status_code
class TrustedCoinCosignerClient(object):
- def __init__(self, user_agent=None, base_url='https://api.trustedcoin.com/2/', debug=False):
+ def __init__(self, user_agent=None, base_url='https://api.trustedcoin.com/2/'):
self.base_url = base_url
- self.debug = debug
+ self.debug = False
self.user_agent = user_agent
def send_request(self, method, relative_url, data=None):
@@ -141,13 +141,18 @@ class TrustedCoinCosignerClient(object):
return self.send_request('post', 'cosigner/%s/auth' % quote(id), payload)
def get(self, id):
- """
- Attempt to authenticate for a particular cosigner.
- :param id: the id of the cosigner
- :param otp: the one time password
- """
+ """ Get billing info """
return self.send_request('get', 'cosigner/%s' % quote(id))
+ def get_challenge(self, id):
+ """ Get challenge to reset Google Auth secret """
+ return self.send_request('get', 'cosigner/%s/otp_secret' % quote(id))
+
+ def reset_auth(self, id, challenge, signatures):
+ """ Reset Google Auth secret """
+ payload = {'challenge':challenge, 'signatures':signatures}
+ return self.send_request('post', 'cosigner/%s/otp_secret' % quote(id), payload)
+
def sign(self, id, transaction, otp):
"""
Attempt to authenticate for a particular cosigner.
@@ -478,8 +483,27 @@ class TrustedCoinPlugin(BasePlugin):
except Exception as e:
wizard.show_message(str(e))
return
- if not self.setup_google_auth(wizard, short_id, otp_secret):
- wizard.show_message("otp error")
+ self.check_otp(wizard, short_id, otp_secret, xpub3)
+
+ def check_otp(self, wizard, short_id, otp_secret, xpub3):
+ otp, reset = self.request_otp_dialog(wizard, short_id, otp_secret)
+ if otp:
+ self.do_auth(wizard, short_id, otp, xpub3)
+ elif reset:
+ wizard.opt_bip39 = False
+ wizard.opt_ext = True
+ f = lambda seed, is_bip39, is_ext: wizard.run('on_reset_seed', short_id, seed, is_ext, xpub3)
+ wizard.restore_seed_dialog(run_next=f, test=self.is_valid_seed)
+
+ def on_reset_seed(self, wizard, short_id, seed, is_ext, xpub3):
+ f = lambda passphrase: wizard.run('on_reset_auth', short_id, seed, passphrase, xpub3)
+ wizard.passphrase_dialog(run_next=f) if is_ext else f('')
+
+ def do_auth(self, wizard, short_id, otp, xpub3):
+ try:
+ server.auth(short_id, otp)
+ except:
+ wizard.show_message(_('Incorrect password'))
return
k3 = keystore.from_xpub(xpub3)
wizard.storage.put('x3/', k3.dump())
@@ -488,6 +512,34 @@ class TrustedCoinPlugin(BasePlugin):
wizard.wallet = Wallet_2fa(wizard.storage)
wizard.run('create_addresses')
+ def on_reset_auth(self, wizard, short_id, seed, passphrase, xpub3):
+ xprv1, xpub1, xprv2, xpub2 = self.xkeys_from_seed(seed, passphrase)
+ try:
+ assert xpub1 == wizard.storage.get('x1/')['xpub']
+ assert xpub2 == wizard.storage.get('x2/')['xpub']
+ except:
+ wizard.show_message(_('Incorrect seed'))
+ return
+ r = server.get_challenge(short_id)
+ challenge = r.get('challenge')
+ message = 'TRUSTEDCOIN CHALLENGE: ' + challenge
+ def f(xprv):
+ from electrum.bitcoin import deserialize_xkey, bip32_private_key, regenerate_key, is_compressed
+ _, _, _, c, k = deserialize_xkey(xprv)
+ pk = bip32_private_key([0, 0], k, c)
+ key = regenerate_key(pk)
+ compressed = is_compressed(pk)
+ sig = key.sign_message(message, compressed)
+ return base64.b64encode(sig)
+
+ signatures = [f(x) for x in [xprv1, xprv2]]
+ r = server.reset_auth(short_id, challenge, signatures)
+ new_secret = r.get('otp_secret')
+ if not new_secret:
+ wizard.show_message(_('Request rejected by server'))
+ return
+ self.check_otp(wizard, short_id, new_secret, xpub3)
+
@hook
def get_action(self, storage):
if storage.get('wallet_type') != '2fa':