def write_file_encrypted(file_name: str, hw_session: HwSessionInfo, data: bytes): label = os.path.basename(file_name) if hw_session.app_config.hw_type: if not hw_session.hw_client: if not hw_session.hw_connect(): return else: raise Exception( 'Invalid hardware wallet type in the app configuration.') protocol, hw_type_bin, bip32_path_n, encryption_key, encrypted_key_bin, pub_key_hash = \ prepare_hw_encryption_attrs(hw_session, label) fer = Fernet(encryption_key) with open(file_name, 'wb') as f_ptr: header = DMT_ENCRYPTED_DATA_PREFIX + \ num_to_varint(protocol) + num_to_varint(hw_type_bin) + \ write_bytes_buf(bytearray(base64.b64encode(bytearray(label, 'utf-8')))) + \ write_bytes_buf(encrypted_key_bin) + \ write_int_list_buf(bip32_path_n) + \ write_bytes_buf(pub_key_hash) f_ptr.write(header) # slice the input data into ENC_FILE_BLOCK_SIZE-byte chunks, encrypt them and # write to file; each block will be preceded with the length of the encrypted # data chunk size begin_idx = 0 while True: data_left = len(data) - begin_idx if data_left <= 0: break cur_input_chunk_size = min(ENC_FILE_BLOCK_SIZE, data_left) data_enc_base64 = fer.encrypt(data[begin_idx:begin_idx + cur_input_chunk_size]) data_enc = base64.urlsafe_b64decode(data_enc_base64) cur_chunk_size_bin = len(data_enc).to_bytes( 8, byteorder='little') # write the size of the data chunk f_ptr.write(cur_chunk_size_bin) # write the data f_ptr.write(data_enc) # write the data begin_idx += cur_input_chunk_size
def sign_message(hw_session: HwSessionInfo, bip32_path, message): client = hw_session.hw_client # Ledger doesn't accept characters other that ascii printable: # https://ledgerhq.github.io/btchip-doc/bitcoin-technical.html#_sign_message message = message.encode('ascii', 'ignore') bip32_path = clean_bip32_path(bip32_path) ok = False for i in range(1, 4): info = client.signMessagePrepare(bip32_path, message) if info['confirmationNeeded'] and info['confirmationType'] == 34: if i == 1 or \ WndUtils.queryDlg('Another application (such as Ledger Wallet Bitcoin app) has probably taken over ' 'the communication with the Ledger device.' '\n\nTo continue, close that application and click the <b>Retry</b> button.' '\nTo cancel, click the <b>Abort</b> button', buttons=QMessageBox.Retry | QMessageBox.Abort, default_button=QMessageBox.Retry, icon=QMessageBox.Warning) == QMessageBox.Retry: # we need to reconnect the device; first, we'll try to reconnect to HW without closing the intefering # application; it it doesn't help we'll display a message requesting the user to close the app hw_session.hw_disconnect() if hw_session.hw_connect(): client = hw_session.hw_client else: raise Exception('Hardware wallet reconnect error.') else: break else: ok = True break if not ok: raise CancelException('Cancelled') try: signature = client.signMessageSign() except Exception as e: logging.exception('Exception while signing message with Ledger Nano S') raise Exception( 'Exception while signing message with Ledger Nano S. Details: ' + str(e)) try: pubkey = client.getWalletPublicKey(bip32_path) except Exception as e: logging.exception( 'Could not get public key for BIP32 path from Ledger Nano S') raise Exception( 'Could not get public key for BIP32 path from Ledger Nano S. Details: ' + str(e)) if len(signature) > 4: r_length = signature[3] r = signature[4:4 + r_length] if len(signature) > 4 + r_length + 1: s_length = signature[4 + r_length + 1] if len(signature) > 4 + r_length + 2: s = signature[4 + r_length + 2:] if r_length == 33: r = r[1:] if s_length == 33: s = s[1:] else: logging.error( 'client.signMessageSign() returned invalid response (code 3): ' + signature.hex()) raise Exception('Invalid signature returned (code 3).') else: logging.error( 'client.signMessageSign() returned invalid response (code 2): ' + signature.hex()) raise Exception('Invalid signature returned (code 2).') else: logging.error( 'client.signMessageSign() returned invalid response (code 1): ' + signature.hex()) raise Exception('Invalid signature returned (code 1).') return MessageSignature( pubkey.get('address').decode('ascii'), bytes(chr(27 + 4 + (signature[0] & 0x01)), "utf-8") + r + s)
def read_file_encrypted(file_name: str, ret_attrs: dict, hw_session: HwSessionInfo): ret_attrs['encrypted'] = False hw_client_internal = None try: with open(file_name, 'rb') as f_ptr: data = f_ptr.read(len(DMT_ENCRYPTED_DATA_PREFIX)) if data == DMT_ENCRYPTED_DATA_PREFIX: ret_attrs['encrypted'] = True protocol = read_varint_from_file(f_ptr) if protocol == 1: # with Trezor method + Fernet hw_type_bin = read_varint_from_file(f_ptr) hw_type = { 1: HWType.trezor, 2: HWType.keepkey, 3: HWType.ledger_nano_s }.get(hw_type_bin) if hw_type: if hw_session.app_config.hw_type == hw_type: if not hw_session.hw_client: if not hw_session.hw_connect(): raise NotConnectedToHardwareWallet( f'This file was encrypted with {HWType.get_desc(hw_type)} hardware wallet, ' f'which has to be connected to the computer decrypt the file.' ) else: # enctypted file uses other type of hardware wallet than the one currently connected - # create a separate (temporary) hw session for it def _get_client(): return hw_client_internal try: hw_session = HwSessionInfo( get_hw_client_function=_get_client, hw_connect_function=None, hw_disconnect_function=None, app_config=hw_session.app_config, fixd_intf=hw_session.fixd_intf) hw_client_internal = connect_hw( hw_session=hw_session, device_id=None, passphrase_encoding='NFKD', hw_type=hw_type) except Exception: raise data_label_bin = read_bytes_from_file(f_ptr) label = base64.urlsafe_b64decode( data_label_bin).decode('utf-8') encrypted_key_bin = read_bytes_from_file(f_ptr) bip32_path_n = read_int_list_from_file(f_ptr) pub_key_hash_hdr = read_bytes_from_file(f_ptr) while True: if hw_session.hw_type in (HWType.trezor, HWType.keepkey): key_bin, pub_key = hw_decrypt_value( hw_session, bip32_path_n, label=label, value=encrypted_key_bin) elif hw_session.hw_type == HWType.ledger_nano_s: display_label = f'<b>Click the sign message confirmation button on the <br>' \ f'hardware wallet to decrypt \'{label}\'.</b>' bip32_path_str = bip32_path_n_to_string( bip32_path_n) sig = hw_sign_message( hw_session, bip32_path_str, encrypted_key_bin.hex(), display_label=display_label) adr_pk = get_address_and_pubkey( hw_session, bip32_path_str) pub_key = adr_pk.get('publicKey') key_bin = SHA256.new(sig.signature).digest() else: raise Exception( 'Invalid hardware wallet type.') pub_key_hash = SHA256.new(pub_key).digest() if pub_key_hash_hdr == pub_key_hash: break url = get_note_url('DMT0003') if WndUtils.queryDlg( message= 'Inconsistency between encryption and decryption keys.\n\n' 'The reason may be using a different passphrase than it was used ' 'for encryption or running another application communicating with the ' 'device simultaneously, like Trezor web wallet (see <a href="{url}">' 'here</a>).\n\n' 'Do you want to try again?', buttons=QMessageBox.Yes | QMessageBox.Cancel, default_button=QMessageBox.Cancel, icon=QMessageBox.Warning ) == QMessageBox.Cancel: raise CancelException('User cancelled.') if hw_client_internal: disconnect_hw(hw_client_internal) hw_client_internal = connect_hw( hw_session=hw_session, device_id=None, passphrase_encoding='NFKD', hw_type=hw_type) else: hw_session.hw_disconnect() key = base64.urlsafe_b64encode(key_bin) fer = Fernet(key) while True: # data is written in blocks; if front of each block there is a block size value data_bin = f_ptr.read(8) if len(data_bin) == 0: break # end of file elif len(data_bin) < 8: raise ValueError( 'File end before read completed.') data_chunk_size = int.from_bytes( data_bin, byteorder='little') if data_chunk_size < 0 or data_chunk_size > 2000000000: raise ValueError( 'Data corrupted: invalid data chunk size.') data_bin = f_ptr.read(data_chunk_size) if data_chunk_size != len(data_bin): raise ValueError( 'File end before read completed.') data_base64 = base64.urlsafe_b64encode(data_bin) try: data_decr = fer.decrypt(data_base64) except InvalidToken: raise Exception( 'Couldn\'t decrypt file (IvalidToken error). The file is probably ' 'corrupted or is encrypted with a different encryption method.' ) yield data_decr else: raise ValueError('Invalid hardware wallet type value.') else: raise ValueError('Invalid protocol value.') else: # the data inside the file isn't encrypted # read and yield raw data while True: # data is written in blocks; if front of each block there is a block size value data += f_ptr.read(ENC_FILE_BLOCK_SIZE) if not len(data): break yield data data = bytes() finally: if hw_client_internal: disconnect_hw(hw_client_internal)