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
Example #2
0
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)