From 2ffe53d9e668c1993fc6f1cbcd7d74c895a5fbb7 Mon Sep 17 00:00:00 2001 From: Dusan Klinec Date: Mon, 5 Nov 2018 00:38:58 +0100 Subject: [PATCH] device/trezor: webusb transport added, cmake fixes - webusb transport based on libusb added. Provides direct access to Trezor via USB, no need for Trezor bridge. - trezor protocol message handler improved, no recursion used. Ready for upcoming integration tests. - libusb (for docker) bumped from v1.0.9 to v1.0.22, newer version required for webusb transport, for device enumeration. - cmake improvements and fixes. Cmake Trezor checks are moved to a dedicated CheckTrezor.cmake file. In case of a problem Trezor is excluded from build. - ifdefs made consistent to Ledger. - UDP Transport enumeration disabled by default in release mode --- CMakeLists.txt | 13 +- cmake/CheckTrezor.cmake | 79 +++++ cmake/FindLibUSB.cmake | 140 ++++++++ cmake/test-libusb-version.c | 52 +++ contrib/depends/packages/libusb.mk | 8 +- src/device_trezor/CMakeLists.txt | 42 +-- src/device_trezor/device_trezor.cpp | 3 +- src/device_trezor/device_trezor.hpp | 2 +- src/device_trezor/device_trezor_base.cpp | 127 ++++--- src/device_trezor/device_trezor_base.hpp | 112 +++---- src/device_trezor/trezor.hpp | 2 +- src/device_trezor/trezor/tools/README.md | 27 +- src/device_trezor/trezor/tools/pb2cpp.py | 61 +++- src/device_trezor/trezor/transport.cpp | 403 ++++++++++++++++++++++- src/device_trezor/trezor/transport.hpp | 67 ++++ src/device_trezor/trezor/trezor_defs.hpp | 39 ++- 16 files changed, 971 insertions(+), 206 deletions(-) create mode 100644 cmake/CheckTrezor.cmake create mode 100644 cmake/FindLibUSB.cmake create mode 100644 cmake/test-libusb-version.c diff --git a/CMakeLists.txt b/CMakeLists.txt index 0c38c673a..d942d31cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -513,15 +513,8 @@ else (HIDAPI_FOUND) message(STATUS "Could not find HIDAPI") endif() -# Protobuf, optional. Required for TREZOR. -include(FindProtobuf) -find_package(Protobuf) -if(Protobuf_FOUND) - set(HAVE_PROTOBUF 1) - add_definitions(-DHAVE_PROTOBUF=1) -else(Protobuf_FOUND) - message(STATUS "Could not find Protobuf") -endif() +# Trezor support check +include(CheckTrezor) if(MSVC) add_definitions("/bigobj /MP /W3 /GS- /D_CRT_SECURE_NO_WARNINGS /wd4996 /wd4345 /D_WIN32_WINNT=0x0600 /DWIN32_LEAN_AND_MEAN /DGTEST_HAS_TR1_TUPLE=0 /FIinline_c.h /D__SSE4_1__") @@ -921,7 +914,7 @@ endif() list(APPEND EXTRA_LIBRARIES ${CMAKE_DL_LIBS}) -if (HIDAPI_FOUND) +if (HIDAPI_FOUND OR LibUSB_COMPILE_TEST_PASSED) if (APPLE) if(DEPENDS) list(APPEND EXTRA_LIBRARIES "-framework Foundation -framework IOKit") diff --git a/cmake/CheckTrezor.cmake b/cmake/CheckTrezor.cmake new file mode 100644 index 000000000..ea21237fd --- /dev/null +++ b/cmake/CheckTrezor.cmake @@ -0,0 +1,79 @@ +OPTION(USE_DEVICE_TREZOR "Trezor support compilation" ON) +OPTION(USE_DEVICE_TREZOR_LIBUSB "Trezor LibUSB compilation" ON) +OPTION(USE_DEVICE_TREZOR_UDP_RELEASE "Trezor UdpTransport in release mode" OFF) + +# Use Trezor master switch +if (USE_DEVICE_TREZOR) + # Protobuf is required to build protobuf messages for Trezor + include(FindProtobuf OPTIONAL) + find_package(Protobuf) + if(NOT Protobuf_FOUND) + message(STATUS "Could not find Protobuf") + endif() + +else() + message(STATUS "Trezor support disabled by USE_DEVICE_TREZOR") +endif() + +if(Protobuf_FOUND AND USE_DEVICE_TREZOR) + if (NOT "$ENV{TREZOR_PYTHON}" STREQUAL "") + set(TREZOR_PYTHON "$ENV{TREZOR_PYTHON}" CACHE INTERNAL "Copied from environment variable TREZOR_PYTHON") + else() + find_package(Python QUIET COMPONENTS Interpreter) # cmake 3.12+ + if(Python_Interpreter_FOUND) + set(TREZOR_PYTHON "${Python_EXECUTABLE}") + endif() + endif() + + if(NOT TREZOR_PYTHON) + find_package(PythonInterp) + if(PYTHONINTERP_FOUND AND PYTHON_EXECUTABLE) + set(TREZOR_PYTHON "${PYTHON_EXECUTABLE}") + endif() + endif() + + if(NOT TREZOR_PYTHON) + message(STATUS "Trezor: Python not found") + endif() +endif() + +# Try to build protobuf messages +if(Protobuf_FOUND AND USE_DEVICE_TREZOR AND TREZOR_PYTHON) + set(ENV{PROTOBUF_INCLUDE_DIRS} "${Protobuf_INCLUDE_DIRS}") + set(ENV{PROTOBUF_PROTOC_EXECUTABLE} "${Protobuf_PROTOC_EXECUTABLE}") + execute_process(COMMAND ${TREZOR_PYTHON} tools/build_protob.py WORKING_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/../src/device_trezor/trezor RESULT_VARIABLE RET OUTPUT_VARIABLE OUT ERROR_VARIABLE ERR) + if(RET) + message(WARNING "Trezor protobuf messages could not be regenerated (err=${RET}, python ${PYTHON})." + "OUT: ${OUT}, ERR: ${ERR}." + "Please read src/device_trezor/trezor/tools/README.md") + else() + message(STATUS "Trezor protobuf messages regenerated ${OUT}") + set(DEVICE_TREZOR_READY 1) + add_definitions(-DDEVICE_TREZOR_READY=1) + + if(CMAKE_BUILD_TYPE STREQUAL "Debug") + add_definitions(-DTREZOR_DEBUG=1) + endif() + + if(USE_DEVICE_TREZOR_UDP_RELEASE) + add_definitions(-DWITH_DEVICE_TREZOR_UDP_RELEASE=1) + endif() + + if (Protobuf_INCLUDE_DIR) + include_directories(${Protobuf_INCLUDE_DIR}) + endif() + + # LibUSB support, check for particular version + # Include support only if compilation test passes + if (USE_DEVICE_TREZOR_LIBUSB) + find_package(LibUSB) + endif() + + if (LibUSB_COMPILE_TEST_PASSED) + add_definitions(-DHAVE_TREZOR_LIBUSB=1) + if(LibUSB_INCLUDE_DIRS) + include_directories(${LibUSB_INCLUDE_DIRS}) + endif() + endif() + endif() +endif() diff --git a/cmake/FindLibUSB.cmake b/cmake/FindLibUSB.cmake new file mode 100644 index 000000000..7e3bf156e --- /dev/null +++ b/cmake/FindLibUSB.cmake @@ -0,0 +1,140 @@ +# - Find libusb for portable USB support +# This module will find libusb as published by +# http://libusb.sf.net and +# http://libusb-win32.sf.net +# +# It will use PkgConfig if present and supported, else search +# it on its own. If the LibUSB_ROOT_DIR environment variable +# is defined, it will be used as base path. +# The following standard variables get defined: +# LibUSB_FOUND: true if LibUSB was found +# LibUSB_HEADER_FILE: the location of the C header file +# LibUSB_INCLUDE_DIRS: the directory that contains the include file +# LibUSB_LIBRARIES: the library +# source: https://github.com/IntelRealSense/librealsense + +include ( CheckLibraryExists ) +include ( CheckIncludeFile ) + +find_package ( PkgConfig ) +if ( PKG_CONFIG_FOUND ) + pkg_check_modules ( PKGCONFIG_LIBUSB libusb-1.0 ) + if ( NOT PKGCONFIG_LIBUSB_FOUND ) + pkg_check_modules ( PKGCONFIG_LIBUSB libusb ) + endif ( NOT PKGCONFIG_LIBUSB_FOUND ) +endif ( PKG_CONFIG_FOUND ) + +if ( PKGCONFIG_LIBUSB_FOUND ) + set ( LibUSB_INCLUDE_DIRS ${PKGCONFIG_LIBUSB_INCLUDE_DIRS} ) + foreach ( i ${PKGCONFIG_LIBUSB_LIBRARIES} ) + string ( REGEX MATCH "[^-]*" ibase "${i}" ) + find_library ( ${ibase}_LIBRARY + NAMES ${i} + PATHS ${PKGCONFIG_LIBUSB_LIBRARY_DIRS} + ) + if ( ${ibase}_LIBRARY ) + list ( APPEND LibUSB_LIBRARIES ${${ibase}_LIBRARY} ) + endif ( ${ibase}_LIBRARY ) + mark_as_advanced ( ${ibase}_LIBRARY ) + endforeach ( i ) + +else ( PKGCONFIG_LIBUSB_FOUND ) + find_file ( LibUSB_HEADER_FILE + NAMES + libusb.h usb.h + PATHS + $ENV{ProgramFiles}/LibUSB-Win32 + $ENV{LibUSB_ROOT_DIR} + PATH_SUFFIXES + include + libusb-1.0 + include/libusb-1.0 + ) + mark_as_advanced ( LibUSB_HEADER_FILE ) + get_filename_component ( LibUSB_INCLUDE_DIRS "${LibUSB_HEADER_FILE}" PATH ) + + if ( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) + # LibUSB-Win32 binary distribution contains several libs. + # Use the lib that got compiled with the same compiler. + if ( MSVC ) + if ( WIN32 ) + set ( LibUSB_LIBRARY_PATH_SUFFIX lib/msvc ) + else ( WIN32 ) + set ( LibUSB_LIBRARY_PATH_SUFFIX lib/msvc_x64 ) + endif ( WIN32 ) + elseif ( BORLAND ) + set ( LibUSB_LIBRARY_PATH_SUFFIX lib/bcc ) + elseif ( CMAKE_COMPILER_IS_GNUCC ) + set ( LibUSB_LIBRARY_PATH_SUFFIX lib/gcc ) + endif ( MSVC ) + endif ( ${CMAKE_SYSTEM_NAME} STREQUAL "Windows" ) + + find_library ( usb_LIBRARY + NAMES + usb-1.0 libusb usb + PATHS + $ENV{ProgramFiles}/LibUSB-Win32 + $ENV{LibUSB_ROOT_DIR} + PATH_SUFFIXES + ${LibUSB_LIBRARY_PATH_SUFFIX} + ) + mark_as_advanced ( usb_LIBRARY ) + if ( usb_LIBRARY ) + set ( LibUSB_LIBRARIES ${usb_LIBRARY} ) + endif ( usb_LIBRARY ) + +endif ( PKGCONFIG_LIBUSB_FOUND ) + +if ( LibUSB_INCLUDE_DIRS AND LibUSB_LIBRARIES ) + set ( LibUSB_FOUND true ) +endif ( LibUSB_INCLUDE_DIRS AND LibUSB_LIBRARIES ) + +if ( LibUSB_FOUND ) + set ( CMAKE_REQUIRED_INCLUDES "${LibUSB_INCLUDE_DIRS}" ) + check_include_file ( "${LibUSB_HEADER_FILE}" LibUSB_FOUND ) +endif ( LibUSB_FOUND ) + +if ( LibUSB_FOUND ) + check_library_exists ( "${LibUSB_LIBRARIES}" usb_open "" LibUSB_FOUND ) + check_library_exists ( "${LibUSB_LIBRARIES}" libusb_get_device_list "" LibUSB_VERSION_1.0 ) + check_library_exists ( "${LibUSB_LIBRARIES}" libusb_get_port_numbers "" LibUSB_VERSION_1.0.16 ) + + # Library 1.0.16+ compilation test. + # The check_library_exists does not work well on Apple with shared libs. + if (APPLE OR LibUSB_VERSION_1.0.16) + if (APPLE) + if(DEPENDS) + list(APPEND TEST_COMPILE_EXTRA_LIBRARIES "-framework Foundation -framework IOKit") + else() + find_library(COREFOUNDATION CoreFoundation) + find_library(IOKIT IOKit) + list(APPEND TEST_COMPILE_EXTRA_LIBRARIES ${IOKIT}) + list(APPEND TEST_COMPILE_EXTRA_LIBRARIES ${COREFOUNDATION}) + endif() + endif() + if (WIN32) + list(APPEND TEST_COMPILE_EXTRA_LIBRARIES setupapi) + endif() + list(APPEND TEST_COMPILE_EXTRA_LIBRARIES ${LibUSB_LIBRARIES}) + + try_compile(LibUSB_COMPILE_TEST_PASSED + ${CMAKE_BINARY_DIR} + "${CMAKE_SOURCE_DIR}/cmake/test-libusb-version.c" + CMAKE_FLAGS + "-DINCLUDE_DIRECTORIES=${LibUSB_INCLUDE_DIRS}" + "-DLINK_DIRECTORIES=${LibUSB_LIBRARIES}" + LINK_LIBRARIES ${TEST_COMPILE_EXTRA_LIBRARIES} + OUTPUT_VARIABLE OUTPUT) + unset(TEST_COMPILE_EXTRA_LIBRARIES) + message(STATUS "LibUSB Compilation test: ${LibUSB_COMPILE_TEST_PASSED}") + endif() +endif ( LibUSB_FOUND ) + +if ( NOT LibUSB_FOUND ) + if ( NOT LibUSB_FIND_QUIETLY ) + message ( STATUS "LibUSB not found, try setting LibUSB_ROOT_DIR environment variable." ) + endif ( NOT LibUSB_FIND_QUIETLY ) + if ( LibUSB_FIND_REQUIRED ) + message ( FATAL_ERROR "" ) + endif ( LibUSB_FIND_REQUIRED ) +endif ( NOT LibUSB_FOUND ) diff --git a/cmake/test-libusb-version.c b/cmake/test-libusb-version.c new file mode 100644 index 000000000..309e4ad27 --- /dev/null +++ b/cmake/test-libusb-version.c @@ -0,0 +1,52 @@ +// Copyright (c) 2014-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#define UNUSED(expr) (void)(expr) + +int main(int argc, char *argv[]) { + libusb_device **devs; + libusb_context *ctx = NULL; + + int r = libusb_init(&ctx); UNUSED(r); + ssize_t cnt = libusb_get_device_list(ctx, &devs); UNUSED(cnt); + + struct libusb_device_descriptor desc; + r = libusb_get_device_descriptor(devs[0], &desc); UNUSED(r); + uint8_t bus_id = libusb_get_bus_number(devs[0]); UNUSED(bus_id); + uint8_t addr = libusb_get_device_address(devs[0]); UNUSED(addr); + + uint8_t tmp_path[16]; + r = libusb_get_port_numbers(devs[0], tmp_path, sizeof(tmp_path)); + UNUSED(r); + UNUSED(tmp_path); + + libusb_free_device_list(devs, 1); + libusb_exit(ctx); +} diff --git a/contrib/depends/packages/libusb.mk b/contrib/depends/packages/libusb.mk index 47f8b3cbc..e9663ace0 100644 --- a/contrib/depends/packages/libusb.mk +++ b/contrib/depends/packages/libusb.mk @@ -1,8 +1,8 @@ package=libusb -$(package)_version=1.0.9 -$(package)_download_path=http://sourceforge.net/projects/libusb/files/libusb-1.0/libusb-1.0.9/ +$(package)_version=1.0.22 +$(package)_download_path=http://sourceforge.net/projects/libusb/files/libusb-1.0/libusb-$($(package)_version)/ $(package)_file_name=$(package)-$($(package)_version).tar.bz2 -$(package)_sha256_hash=e920eedc2d06b09606611c99ec7304413c6784cba6e33928e78243d323195f9b +$(package)_sha256_hash=75aeb9d59a4fdb800d329a545c2e6799f732362193b465ea198f2aa275518157 define $(package)_preprocess_cmds autoreconf -i @@ -10,7 +10,7 @@ endef define $(package)_set_vars $(package)_config_opts=--disable-shared - $(package)_config_opts_linux=--with-pic + $(package)_config_opts_linux=--with-pic --disable-udev endef define $(package)_config_cmds diff --git a/src/device_trezor/CMakeLists.txt b/src/device_trezor/CMakeLists.txt index c555e9fcd..b27c843b6 100644 --- a/src/device_trezor/CMakeLists.txt +++ b/src/device_trezor/CMakeLists.txt @@ -63,37 +63,20 @@ set(trezor_sources set(trezor_private_headers) -include(FindProtobuf) -find_package(Protobuf) # REQUIRED - -# Test for HAVE_PROTOBUF from the parent -if(Protobuf_FOUND AND HAVE_PROTOBUF) - if ("$ENV{PYTHON3}" STREQUAL "") - set(PYTHON3 "python3") - else() - set(PYTHON3 "$ENV{PYTHON3}" CACHE INTERNAL "Copied from environment variable") - endif() - - execute_process(COMMAND ${PYTHON3} tools/build_protob.py WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/trezor RESULT_VARIABLE RET OUTPUT_VARIABLE OUT ERROR_VARIABLE ERR) - if(RET) - message(WARNING "Trezor protobuf messages could not be regenerated (err=${RET}, python ${PYTHON})." - "OUT: ${OUT}, ERR: ${ERR}." - "Please read src/device_trezor/trezor/tools/README.md") - else() - message(STATUS "Trezor protobuf messages regenerated ${OUT}") - set(TREZOR_PROTOBUF_GENERATED 1) - endif() -endif() - - -if(TREZOR_PROTOBUF_GENERATED) +# Protobuf and LibUSB processed by CheckTrezor +if(DEVICE_TREZOR_READY) message(STATUS "Trezor support enabled") add_definitions(-DPROTOBUF_INLINE_NOT_IN_HEADERS=0) + set(TREZOR_LIBUSB_LIBRARIES "") + if(LibUSB_COMPILE_TEST_PASSED) + list(APPEND TREZOR_LIBUSB_LIBRARIES ${LibUSB_LIBRARIES}) + message(STATUS "Trezor compatible LibUSB found at: ${LibUSB_INCLUDE_DIRS}") + endif() + monero_private_headers(device_trezor - ${device_private_headers} - ${PROTOBUF_INCLUDE_DIR}) + ${device_private_headers}) monero_add_library(device_trezor ${trezor_sources} @@ -109,14 +92,13 @@ if(TREZOR_PROTOBUF_GENERATED) common ${SODIUM_LIBRARY} ${Boost_CHRONO_LIBRARY} - ${PROTOBUF_LIBRARY} + ${Protobuf_LIBRARY} + ${TREZOR_LIBUSB_LIBRARIES} PRIVATE ${EXTRA_LIBRARIES}) - # set(WITH_DEVICE_TREZOR 1 PARENT_SCOPE) - # add_definitions(-DWITH_DEVICE_TREZOR=1) - else() + message(STATUS "Trezor support disabled") monero_private_headers(device_trezor) monero_add_library(device_trezor device_trezor.cpp) target_link_libraries(device_trezor PUBLIC cncrypto) diff --git a/src/device_trezor/device_trezor.cpp b/src/device_trezor/device_trezor.cpp index f55cbb15d..5096fcea8 100644 --- a/src/device_trezor/device_trezor.cpp +++ b/src/device_trezor/device_trezor.cpp @@ -32,13 +32,12 @@ namespace hw { namespace trezor { -#if WITH_DEVICE_TREZOR +#ifdef WITH_DEVICE_TREZOR #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "device.trezor" #define HW_TREZOR_NAME "Trezor" -#define HW_TREZOR_NAME_LITE "TrezorLite" static device_trezor *trezor_device = nullptr; static device_trezor *ensure_trezor_device(){ diff --git a/src/device_trezor/device_trezor.hpp b/src/device_trezor/device_trezor.hpp index 765c9b82c..a2134574c 100644 --- a/src/device_trezor/device_trezor.hpp +++ b/src/device_trezor/device_trezor.hpp @@ -49,7 +49,7 @@ namespace trezor { void register_all(); void register_all(std::map> ®istry); -#if WITH_DEVICE_TREZOR +#ifdef WITH_DEVICE_TREZOR class device_trezor; /** diff --git a/src/device_trezor/device_trezor_base.cpp b/src/device_trezor/device_trezor_base.cpp index 3a98bba5a..38c20c30b 100644 --- a/src/device_trezor/device_trezor_base.cpp +++ b/src/device_trezor/device_trezor_base.cpp @@ -32,43 +32,11 @@ namespace hw { namespace trezor { -#if WITH_DEVICE_TREZOR +#ifdef WITH_DEVICE_TREZOR #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "device.trezor" - std::shared_ptr trezor_protocol_callback::on_button_request(const messages::common::ButtonRequest * msg){ - MDEBUG("on_button_request"); - device.on_button_request(); - return std::make_shared(); - } - - std::shared_ptr trezor_protocol_callback::on_pin_matrix_request(const messages::common::PinMatrixRequest * msg){ - MDEBUG("on_pin_request"); - epee::wipeable_string pin; - device.on_pin_request(pin); - auto resp = std::make_shared(); - resp->set_pin(pin.data(), pin.size()); - return resp; - } - - std::shared_ptr trezor_protocol_callback::on_passphrase_request(const messages::common::PassphraseRequest * msg){ - MDEBUG("on_passhprase_request"); - epee::wipeable_string passphrase; - device.on_passphrase_request(msg->on_device(), passphrase); - auto resp = std::make_shared(); - if (!msg->on_device()){ - resp->set_passphrase(passphrase.data(), passphrase.size()); - } - return resp; - } - - std::shared_ptr trezor_protocol_callback::on_passphrase_state_request(const messages::common::PassphraseStateRequest * msg){ - MDEBUG("on_passhprase_state_request"); - device.on_passphrase_state_request(msg->state()); - return std::make_shared(); - } - const uint32_t device_trezor_base::DEFAULT_BIP44_PATH[] = {0x8000002c, 0x80000080, 0x80000000}; device_trezor_base::device_trezor_base() { @@ -117,9 +85,6 @@ namespace trezor { return false; } - if (!m_protocol_callback){ - m_protocol_callback = std::make_shared(*this); - } return true; } @@ -245,6 +210,49 @@ namespace trezor { } } + void device_trezor_base::write_raw(const google::protobuf::Message * msg){ + require_connected(); + CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); + this->getTransport()->write(*msg); + } + + GenericMessage device_trezor_base::read_raw(){ + require_connected(); + std::shared_ptr msg_resp; + hw::trezor::messages::MessageType msg_resp_type; + + this->getTransport()->read(msg_resp, &msg_resp_type); + return GenericMessage(msg_resp_type, msg_resp); + } + + GenericMessage device_trezor_base::call_raw(const google::protobuf::Message * msg) { + write_raw(msg); + return read_raw(); + } + + bool device_trezor_base::message_handler(GenericMessage & input){ + // Later if needed this generic message handler can be replaced by a pointer to + // a protocol message handler which by default points to the device class which implements + // the default handler. + switch(input.m_type){ + case messages::MessageType_ButtonRequest: + on_button_request(input, dynamic_cast(input.m_msg.get())); + return true; + case messages::MessageType_PassphraseRequest: + on_passphrase_request(input, dynamic_cast(input.m_msg.get())); + return true; + case messages::MessageType_PassphraseStateRequest: + on_passphrase_state_request(input, dynamic_cast(input.m_msg.get())); + return true; + case messages::MessageType_PinMatrixRequest: + on_pin_request(input, dynamic_cast(input.m_msg.get())); + return true; + default: + return false; + } + } + + /* ======================================================================= */ /* TREZOR PROTOCOL */ /* ======================================================================= */ @@ -269,32 +277,67 @@ namespace trezor { return false; } - void device_trezor_base::on_button_request() + void device_trezor_base::on_button_request(GenericMessage & resp, const messages::common::ButtonRequest * msg) { + CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); + MDEBUG("on_button_request, code: " << msg->code()); + + messages::common::ButtonAck ack; + write_raw(&ack); + if (m_callback){ m_callback->on_button_request(); } + + resp = read_raw(); } - void device_trezor_base::on_pin_request(epee::wipeable_string & pin) + void device_trezor_base::on_pin_request(GenericMessage & resp, const messages::common::PinMatrixRequest * msg) { + MDEBUG("on_pin_request"); + CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); + + epee::wipeable_string pin; + if (m_callback){ m_callback->on_pin_request(pin); } + + // TODO: remove PIN from memory + messages::common::PinMatrixAck m; + m.set_pin(pin.data(), pin.size()); + resp = call_raw(&m); } - void device_trezor_base::on_passphrase_request(bool on_device, epee::wipeable_string & passphrase) + void device_trezor_base::on_passphrase_request(GenericMessage & resp, const messages::common::PassphraseRequest * msg) { + CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); + MDEBUG("on_passhprase_request, on device: " << msg->on_device()); + epee::wipeable_string passphrase; + if (m_callback){ - m_callback->on_passphrase_request(on_device, passphrase); + m_callback->on_passphrase_request(msg->on_device(), passphrase); } + + messages::common::PassphraseAck m; + if (!msg->on_device()){ + // TODO: remove passphrase from memory + m.set_passphrase(passphrase.data(), passphrase.size()); + } + resp = call_raw(&m); } - void device_trezor_base::on_passphrase_state_request(const std::string & state) + void device_trezor_base::on_passphrase_state_request(GenericMessage & resp, const messages::common::PassphraseStateRequest * msg) { + MDEBUG("on_passhprase_state_request"); + CHECK_AND_ASSERT_THROW_MES(msg, "Empty message"); + if (m_callback){ - m_callback->on_passphrase_state_request(state); + m_callback->on_passphrase_state_request(msg->state()); } + + messages::common::PassphraseStateAck m; + resp = call_raw(&m); } #endif //WITH_DEVICE_TREZOR diff --git a/src/device_trezor/device_trezor_base.hpp b/src/device_trezor/device_trezor_base.hpp index 644a49332..88d419494 100644 --- a/src/device_trezor/device_trezor_base.hpp +++ b/src/device_trezor/device_trezor_base.hpp @@ -54,7 +54,7 @@ namespace hw { namespace trezor { -#if WITH_DEVICE_TREZOR +#ifdef WITH_DEVICE_TREZOR class device_trezor_base; /** @@ -68,41 +68,6 @@ namespace trezor { virtual void on_passphrase_state_request(const std::string & state) {}; }; - /** - * Default Trezor protocol client callback - */ - class trezor_protocol_callback { - protected: - device_trezor_base & device; - - public: - explicit trezor_protocol_callback(device_trezor_base & device): device(device) {} - - std::shared_ptr on_button_request(const messages::common::ButtonRequest * msg); - std::shared_ptr on_pin_matrix_request(const messages::common::PinMatrixRequest * msg); - std::shared_ptr on_passphrase_request(const messages::common::PassphraseRequest * msg); - std::shared_ptr on_passphrase_state_request(const messages::common::PassphraseStateRequest * msg); - - std::shared_ptr on_message(const google::protobuf::Message * msg, messages::MessageType message_type){ - MDEBUG("on_general_message"); - return on_message_dispatch(msg, message_type); - } - - std::shared_ptr on_message_dispatch(const google::protobuf::Message * msg, messages::MessageType message_type){ - if (message_type == messages::MessageType_ButtonRequest){ - return on_button_request(dynamic_cast(msg)); - } else if (message_type == messages::MessageType_PassphraseRequest) { - return on_passphrase_request(dynamic_cast(msg)); - } else if (message_type == messages::MessageType_PassphraseStateRequest) { - return on_passphrase_state_request(dynamic_cast(msg)); - } else if (message_type == messages::MessageType_PinMatrixRequest) { - return on_pin_matrix_request(dynamic_cast(msg)); - } else { - return nullptr; - } - } - }; - /** * TREZOR device template with basic functions */ @@ -114,7 +79,6 @@ namespace trezor { mutable boost::mutex command_locker; std::shared_ptr m_transport; - std::shared_ptr m_protocol_callback; std::shared_ptr m_callback; std::string full_name; @@ -129,6 +93,15 @@ namespace trezor { void call_ping_unsafe(); void test_ping(); + // Communication methods + + void write_raw(const google::protobuf::Message * msg); + GenericMessage read_raw(); + GenericMessage call_raw(const google::protobuf::Message * msg); + + // Trezor message protocol handler. Handles specific signalling messages. + bool message_handler(GenericMessage & input); + /** * Client communication wrapper, handles specific Trezor protocol. * @@ -141,8 +114,7 @@ namespace trezor { const boost::optional & resp_type = boost::none, const boost::optional> & resp_types = boost::none, const boost::optional & resp_type_ptr = boost::none, - bool open_session = false, - unsigned depth=0) + bool open_session = false) { // Require strictly protocol buffers response in the template. BOOST_STATIC_ASSERT(boost::is_base_of::value); @@ -151,8 +123,12 @@ namespace trezor { throw std::invalid_argument("Cannot specify list of accepted types and not using generic response"); } + // Determine type of expected message response + const messages::MessageType required_type = accepting_base ? messages::MessageType_Success : + (resp_type ? resp_type.get() : MessageMapper::get_message_wire_number()); + // Open session if required - if (open_session && depth == 0){ + if (open_session){ try { m_transport->open(); } catch (const std::exception& e) { @@ -162,47 +138,37 @@ namespace trezor { // Scoped session closer BOOST_SCOPE_EXIT_ALL(&, this) { - if (open_session && depth == 0){ + if (open_session){ this->getTransport()->close(); } }; - // Write the request + // Write/read the request CHECK_AND_ASSERT_THROW_MES(req, "Request is null"); - this->getTransport()->write(*req); + auto msg_resp = call_raw(req.get()); - // Read the response - std::shared_ptr msg_resp; - hw::trezor::messages::MessageType msg_resp_type; + bool processed = false; + do { + processed = message_handler(msg_resp); + } while(processed); - // We may have several roundtrips with the handler - this->getTransport()->read(msg_resp, &msg_resp_type); + // Response section if (resp_type_ptr){ - *(resp_type_ptr.get()) = msg_resp_type; + *(resp_type_ptr.get()) = msg_resp.m_type; } - // Determine type of expected message response - messages::MessageType required_type = accepting_base ? messages::MessageType_Success : - (resp_type ? resp_type.get() : MessageMapper::get_message_wire_number()); + if (msg_resp.m_type == messages::MessageType_Failure) { + throw_failure_exception(dynamic_cast(msg_resp.m_msg.get())); - if (msg_resp_type == messages::MessageType_Failure) { - throw_failure_exception(dynamic_cast(msg_resp.get())); + } else if (!accepting_base && msg_resp.m_type == required_type) { + return message_ptr_retype(msg_resp.m_msg); - } else if (!accepting_base && msg_resp_type == required_type) { - return message_ptr_retype(msg_resp); + } else if (accepting_base && (!resp_types || + std::find(resp_types.get().begin(), resp_types.get().end(), msg_resp.m_type) != resp_types.get().end())) { + return message_ptr_retype(msg_resp.m_msg); } else { - auto resp = this->getProtocolCallback()->on_message(msg_resp.get(), msg_resp_type); - if (resp) { - return this->client_exchange(resp, boost::none, resp_types, resp_type_ptr, false, depth + 1); - - } else if (accepting_base && (!resp_types || - std::find(resp_types.get().begin(), resp_types.get().end(), msg_resp_type) != resp_types.get().end())) { - return message_ptr_retype(msg_resp); - - } else { - throw exc::UnexpectedMessageException(msg_resp_type, msg_resp); - } + throw exc::UnexpectedMessageException(msg_resp.m_type, msg_resp.m_msg); } } @@ -252,10 +218,6 @@ namespace trezor { return m_transport; } - std::shared_ptr getProtocolCallback(){ - return m_protocol_callback; - } - std::shared_ptr getCallback(){ return m_callback; } @@ -288,10 +250,10 @@ namespace trezor { bool ping(); // Protocol callbacks - void on_button_request(); - void on_pin_request(epee::wipeable_string & pin); - void on_passphrase_request(bool on_device, epee::wipeable_string & passphrase); - void on_passphrase_state_request(const std::string & state); + void on_button_request(GenericMessage & resp, const messages::common::ButtonRequest * msg); + void on_pin_request(GenericMessage & resp, const messages::common::PinMatrixRequest * msg); + void on_passphrase_request(GenericMessage & resp, const messages::common::PassphraseRequest * msg); + void on_passphrase_state_request(GenericMessage & resp, const messages::common::PassphraseStateRequest * msg); }; #endif diff --git a/src/device_trezor/trezor.hpp b/src/device_trezor/trezor.hpp index 8abdd2c18..97dc0a957 100644 --- a/src/device_trezor/trezor.hpp +++ b/src/device_trezor/trezor.hpp @@ -32,7 +32,7 @@ #include "trezor/trezor_defs.hpp" -#ifdef HAVE_PROTOBUF +#ifdef WITH_DEVICE_TREZOR #include "trezor/transport.hpp" #include "trezor/messages/messages.pb.h" #include "trezor/messages/messages-common.pb.h" diff --git a/src/device_trezor/trezor/tools/README.md b/src/device_trezor/trezor/tools/README.md index 91a8fb3f0..b176017ac 100644 --- a/src/device_trezor/trezor/tools/README.md +++ b/src/device_trezor/trezor/tools/README.md @@ -2,33 +2,28 @@ ## Messages rebuild -Install `protoc` for your distribution. +Install `protoc` for your distribution. Requirements: - `protobuf-compiler` - `libprotobuf-dev` -- `libprotoc-dev` -- `python-protobuf` +- `python` -Python 3 is required. If you don't have python 3 quite an easy way is -to use [pyenv]. -It is also advised to create own python virtual environment so dependencies -are installed in this project-related virtual environment. +Soft requirement: Python 3, can be easily installed with [pyenv]. + +### Python 2 + +Workaround if there is no Python3 available: ```bash -python -m venv / +pip install backports.tempfile ``` -Make sure your python has `protobuf` package installed +### Regenerate messages ```bash -pip install protobuf -``` - -Regenerate messages: - -``` -./venv/bin/python3 src/device_trezor/trezor/tools/build_protob.py +cd src/device_trezor/trezor +python tools/build_protob.py ``` The messages regeneration is done also automatically via cmake. diff --git a/src/device_trezor/trezor/tools/pb2cpp.py b/src/device_trezor/trezor/tools/pb2cpp.py index eaa8a90ed..4d7cc775f 100644 --- a/src/device_trezor/trezor/tools/pb2cpp.py +++ b/src/device_trezor/trezor/tools/pb2cpp.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python3 +#!/usr/bin/env python # Converts Google's protobuf python definitions of TREZOR wire messages # to plain-python objects as used in TREZOR Core and python-trezor @@ -8,11 +8,19 @@ import os import re import shutil import subprocess -import sys import glob -import tempfile import hashlib +try: + from tempfile import TemporaryDirectory +except: + # Py2 backward compatibility, optionally installed by user + # pip install backports.tempfile + try: + from backports.tempfile import TemporaryDirectory + except: + raise EnvironmentError('TemporaryDirectory could not be imported. Try: pip install backports.tempfile') + AUTO_HEADER = "# Automatically generated by pb2cpp\n" @@ -23,6 +31,9 @@ UNDEF_STATEMENT = """ #endif """ +PROTOC = None +PROTOC_INCLUDE = None + def which(pgm): path = os.getenv('PATH') @@ -32,15 +43,6 @@ def which(pgm): return p -PROTOC = which("protoc") -if not PROTOC: - print("protoc command not found") - sys.exit(1) - -PROTOC_PREFIX = os.path.dirname(os.path.dirname(PROTOC)) -PROTOC_INCLUDE = os.path.join(PROTOC_PREFIX, "include") - - def namespace_file(fpath, package): """Adds / replaces package name. Simple regex parsing, may use https://github.com/ph4r05/plyprotobuf later""" with open(fpath) as fh: @@ -82,9 +84,10 @@ def protoc(files, out_dir, additional_includes=(), package=None, force=False): include_dirs = set() include_dirs.add(PROTOC_INCLUDE) - include_dirs.update(additional_includes) + if additional_includes: + include_dirs.update(additional_includes) - with tempfile.TemporaryDirectory() as tmpdir_protob, tempfile.TemporaryDirectory() as tmpdir_out: + with TemporaryDirectory() as tmpdir_protob, TemporaryDirectory() as tmpdir_out: include_dirs.add(tmpdir_protob) new_files = [] @@ -125,9 +128,9 @@ def update_message_files(tmpdir_out, out_dir, force=False): dest_file = os.path.join(out_dir, bname) if not force and os.path.exists(dest_file): data = open(fname, 'rb').read() - data_hash = hashlib.sha3_256(data).digest() + data_hash = hashlib.sha256(data).digest() data_dest = open(dest_file, 'rb').read() - data_dest_hash = hashlib.sha3_256(data_dest).digest() + data_dest_hash = hashlib.sha256(data_dest).digest() if data_hash == data_dest_hash: continue @@ -163,7 +166,8 @@ def strip_leader(s, prefix): return s -if __name__ == "__main__": +def main(): + global PROTOC, PROTOC_INCLUDE logging.basicConfig(level=logging.DEBUG) parser = argparse.ArgumentParser() @@ -179,8 +183,31 @@ if __name__ == "__main__": protoc_includes = args.protoc_include or (os.environ.get("PROTOC_INCLUDE"),) + PROTOBUF_INCLUDE_DIRS = os.getenv("PROTOBUF_INCLUDE_DIRS", None) + PROTOBUF_PROTOC_EXECUTABLE = os.getenv("PROTOBUF_PROTOC_EXECUTABLE", None) + + if PROTOBUF_PROTOC_EXECUTABLE and not os.path.exists(PROTOBUF_PROTOC_EXECUTABLE): + raise ValueError("PROTOBUF_PROTOC_EXECUTABLE set but not found: %s" % PROTOBUF_PROTOC_EXECUTABLE) + + elif PROTOBUF_PROTOC_EXECUTABLE: + PROTOC = PROTOBUF_PROTOC_EXECUTABLE + + else: + if os.name == "nt": + PROTOC = which("protoc.exe") + else: + PROTOC = which("protoc") + + if not PROTOC: + raise ValueError("protoc command not found. Set PROTOBUF_PROTOC_EXECUTABLE env var to the protoc binary and optionally PROTOBUF_INCLUDE_DIRS") + + PROTOC_PREFIX = os.path.dirname(os.path.dirname(PROTOC)) + PROTOC_INCLUDE = PROTOBUF_INCLUDE_DIRS if PROTOBUF_INCLUDE_DIRS else os.path.join(PROTOC_PREFIX, "include") + protoc( args.proto, args.out_dir, protoc_includes, package=args.namespace, force=args.force ) +if __name__ == "__main__": + main() diff --git a/src/device_trezor/trezor/transport.cpp b/src/device_trezor/trezor/transport.cpp index fc86177e1..814537eb6 100644 --- a/src/device_trezor/trezor/transport.cpp +++ b/src/device_trezor/trezor/transport.cpp @@ -27,13 +27,21 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // +#ifdef WITH_DEVICE_TREZOR_WEBUSB +#include +#endif + #include #include #include #include +#include #include "transport.hpp" #include "messages/messages-common.pb.h" +#undef MONERO_DEFAULT_LOG_CATEGORY +#define MONERO_DEFAULT_LOG_CATEGORY "device.trezor.transport" + using namespace std; using json = rapidjson::Document; @@ -581,12 +589,400 @@ namespace trezor{ << ">"; } +#ifdef WITH_DEVICE_TREZOR_WEBUSB + + static bool is_trezor1(libusb_device_descriptor * info){ + return info->idVendor == 0x534C && info->idProduct == 0x0001; + } + + static bool is_trezor2(libusb_device_descriptor * info){ + return info->idVendor == 0x1209 && info->idProduct == 0x53C1; + } + + static bool is_trezor2_bl(libusb_device_descriptor * info){ + return info->idVendor == 0x1209 && info->idProduct == 0x53C0; + } + + static uint8_t get_trezor_dev_mask(libusb_device_descriptor * info){ + uint8_t mask = 0; + CHECK_AND_ASSERT_THROW_MES(info, "Empty device descriptor"); + mask |= is_trezor1(info) ? 1 : 0; + mask |= is_trezor2(info) ? 2 : 0; + mask |= is_trezor2_bl(info) ? 4 : 0; + return mask; + } + + static void set_libusb_log(libusb_context *ctx){ + CHECK_AND_ASSERT_THROW_MES(ctx, "Null libusb context"); + + // http://libusb.sourceforge.net/api-1.0/group__libusb__lib.html +#if defined(LIBUSB_API_VERSION) && (LIBUSB_API_VERSION >= 0x01000106) +# define TREZOR_LIBUSB_SET_DEBUG(ctx, level) libusb_set_option(ctx, LIBUSB_OPTION_LOG_LEVEL, level) +#else +# define TREZOR_LIBUSB_SET_DEBUG(ctx, level) libusb_set_debug(ctx, level) +#endif + + if (ELPP->vRegistry()->allowed(el::Level::Debug, MONERO_DEFAULT_LOG_CATEGORY)) + TREZOR_LIBUSB_SET_DEBUG(ctx, 3); + else if (ELPP->vRegistry()->allowed(el::Level::Warning, MONERO_DEFAULT_LOG_CATEGORY)) + TREZOR_LIBUSB_SET_DEBUG(ctx, 2); + else if (ELPP->vRegistry()->allowed(el::Level::Error, MONERO_DEFAULT_LOG_CATEGORY)) + TREZOR_LIBUSB_SET_DEBUG(ctx, 1); + +#undef TREZOR_LIBUSB_SET_DEBUG + } + + static int get_libusb_ports(libusb_device *dev, std::vector &path){ + uint8_t tmp_path[16]; + int r = libusb_get_port_numbers(dev, tmp_path, sizeof(tmp_path)); + CHECK_AND_ASSERT_MES(r != LIBUSB_ERROR_OVERFLOW, -1, "Libusb path array too small"); + CHECK_AND_ASSERT_MES(r >= 0, -1, "Libusb path array error"); + + path.resize(r); + for (int i = 0; i < r; i++){ + path[i] = tmp_path[i]; + } + + return 0; + } + + static std::string get_usb_path(uint8_t bus_id, const std::vector &path){ + std::stringstream ss; + ss << WebUsbTransport::PATH_PREFIX << (boost::format("%03d") % ((int)bus_id)); + for(uint8_t port : path){ + ss << ":" << ((int) port); + } + return ss.str(); + } + + const char * WebUsbTransport::PATH_PREFIX = "webusb:"; + + WebUsbTransport::WebUsbTransport( + boost::optional descriptor, + boost::optional> proto + ): m_conn_count(0), + m_usb_session(nullptr), m_usb_device(nullptr), m_usb_device_handle(nullptr), + m_bus_id(-1), m_device_addr(-1) + { + if (descriptor){ + libusb_device_descriptor * desc = new libusb_device_descriptor; + memcpy(desc, descriptor.get(), sizeof(libusb_device_descriptor)); + this->m_usb_device_desc.reset(desc); + } + + m_proto = proto ? proto.get() : std::make_shared(); + +#ifdef WITH_TREZOR_DEBUG + m_debug_mode = false; +#endif + } + + WebUsbTransport::~WebUsbTransport(){ + if (m_usb_device){ + close(); + } + + if (m_usb_session) { + libusb_exit(m_usb_session); + m_usb_session = nullptr; + } + } + + void WebUsbTransport::require_device() const{ + if (!m_usb_device_desc){ + throw std::runtime_error("No USB device specified"); + } + } + + void WebUsbTransport::require_connected() const{ + require_device(); + if (!m_usb_device_handle){ + throw std::runtime_error("USB Device not opened"); + } + } + + void WebUsbTransport::enumerate(t_transport_vect & res) { + int r; + libusb_device **devs; + libusb_context *ctx = nullptr; + + r = libusb_init(&ctx); + CHECK_AND_ASSERT_THROW_MES(r >= 0, "Unable to init libusb"); + + set_libusb_log(ctx); + + ssize_t cnt = libusb_get_device_list(ctx, &devs); + if (cnt < 0){ + libusb_exit(ctx); + throw std::runtime_error("Unable to enumerate libusb devices"); + } + + MTRACE("Libusb devices: " << cnt); + + for(ssize_t i = 0; i < cnt; i++) { + libusb_device_descriptor desc{}; + r = libusb_get_device_descriptor(devs[i], &desc); + if (r < 0){ + MERROR("Unable to get libusb device descriptor " << i); + continue; + } + + const auto trezor_mask = get_trezor_dev_mask(&desc); + if (!trezor_mask){ + continue; + } + + MTRACE("Found Trezor device: " << desc.idVendor << ":" << desc.idProduct << " mask " << (int)trezor_mask); + + auto t = std::make_shared(boost::make_optional(&desc)); + t->m_bus_id = libusb_get_bus_number(devs[i]); + t->m_device_addr = libusb_get_device_address(devs[i]); + + // Port resolution may fail. Non-critical error, just addressing precision is decreased. + get_libusb_ports(devs[i], t->m_port_numbers); + + res.push_back(t); + } + + libusb_free_device_list(devs, 1); + libusb_exit(ctx); + } + + std::string WebUsbTransport::get_path() const { + if (!m_usb_device_desc){ + return ""; + } + + return get_usb_path(static_cast(m_bus_id), m_port_numbers); + }; + + void WebUsbTransport::open() { + const int interface = get_interface(); + if (m_conn_count > 0){ + MTRACE("Already opened, count: " << m_conn_count); + m_conn_count += 1; + return; + } + +#define TREZOR_DESTROY_SESSION() do { libusb_exit(m_usb_session); m_usb_session = nullptr; } while(0) + + int r; + libusb_device **devs = nullptr; + + if (m_usb_session) { + TREZOR_DESTROY_SESSION(); + } + + r = libusb_init(&m_usb_session); + CHECK_AND_ASSERT_THROW_MES(r >= 0, "Unable to init libusb"); + set_libusb_log(m_usb_session); + + bool found = false; + int open_res = 0; + + ssize_t cnt = libusb_get_device_list(m_usb_session, &devs); + if (cnt < 0){ + TREZOR_DESTROY_SESSION(); + throw std::runtime_error("Unable to enumerate libusb devices"); + } + + for (ssize_t i = 0; i < cnt; i++) { + libusb_device_descriptor desc{}; + r = libusb_get_device_descriptor(devs[i], &desc); + if (r < 0){ + MERROR("Unable to get libusb device descriptor " << i); + continue; + } + + const auto trezor_mask = get_trezor_dev_mask(&desc); + if (!trezor_mask) { + continue; + } + + auto bus_id = libusb_get_bus_number(devs[i]); + std::vector path; + + // Port resolution may fail. Non-critical error, just addressing precision is decreased. + get_libusb_ports(devs[i], path); + + MTRACE("Found Trezor device: " << desc.idVendor << ":" << desc.idProduct + << ", mask: " << (int)trezor_mask + << ". path: " << get_usb_path(bus_id, path)); + + if (bus_id == m_bus_id && path == m_port_numbers) { + found = true; + m_usb_device = devs[i]; + open_res = libusb_open(m_usb_device, &m_usb_device_handle); + break; + } + } + + libusb_free_device_list(devs, 1); + + if (!found){ + TREZOR_DESTROY_SESSION(); + throw exc::DeviceAcquireException("Device not found"); + + } else if (found && open_res != 0) { + m_usb_device_handle = nullptr; + m_usb_device = nullptr; + TREZOR_DESTROY_SESSION(); + throw exc::DeviceAcquireException("Unable to open libusb device"); + } + + r = libusb_claim_interface(m_usb_device_handle, interface); + + if (r != 0){ + libusb_close(m_usb_device_handle); + m_usb_device_handle = nullptr; + m_usb_device = nullptr; + TREZOR_DESTROY_SESSION(); + throw exc::DeviceAcquireException("Unable to claim libusb device"); + } + + m_conn_count += 1; + m_proto->session_begin(*this); + +#undef TREZOR_DESTROY_SESSION + }; + + void WebUsbTransport::close() { + m_conn_count -= 1; + + if (m_conn_count < 0){ + MERROR("Close counter is negative: " << m_conn_count); + + } else if (m_conn_count == 0){ + MTRACE("Closing webusb device"); + + m_proto->session_end(*this); + + int r = libusb_release_interface(m_usb_device_handle, get_interface()); + if (r != 0){ + MERROR("Could not release libusb interface: " << r); + } + + m_usb_device = nullptr; + if (m_usb_device_handle) { + libusb_close(m_usb_device_handle); + m_usb_device_handle = nullptr; + } + + if (m_usb_session) { + libusb_exit(m_usb_session); + m_usb_session = nullptr; + } + } + }; + + + int WebUsbTransport::get_interface() const{ + const int INTERFACE_NORMAL = 0; +#ifdef WITH_TREZOR_DEBUG + const int INTERFACE_DEBUG = 1; + return m_debug_mode ? INTERFACE_DEBUG : INTERFACE_NORMAL; +#else + return INTERFACE_NORMAL; +#endif + } + + unsigned char WebUsbTransport::get_endpoint() const{ + const unsigned char ENDPOINT_NORMAL = 1; +#ifdef WITH_TREZOR_DEBUG + const unsigned char ENDPOINT_DEBUG = 2; + return m_debug_mode ? ENDPOINT_DEBUG : ENDPOINT_NORMAL; +#else + return ENDPOINT_NORMAL; +#endif + } + + void WebUsbTransport::write(const google::protobuf::Message &req) { + m_proto->write(*this, req); + }; + + void WebUsbTransport::read(std::shared_ptr & msg, messages::MessageType * msg_type) { + m_proto->read(*this, msg, msg_type); + }; + + void WebUsbTransport::write_chunk(const void * buff, size_t size) { + require_connected(); + if (size != REPLEN){ + throw exc::CommunicationException("Invalid chunk size: "); + } + + unsigned char endpoint = get_endpoint(); + endpoint = (endpoint & ~LIBUSB_ENDPOINT_DIR_MASK) | LIBUSB_ENDPOINT_OUT; + + int transferred = 0; + int r = libusb_interrupt_transfer(m_usb_device_handle, endpoint, (unsigned char*)buff, (int)size, &transferred, 0); + CHECK_AND_ASSERT_THROW_MES(r == 0, "Unable to transfer, r: " << r); + if (transferred != (int)size){ + throw exc::CommunicationException("Could not transfer chunk"); + } + }; + + size_t WebUsbTransport::read_chunk(void * buff, size_t size) { + require_connected(); + unsigned char endpoint = get_endpoint(); + endpoint = (endpoint & ~LIBUSB_ENDPOINT_DIR_MASK) | LIBUSB_ENDPOINT_IN; + + int transferred = 0; + int r = libusb_interrupt_transfer(m_usb_device_handle, endpoint, (unsigned char*)buff, (int)size, &transferred, 0); + CHECK_AND_ASSERT_THROW_MES(r == 0, "Unable to transfer, r: " << r); + if (transferred != (int)size){ + throw exc::CommunicationException("Could not read the chunk"); + } + + return transferred; + }; + + std::ostream& WebUsbTransport::dump(std::ostream& o) const { + o << "WebUsbTransport"; + }; + +#endif // WITH_DEVICE_TREZOR_WEBUSB + void enumerate(t_transport_vect & res){ BridgeTransport bt; - bt.enumerate(res); + try{ + bt.enumerate(res); + } catch (const std::exception & e){ + MERROR("BridgeTransport enumeration failed:" << e.what()); + } +#ifdef WITH_DEVICE_TREZOR_WEBUSB + hw::trezor::WebUsbTransport btw; + try{ + btw.enumerate(res); + } catch (const std::exception & e){ + MERROR("WebUsbTransport enumeration failed:" << e.what()); + } +#endif + +#ifdef WITH_DEVICE_TREZOR_UDP hw::trezor::UdpTransport btu; - btu.enumerate(res); + try{ + btu.enumerate(res); + } catch (const std::exception & e){ + MERROR("UdpTransport enumeration failed:" << e.what()); + } +#endif } std::shared_ptr transport(const std::string & path){ @@ -633,6 +1029,9 @@ namespace trezor{ } } + GenericMessage::GenericMessage(messages::MessageType m_type, const shared_ptr &m_msg) + : m_type(m_type), m_msg(m_msg), m_empty(false) {} + std::ostream& operator<<(std::ostream& o, hw::trezor::Transport const& t){ return t.dump(o); } diff --git a/src/device_trezor/trezor/transport.hpp b/src/device_trezor/trezor/transport.hpp index 7b82fd06f..50c31cf73 100644 --- a/src/device_trezor/trezor/transport.hpp +++ b/src/device_trezor/trezor/transport.hpp @@ -239,6 +239,59 @@ namespace trezor { udp::endpoint m_endpoint; }; +#ifdef WITH_DEVICE_TREZOR_WEBUSB +#include + + class WebUsbTransport : public Transport { + public: + + explicit WebUsbTransport( + boost::optional descriptor = boost::none, + boost::optional> proto = boost::none + ); + + virtual ~WebUsbTransport(); + + static const char * PATH_PREFIX; + + std::string get_path() const override; + void enumerate(t_transport_vect & res) override; + + void open() override; + void close() override; + + void write(const google::protobuf::Message &req) override; + void read(std::shared_ptr & msg, messages::MessageType * msg_type=nullptr) override; + + void write_chunk(const void * buff, size_t size) override; + size_t read_chunk(void * buff, size_t size) override; + + std::ostream& dump(std::ostream& o) const override; + + private: + void require_device() const; + void require_connected() const; + int get_interface() const; + unsigned char get_endpoint() const; + + int m_conn_count; + std::shared_ptr m_proto; + + libusb_context *m_usb_session; + libusb_device *m_usb_device; + libusb_device_handle *m_usb_device_handle; + std::unique_ptr m_usb_device_desc; + std::vector m_port_numbers; + int m_bus_id; + int m_device_addr; + +#ifdef WITH_TREZOR_DEBUG + bool m_debug_mode; +#endif + }; + +#endif + // // General helpers // @@ -289,6 +342,20 @@ namespace trezor { */ [[ noreturn ]] void throw_failure_exception(const messages::common::Failure * failure); + /** + * Generic message holder, type + obj + */ + class GenericMessage { + public: + GenericMessage(): m_empty(true) {} + GenericMessage(messages::MessageType m_type, const std::shared_ptr &m_msg); + bool empty() const { return m_empty; } + + hw::trezor::messages::MessageType m_type; + std::shared_ptr m_msg; + bool m_empty; + }; + /** * Simple wrapper for write-read message exchange with expected message response type. * diff --git a/src/device_trezor/trezor/trezor_defs.hpp b/src/device_trezor/trezor/trezor_defs.hpp index 951a8f802..30e76eadc 100644 --- a/src/device_trezor/trezor/trezor_defs.hpp +++ b/src/device_trezor/trezor/trezor_defs.hpp @@ -27,14 +27,41 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -#if defined(HAVE_PROTOBUF) && !defined(WITHOUT_TREZOR) - #define WITH_DEVICE_TREZOR 1 -#else - #define WITH_DEVICE_TREZOR 0 +#ifndef USE_DEVICE_TREZOR +#define USE_DEVICE_TREZOR 1 #endif -#ifndef WITH_DEVICE_TREZOR_LITE -#define WITH_DEVICE_TREZOR_LITE 0 +// HAVE_TREZOR_READY set by cmake after protobuf messages +// were generated successfully and all minimal dependencies are met. +#ifndef DEVICE_TREZOR_READY +#undef USE_DEVICE_TREZOR +#define USE_DEVICE_TREZOR 0 +#endif + +#if USE_DEVICE_TREZOR +#define WITH_DEVICE_TREZOR 1 +#endif + +#ifndef WITH_DEVICE_TREZOR +#undef WITH_DEVICE_TREZOR_LITE +#endif + +#if defined(HAVE_TREZOR_LIBUSB) && USE_DEVICE_TREZOR +#define WITH_DEVICE_TREZOR_WEBUSB 1 +#endif + +// Enable / disable UDP in the enumeration +#ifndef USE_DEVICE_TREZOR_UDP +#define USE_DEVICE_TREZOR_UDP 1 +#endif + +// Enable / disable UDP in the enumeration for release +#ifndef USE_DEVICE_TREZOR_UDP_RELEASE +#define USE_DEVICE_TREZOR_UDP_RELEASE 0 +#endif + +#if USE_DEVICE_TREZOR_UDP && (USE_DEVICE_TREZOR_UDP_RELEASE || defined(TREZOR_DEBUG)) +#define WITH_DEVICE_TREZOR_UDP 1 #endif // Avoids protobuf undefined macro warning