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