commit 5ae9365f7775fe6f235b6c2931abb1839c08c18a
parent 059beab700fcc4fe5cb979bae8c975ff6968554d
Author: ThomasV <thomasv@electrum.org>
Date: Tue, 4 Dec 2018 11:19:00 +0100
Merge pull request #4895 from benma/bitbox
plugins/digitalbitbox: compatibility with firmware v5.0.0
Diffstat:
1 file changed, 29 insertions(+), 7 deletions(-)
diff --git a/electrum/plugins/digitalbitbox/digitalbitbox.py b/electrum/plugins/digitalbitbox/digitalbitbox.py
@@ -4,7 +4,7 @@
#
try:
- from electrum.crypto import sha256d, EncodeAES_base64, DecodeAES_base64
+ from electrum.crypto import sha256d, EncodeAES_base64, EncodeAES_bytes, DecodeAES_bytes, hmac_oneshot
from electrum.bitcoin import (TYPE_ADDRESS, push_script, var_int, public_key_to_p2pkh,
is_address)
from electrum.bip32 import serialize_xpub, deserialize_xpub
@@ -30,6 +30,8 @@ try:
import base64
import os
import sys
+ import re
+ import hmac
DIGIBOX = True
except ImportError as e:
DIGIBOX = False
@@ -43,6 +45,14 @@ except ImportError as e:
def to_hexstr(s):
return binascii.hexlify(s).decode('ascii')
+
+def derive_keys(x):
+ h = sha256d(x)
+ h = hashlib.sha512(h).digest()
+ return (h[:32],h[32:])
+
+MIN_MAJOR_VERSION = 5
+
class DigitalBitbox_Client():
def __init__(self, plugin, hidDevice):
@@ -110,7 +120,6 @@ class DigitalBitbox_Client():
else:
raise Exception('no reply')
-
def dbb_has_password(self):
reply = self.hid_send_plain(b'{"ping":""}')
if 'ping' not in reply:
@@ -121,7 +130,6 @@ class DigitalBitbox_Client():
def stretch_key(self, key):
- import hmac
return to_hexstr(hashlib.pbkdf2_hmac('sha512', key.encode('utf-8'), b'Digital Bitbox', iterations = 20480))
@@ -158,6 +166,12 @@ class DigitalBitbox_Client():
def check_device_dialog(self):
+ match = re.search(r'v([0-9])+\.[0-9]+\.[0-9]+', self.dbb_hid.get_serial_number_string())
+ if match is None:
+ raise Exception("error detecting firmware version")
+ major_version = int(match.group(1))
+ if major_version < MIN_MAJOR_VERSION:
+ raise Exception("Please upgrade to the newest firmware using the BitBox Desktop app: https://shiftcrypto.ch/start")
# Set password if fresh device
if self.password is None and not self.dbb_has_password():
if not self.setupRunning:
@@ -393,13 +407,21 @@ class DigitalBitbox_Client():
def hid_send_encrypt(self, msg):
+ sha256_byte_len = 32
reply = ""
try:
- secret = sha256d(self.password)
- msg = EncodeAES_base64(secret, msg)
- reply = self.hid_send_plain(msg)
+ encryption_key, authentication_key = derive_keys(self.password)
+ msg = EncodeAES_bytes(encryption_key, msg)
+ hmac_digest = hmac_oneshot(authentication_key, msg, hashlib.sha256)
+ authenticated_msg = base64.b64encode(msg + hmac_digest)
+ reply = self.hid_send_plain(authenticated_msg)
if 'ciphertext' in reply:
- reply = DecodeAES_base64(secret, ''.join(reply["ciphertext"]))
+ b64_unencoded = bytes(base64.b64decode(''.join(reply["ciphertext"])))
+ reply_hmac = b64_unencoded[-sha256_byte_len:]
+ hmac_calculated = hmac_oneshot(authentication_key, b64_unencoded[:-sha256_byte_len], hashlib.sha256)
+ if not hmac.compare_digest(reply_hmac, hmac_calculated):
+ raise Exception("Failed to validate HMAC")
+ reply = DecodeAES_bytes(encryption_key, b64_unencoded[:-sha256_byte_len])
reply = to_string(reply, 'utf8')
reply = json.loads(reply)
if 'error' in reply: