Skip to content

Commit

Permalink
Merge branch 'harden-connection'
Browse files Browse the repository at this point in the history
  • Loading branch information
Toporin committed Jul 12, 2021
2 parents 3b33788 + b9ec6d1 commit 4414773
Show file tree
Hide file tree
Showing 7 changed files with 234 additions and 138 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
satochip_bridge/__pycache__/
satochip_bridge/satochip_bridge.ini
.venv/
dist/
contrib/build-linux/appimage/build/
Expand Down
6 changes: 5 additions & 1 deletion contrib/build-wine/build-electrum-git.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#!/bin/bash

NAME_ROOT= SatochipBridge #electrum
NAME_ROOT=SatochipBridge #electrum

# These settings probably don't need any change
export WINEPREFIX=/opt/wine64
Expand Down Expand Up @@ -33,6 +33,10 @@ popd
#$PYTHON -m pip install --no-warn-script-location -r "$CONTRIB"/deterministic-build/requirements.txt
# $PYTHON -m pip install --no-warn-script-location -r "$CONTRIB"/deterministic-build/requirements-hw.txt

#DEBUG
$PYTHON -m pip install --upgrade pip
#$PYTHON -m pip install cryptography==3.3.1

pushd $WINEPREFIX/drive_c/electrum
# see https://github.com/pypa/pip/issues/2195 -- pip makes a copy of the entire directory
info "Pip installing SatochipBrige. This might take a long time if the project folder is large."
Expand Down
4 changes: 3 additions & 1 deletion contrib/requirements/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ pyperclip==1.7.0
ecdsa==0.15
SimpleWebSocketServer==0.1.1
pyscard==1.9.9
pysatochip==0.11.2
pysatochip==0.12.3
#eth-keys==0.3.1
#eth-utils==1.8.4
cryptography==3.3.2
certifi

