88#
99# - ec_mult, ec_setup, aes_setup, mitm_verify
1010#
11- import hid , sys , os , platform
12- from binascii import b2a_hex , a2b_hex
11+ import hid , os , socket , atexit
12+ from binascii import b2a_hex
1313from hashlib import sha256
14- from .protocol import CCProtocolPacker , CCProtocolUnpacker , CCProtoError , MAX_MSG_LEN , MAX_BLK_LEN
14+ from .constants import USB_NCRY_V1 , USB_NCRY_V2
15+ from .protocol import CCProtocolPacker , CCProtocolUnpacker , CCProtoError , MAX_MSG_LEN
1516from .utils import decode_xpub , get_pubkey_string
1617
1718# unofficial, unpermissioned... USB numbers
1819COINKITE_VID = 0xd13e
1920CKCC_PID = 0xcc10
2021
21- # Unix domain socket used by the simulator
22- CKCC_SIMULATOR_PATH = '/tmp/ckcc-simulator.sock'
22+ DEFAULT_SIM_SOCKET = "/tmp/ckcc- simulator.sock"
23+
2324
2425class ColdcardDevice :
25- def __init__ (self , sn = None , dev = None , encrypt = True ):
26+ def __init__ (self , sn = None , dev = None , encrypt = True , ncry_ver = USB_NCRY_V1 , is_simulator = False ):
2627 # Establish connection via USB (HID) or Unix Pipe
27- self .is_simulator = False
28+ self .is_simulator = is_simulator
2829
29- if not dev and sn and '/' in sn :
30- if platform .system () == 'Windows' :
31- raise RuntimeError ("Cannot connect to simulator. Is it running?" )
30+ if not dev and ((sn and ('/' in sn )) or self .is_simulator ):
3231 dev = UnixSimulatorPipe (sn )
3332 found = 'simulator'
3433 self .is_simulator = True
@@ -49,7 +48,7 @@ def __init__(self, sn=None, dev=None, encrypt=True):
4948 break
5049
5150 if not dev :
52- raise KeyError ("Could not find Coldcard!"
51+ raise KeyError ("Could not find Coldcard!"
5352 if not sn else ('Cannot find CC with serial: ' + sn ))
5453 else :
5554 found = dev .get_serial_number_string ()
@@ -58,6 +57,7 @@ def __init__(self, sn=None, dev=None, encrypt=True):
5857 self .serial = found
5958
6059 # they will be defined after we've established a shared secret w/ device
60+ self .ncry_ver = ncry_ver
6161 self .session_key = None
6262 self .encrypt_request = None
6363 self .decrypt_response = None
@@ -67,7 +67,7 @@ def __init__(self, sn=None, dev=None, encrypt=True):
6767 self .resync ()
6868
6969 if encrypt :
70- self .start_encryption ()
70+ self .start_encryption (version = self . ncry_ver )
7171
7272 def close (self ):
7373 # close underlying HID device
@@ -101,17 +101,21 @@ def send_recv(self, msg, expect_errors=False, verbose=0, timeout=3000, encrypt=T
101101 # first byte of each 64-byte packet encodes length or packet-offset
102102 assert 4 <= len (msg ) <= MAX_MSG_LEN , "msg length: %d" % len (msg )
103103
104- if not self .encrypt_request :
104+ if self .encrypt_request is None :
105105 # disable encryption if not already enabled for this connection
106106 encrypt = False
107107
108+ if self .encrypt_request and self .ncry_ver == USB_NCRY_V2 :
109+ # ncry version 2 - everything needs to be encrypted
110+ encrypt = True
111+
108112 if encrypt :
109113 msg = self .encrypt_request (msg )
110114
111115 left = len (msg )
112116 offset = 0
113117 while left > 0 :
114- # Note: first byte always zero (HID report number),
118+ # Note: first byte always zero (HID report number),
115119 # [1] is framing header (length+flags)
116120 # [2:65] payload (63 bytes, perhaps including padding)
117121 here = min (63 , left )
@@ -224,7 +228,7 @@ def aes_setup(self, session_key):
224228 self .encrypt_request = pyaes .AESModeOfOperationCTR (session_key , pyaes .Counter (0 )).encrypt
225229 self .decrypt_response = pyaes .AESModeOfOperationCTR (session_key , pyaes .Counter (0 )).decrypt
226230
227- def start_encryption (self ):
231+ def start_encryption (self , version = USB_NCRY_V1 ):
228232 # setup encryption on the link
229233 # - pick our own key pair, IV for AES
230234 # - send IV and pubkey to device
@@ -233,10 +237,12 @@ def start_encryption(self):
233237
234238 pubkey = self .ec_setup ()
235239
236- msg = CCProtocolPacker .encrypt_start (pubkey )
240+ msg = CCProtocolPacker .encrypt_start (pubkey , version = version )
237241
238242 his_pubkey , fingerprint , xpub = self .send_recv (msg , encrypt = False )
239243
244+ self .ncry_ver = version
245+
240246 self .session_key = self .ec_mult (his_pubkey )
241247
242248 # capture some public details of remote side's master key
@@ -248,7 +254,6 @@ def start_encryption(self):
248254 self .aes_setup (self .session_key )
249255
250256 def mitm_verify (self , sig , expected_xpub ):
251- # If Pycoin is not available, do it using ecdsa
252257 from ecdsa import BadSignatureError , SECP256k1 , VerifyingKey
253258 # of the returned (pubkey, chaincode) tuple, chaincode is not used
254259 pubkey , _ = decode_xpub (expected_xpub )
@@ -318,42 +323,54 @@ def download_file(self, length, checksum, blksize=1024, file_number=1):
318323
319324 return data
320325
321- def hash_password (self , text_password ):
326+ def hash_password (self , text_password , v3 = False ):
322327 # Turn text password into a key for use in HSM auth protocol
328+ # - changed from pbkdf2_hmac_sha256 to pbkdf2_hmac_sha512 in version 4 of CC firmware
323329 from hashlib import pbkdf2_hmac , sha256
324330 from .constants import PBKDF2_ITER_COUNT
325331
326332 salt = sha256 (b'pepper' + self .serial .encode ('ascii' )).digest ()
327333
328- return pbkdf2_hmac ('sha256' , text_password , salt , PBKDF2_ITER_COUNT )
334+ return pbkdf2_hmac ('sha256' if v3 else 'sha512' , text_password , salt , PBKDF2_ITER_COUNT )[: 32 ]
329335
330336
331337class UnixSimulatorPipe :
332338 # Use a UNIX pipe to the simulator instead of a real USB connection.
333339 # - emulates the API of hidapi device object.
334340
335- def __init__ (self , path ):
336- import socket , atexit
341+ def __init__ (self , socket_path = None ):
342+ self . socket_path = socket_path or DEFAULT_SIM_SOCKET
337343 self .pipe = socket .socket (socket .AF_UNIX , socket .SOCK_DGRAM )
338344 try :
339- self .pipe .connect (path )
345+ self .pipe .connect (self . socket_path )
340346 except Exception :
341347 self .close ()
342348 raise RuntimeError ("Cannot connect to simulator. Is it running?" )
343349
344- instance = 0
345- while instance < 10 :
346- pn = '/tmp/ckcc-client-%d-%d.sock' % (os .getpid (), instance )
350+ last_err = None
351+ for instance in range (5 ):
352+ # if simulator has PID in socket path, client will have matching, or empty
353+ pn = '/tmp/ckcc-client%s-%d-%d.sock' % (self .get_sim_pid (), os .getpid (), instance )
347354 try :
348355 self .pipe .bind (pn ) # just needs any name
349356 break
350- except OSError :
351- instance += 1
357+ except OSError as err :
358+ last_err = err
359+ if os .path .exists (pn ):
360+ os .remove (pn )
352361 continue
362+ else :
363+ raise last_err # raise whatever was raised last in the loop
353364
354365 self .pipe_name = pn
355366 atexit .register (self .close )
356367
368+ def get_sim_pid (self ):
369+ # return str PID if any in socket_path
370+ if self .socket_path == DEFAULT_SIM_SOCKET :
371+ return ""
372+ return "-" + self .socket_path .split ("." )[0 ].split ("-" )[- 1 ]
373+
357374 def read (self , max_count , timeout_ms = None ):
358375 import socket
359376 if not timeout_ms :
@@ -383,7 +400,7 @@ def close(self):
383400 pass
384401
385402 def get_serial_number_string (self ):
386- return 'simulator'
403+ return 'F1' * 6
387404
388405
389- # EOF
406+ # EOF
0 commit comments