electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 730cbefeb15aa708b955d12088ce3b26d603c55f
parent dfef56491b7af5ded0b646f145a84a5b2fbe909d
Author: ThomasV <thomasv@electrum.org>
Date:   Sat,  1 Oct 2016 11:45:43 +0200

Trustedcoin: add Google Authenticator reset

Diffstat:
Mplugins/trustedcoin/qt.py | 32++++++++++++++++----------------
Mplugins/trustedcoin/trustedcoin.py | 70+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------
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':