# GUI
PySimpleGUIQt==0.31.0
Expand Down
68 changes: 34 additions & 34 deletions satochip_bridge/Client.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,10 @@ def PIN_change_dialog(self, msg_oldpin, msg_newpin, msg_confirm, msg_error, msg_
########################################

def card_init_connect(self):
logger.info("ATR: "+str(self.cc.card_get_ATR()))
#response, sw1, sw2 = self.card_select() #TODO: remove?

# check setup
while(self.cc.card_present):
if (self.cc.card_present) and (self.cc.card_type == "Satochip"):
#logger.info("ATR: "+str(self.cc.card_get_ATR()))
(response, sw1, sw2, d)=self.cc.card_get_status()

# check version
Expand All @@ -142,8 +141,6 @@ def card_init_connect(self):
if (self.cc.needs_secure_channel):
self.cc.card_initiate_secure_channel()

break

# setup device (done only once)
else:
# PIN dialog
Expand Down Expand Up @@ -184,35 +181,38 @@ def card_init_connect(self):
return
#raise RuntimeError('Unable to setup the device with error code:'+hex(sw1)+' '+hex(sw2))

# verify pin:
try:
self.cc.card_verify_PIN()
except RuntimeError as ex:
logger.warning(repr(ex))
self.request('show_error', repr(ex))
return

# get authentikey
try:
authentikey=self.cc.card_bip32_get_authentikey()
except UninitializedSeedError:
# Option: setup 2-Factor-Authentication (2FA)
self.init_2FA()

# seed dialog...
(mnemonic, passphrase, seed)= self.seed_wizard()
if seed:
seed= list(seed)
authentikey= self.cc.card_bip32_import_seed(seed)
if authentikey:
self.request('show_success','Seed successfully imported to Satochip!')
hex_authentikey= authentikey.get_public_key_hex(compressed=True)
logger.info(f"Authentikey={hex_authentikey}")
else:
self.request('show_error','Error when importing seed to Satochip!')
else: #if cancel
self.request('show_message','Seed import cancelled!')

# verify pin:
try:
self.cc.card_verify_PIN()
except RuntimeError as ex:
logger.warning(repr(ex))
self.request('show_error', repr(ex))
return

# get authentikey
try:
authentikey=self.cc.card_bip32_get_authentikey()
except UninitializedSeedError:
# Option: setup 2-Factor-Authentication (2FA)
self.init_2FA()

# seed dialog...
(mnemonic, passphrase, seed)= self.seed_wizard()
if seed:
seed= list(seed)
authentikey= self.cc.card_bip32_import_seed(seed)
if authentikey:
self.request('show_success','Seed successfully imported to Satochip!')
hex_authentikey= authentikey.get_public_key_hex(compressed=True)
logger.info(f"Authentikey={hex_authentikey}")
else:
self.request('show_error','Error when importing seed to Satochip!')
else: #if cancel
self.request('show_message','Seed import cancelled!')

else:
self.request('show_error','No card found! Please insert a Satochip and try again...')


def init_2FA(self, from_backup=False):
logger.debug("In init_2FA")
Expand Down
103 changes: 65 additions & 38 deletions satochip_bridge/SatochipBridge.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@
import time
import logging
import sys
import os.path
#import traceback
from os import urandom

from SimpleWebSocketServer import SimpleWebSocketServer, WebSocket


from pysatochip.CardConnector import CardConnector, UninitializedSeedError
from pysatochip.JCconstants import JCconstants
from pysatochip.Satochip2FA import Satochip2FA
from pysatochip.version import SATOCHIP_PROTOCOL_MAJOR_VERSION, SATOCHIP_PROTOCOL_MINOR_VERSION, SATOCHIP_PROTOCOL_VERSION
from pysatochip.CardConnector import CardConnector #, UninitializedSeedError
from pysatochip.Satochip2FA import Satochip2FA, SERVER_LIST
#from pysatochip.JCconstants import JCconstants
#from pysatochip.version import SATOCHIP_PROTOCOL_MAJOR_VERSION, SATOCHIP_PROTOCOL_MINOR_VERSION, SATOCHIP_PROTOCOL_VERSION

# from Client import Client
# from handler import HandlerTxt, HandlerSimpleGUI
Expand Down Expand Up @@ -73,7 +72,23 @@ def handleMessage(self):
action= msg["action"]
except Exception as e:
logger.warning("exception: "+repr(e))
cc.client.request('show_error', "Exception while parsing request: \n"+repr(e) )

# check that card is present or wait
while (cc.card_present is not True) or (cc.card_type != "Satochip"):
(event, values)= cc.client.request('ok_or_cancel_msg','No card found! Please insert a Satochip to proceed...')
if event== 'Ok' :
continue
else:
# return failure code
logger.debug("Cancel the request...")
msg['exitstatus']=EXIT_FAILURE
msg['reason']='No card found'
reply= json.dumps(msg)
self.sendMessage(reply)
logger.debug("Reply (failure): "+reply)
return

try:
if (action=="get_status"):
response, sw1, sw2, status = cc.card_get_status()
Expand Down Expand Up @@ -116,21 +131,36 @@ def handleMessage(self):
logger.debug("encrypted message: "+msg_2FA)
logger.debug("id_2FA: "+ id_2FA)

#do challenge-response with 2FA device...
notif= '2FA request sent! Approve or reject request on your second device.'
cc.client.request('show_notification', notif)
#cc.client.request('show_message', notif)
Satochip2FA.do_challenge_response(d)
# decrypt and parse reply to extract challenge response
try:
# get server from config file
if os.path.isfile('satochip_bridge.ini'):
from configparser import ConfigParser
config = ConfigParser()
config.read('satochip_bridge.ini')
server_default= config.get('2FA', 'server_default')
else:
server_default= SERVER_LIST[0] # no config file => default server
#do challenge-response with 2FA device...
notif= '2FA request sent! Approve or reject request on your second device.'
cc.client.request('show_notification', notif)
#cc.client.request('show_message', notif)
Satochip2FA.do_challenge_response(d, server_default)
# decrypt and parse reply to extract challenge response
reply_encrypt= d['reply_encrypt']
reply_decrypt= cc.card_crypt_transaction_2FA(reply_encrypt, False)
except Exception as e:
cc.client.request('show_error', "No response received from 2FA...")
reply_decrypt= cc.card_crypt_transaction_2FA(reply_encrypt, False)
msg['exitstatus']=EXIT_FAILURE
msg['reason']= "No response received from 2FA"
reply= json.dumps(msg)
self.sendMessage(reply)
return
logger.debug("challenge:response= "+ reply_decrypt)
reply_decrypt= reply_decrypt.split(":")
chalresponse=reply_decrypt[1]
hmac= list(bytes.fromhex(chalresponse))
notif= 'Received response from 2FA device!'
cc.client.request('show_notification', notif)
else:
hmac=None
logger.debug("Skip confirmation for this action? "+ str(wallets[self]) )
Expand All @@ -153,6 +183,7 @@ def handleMessage(self):
reply= json.dumps(d)
self.sendMessage(reply)
logger.debug("Reply: "+reply)
return
else:
hash= list(bytes.fromhex(msg["hash"]))
(response, sw1, sw2)=cc.card_sign_transaction_hash(keynbr, hash, hmac)
Expand All @@ -171,23 +202,35 @@ def handleMessage(self):
logger.debug ("v= " + str(v))
except Exception as e:
logger.warning("Exception in parse_rsv_from_dersig: " + repr(e))

cc.client.request('show_error', "Exception in parse_rsv_from_dersig: " + repr(e))
msg['exitstatus']=EXIT_FAILURE
msg['reason']= "Exception in parse_rsv_from_dersig"
reply= json.dumps(msg)
self.sendMessage(reply)
return
d= {'requestID':msg["requestID"], 'action':msg["action"], "hash":msg["hash"],
"sig":sigstring.hex(), "r":r, "s":s, "v":v, "pubkey":pubkey.get_public_key_bytes().hex(),
'exitstatus':EXIT_SUCCESS}
reply= json.dumps(d)
self.sendMessage(reply)
logger.debug("Reply: "+reply)

return

else:
d= {'requestID':msg['requestID'], 'action':msg['action'], 'exitstatus':EXIT_FAILURE, 'reason':'Action unknown'}
reply= json.dumps(d)
self.sendMessage(reply)
logger.warning("Unknown action: "+action)

except Exception as e:
# return failure code
logger.warning('Exception: ' + repr(e))
msg['exitstatus']=EXIT_FAILURE
msg['reason']= repr(e)
reply= json.dumps(msg)
self.sendMessage(reply)
cc.client.request('show_error','[handleMessage] Exception: '+repr(e))
return
#traceback.print_exc()

#TODO: Only one connection at a time?
Expand Down Expand Up @@ -229,34 +272,18 @@ def handleConnected(self):
# self.close()
# return

try:
cc.client.card_init_connect()
except Exception as e:
cc.client.request('show_error','[handleConnected] Exception:'+repr(e))
logger.warning('Exception:'+repr(e))
# try:
# cc.card_disconnect()
# cc = CardConnector(parser)
# except Exception as e:
# print("In handleConnected(): exception")
# print(repr(e))

# We do not touch the card until we receive an actual request (in handleMessage)
# try:
# cc.client.card_init_connect()
# except Exception as e:
# cc.client.request('show_error','[handleConnected] Exception:'+repr(e))
# logger.warning('Exception:'+repr(e))

def handleClose(self):
global logger
wallets.pop(self)
logger.info(self.address + 'closed')


#debug
#cc.client.card_init_connect()
#cc.client.handler.seed_wizard()
#cc.client.handler.QRDialog(20*"00", None, "Satochip-Bridge: QR Code", True, "2FA: ")
#cc.client.handler.choose_seed_action()
#cc.client.handler.create_seed("AA BB CC DD EE FF")
#cc.client.handler.request_passphrase()
#cc.client.handler.confirm_seed()
#cc.client.handler.confirm_passphrase()
#cc.client.handler.restore_from_seed()

def my_threaded_func(server):
server.serveforever()
Expand Down
Loading

0 comments on commit 4414773

Please sign in to comment.