diff options
Diffstat (limited to 'src/device_trezor')
-rw-r--r-- | src/device_trezor/CMakeLists.txt | 42 | ||||
-rw-r--r-- | src/device_trezor/device_trezor.cpp | 3 | ||||
-rw-r--r-- | src/device_trezor/device_trezor.hpp | 2 | ||||
-rw-r--r-- | src/device_trezor/device_trezor_base.cpp | 127 | ||||
-rw-r--r-- | src/device_trezor/device_trezor_base.hpp | 112 | ||||
-rw-r--r-- | src/device_trezor/trezor.hpp | 2 | ||||
-rw-r--r-- | src/device_trezor/trezor/tools/README.md | 25 | ||||
-rw-r--r-- | src/device_trezor/trezor/tools/pb2cpp.py | 61 | ||||
-rw-r--r-- | src/device_trezor/trezor/transport.cpp | 403 | ||||
-rw-r--r-- | src/device_trezor/trezor/transport.hpp | 67 | ||||
-rw-r--r-- | src/device_trezor/trezor/trezor_defs.hpp | 39 |
11 files changed, 692 insertions, 191 deletions
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<std::string, std::unique_ptr<device>> ®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<google::protobuf::Message> trezor_protocol_callback::on_button_request(const messages::common::ButtonRequest * msg){ - MDEBUG("on_button_request"); - device.on_button_request(); - return std::make_shared<messages::common::ButtonAck>(); - } - - std::shared_ptr<google::protobuf::Message> 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<messages::common::PinMatrixAck>(); - resp->set_pin(pin.data(), pin.size()); - return resp; - } - - std::shared_ptr<google::protobuf::Message> 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<messages::common::PassphraseAck>(); - if (!msg->on_device()){ - resp->set_passphrase(passphrase.data(), passphrase.size()); - } - return resp; - } - - std::shared_ptr<google::protobuf::Message> 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<messages::common::PassphraseStateAck>(); - } - 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<trezor_protocol_callback>(*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<google::protobuf::Message> 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<const messages::common::ButtonRequest*>(input.m_msg.get())); + return true; + case messages::MessageType_PassphraseRequest: + on_passphrase_request(input, dynamic_cast<const messages::common::PassphraseRequest*>(input.m_msg.get())); + return true; + case messages::MessageType_PassphraseStateRequest: + on_passphrase_state_request(input, dynamic_cast<const messages::common::PassphraseStateRequest*>(input.m_msg.get())); + return true; + case messages::MessageType_PinMatrixRequest: + on_pin_request(input, dynamic_cast<const messages::common::PinMatrixRequest*>(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; /** @@ -69,41 +69,6 @@ namespace trezor { }; /** - * 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<google::protobuf::Message> on_button_request(const messages::common::ButtonRequest * msg); - std::shared_ptr<google::protobuf::Message> on_pin_matrix_request(const messages::common::PinMatrixRequest * msg); - std::shared_ptr<google::protobuf::Message> on_passphrase_request(const messages::common::PassphraseRequest * msg); - std::shared_ptr<google::protobuf::Message> on_passphrase_state_request(const messages::common::PassphraseStateRequest * msg); - - std::shared_ptr<google::protobuf::Message> 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<google::protobuf::Message> on_message_dispatch(const google::protobuf::Message * msg, messages::MessageType message_type){ - if (message_type == messages::MessageType_ButtonRequest){ - return on_button_request(dynamic_cast<const messages::common::ButtonRequest*>(msg)); - } else if (message_type == messages::MessageType_PassphraseRequest) { - return on_passphrase_request(dynamic_cast<const messages::common::PassphraseRequest*>(msg)); - } else if (message_type == messages::MessageType_PassphraseStateRequest) { - return on_passphrase_state_request(dynamic_cast<const messages::common::PassphraseStateRequest*>(msg)); - } else if (message_type == messages::MessageType_PinMatrixRequest) { - return on_pin_matrix_request(dynamic_cast<const messages::common::PinMatrixRequest*>(msg)); - } else { - return nullptr; - } - } - }; - - /** * TREZOR device template with basic functions */ class device_trezor_base : public hw::core::device_default { @@ -114,7 +79,6 @@ namespace trezor { mutable boost::mutex command_locker; std::shared_ptr<Transport> m_transport; - std::shared_ptr<trezor_protocol_callback> m_protocol_callback; std::shared_ptr<trezor_callback> 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<messages::MessageType> & resp_type = boost::none, const boost::optional<std::vector<messages::MessageType>> & resp_types = boost::none, const boost::optional<messages::MessageType*> & 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<google::protobuf::Message, t_message>::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<t_message>()); + // 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<google::protobuf::Message> 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<t_message>()); + if (msg_resp.m_type == messages::MessageType_Failure) { + throw_failure_exception(dynamic_cast<messages::common::Failure *>(msg_resp.m_msg.get())); - if (msg_resp_type == messages::MessageType_Failure) { - throw_failure_exception(dynamic_cast<messages::common::Failure *>(msg_resp.get())); + } else if (!accepting_base && msg_resp.m_type == required_type) { + return message_ptr_retype<t_message>(msg_resp.m_msg); - } else if (!accepting_base && msg_resp_type == required_type) { - return message_ptr_retype<t_message>(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<t_message>(msg_resp.m_msg); } else { - auto resp = this->getProtocolCallback()->on_message(msg_resp.get(), msg_resp_type); - if (resp) { - return this->client_exchange<t_message>(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<t_message>(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<trezor_protocol_callback> getProtocolCallback(){ - return m_protocol_callback; - } - std::shared_ptr<trezor_callback> 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]. -```bash -python -m venv / -``` +### Python 2 -Make sure your python has `protobuf` package installed +Workaround if there is no Python3 available: ```bash -pip install protobuf +pip install backports.tempfile ``` -Regenerate messages: +### Regenerate messages -``` -./venv/bin/python3 src/device_trezor/trezor/tools/build_protob.py +```bash +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 <libusb.h> +#endif + #include <boost/endian/conversion.hpp> #include <boost/asio/io_service.hpp> #include <boost/asio/ip/udp.hpp> #include <boost/date_time/posix_time/posix_time_types.hpp> +#include <boost/format.hpp> #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<uint8_t> &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<uint8_t> &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<libusb_device_descriptor*> descriptor, + boost::optional<std::shared_ptr<Protocol>> 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<ProtocolV1>(); + +#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<WebUsbTransport>(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<uint8_t>(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<uint8_t> 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<google::protobuf::Message> & 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<path=" << get_path() + << ", vendorId=" << (m_usb_device_desc ? std::to_string(m_usb_device_desc->idVendor) : "?") + << ", productId=" << (m_usb_device_desc ? std::to_string(m_usb_device_desc->idProduct) : "?") + << ", deviceType="; + + if (m_usb_device_desc){ + if (is_trezor1(m_usb_device_desc.get())) + o << "TrezorOne"; + else if (is_trezor2(m_usb_device_desc.get())) + o << "TrezorT"; + else if (is_trezor2_bl(m_usb_device_desc.get())) + o << "TrezorT BL"; + } else { + o << "?"; + } + + return o << ">"; + }; + +#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> transport(const std::string & path){ @@ -633,6 +1029,9 @@ namespace trezor{ } } + GenericMessage::GenericMessage(messages::MessageType m_type, const shared_ptr<google::protobuf::Message> &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 <libusb.h> + + class WebUsbTransport : public Transport { + public: + + explicit WebUsbTransport( + boost::optional<libusb_device_descriptor*> descriptor = boost::none, + boost::optional<std::shared_ptr<Protocol>> 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<google::protobuf::Message> & 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<Protocol> m_proto; + + libusb_context *m_usb_session; + libusb_device *m_usb_device; + libusb_device_handle *m_usb_device_handle; + std::unique_ptr<libusb_device_descriptor> m_usb_device_desc; + std::vector<uint8_t> m_port_numbers; + int m_bus_id; + int m_device_addr; + +#ifdef WITH_TREZOR_DEBUG + bool m_debug_mode; +#endif + }; + +#endif + // // General helpers // @@ -290,6 +343,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<google::protobuf::Message> &m_msg); + bool empty() const { return m_empty; } + + hw::trezor::messages::MessageType m_type; + std::shared_ptr<google::protobuf::Message> m_msg; + bool m_empty; + }; + + /** * Simple wrapper for write-read message exchange with expected message response type. * * @throws UnexpectedMessageException if the response message type is different than expected. 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 |