Electroneum
device_trezor_base.cpp
Go to the documentation of this file.
1 // Copyright (c) 2017-Present, Electroneum
2 //
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without modification, are
6 // permitted provided that the following conditions are met:
7 //
8 // 1. Redistributions of source code must retain the above copyright notice, this list of
9 // conditions and the following disclaimer.
10 //
11 // 2. Redistributions in binary form must reproduce the above copyright notice, this list
12 // of conditions and the following disclaimer in the documentation and/or other
13 // materials provided with the distribution.
14 //
15 // 3. Neither the name of the copyright holder nor the names of its contributors may be
16 // used to endorse or promote products derived from this software without specific
17 // prior written permission.
18 //
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
20 // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
21 // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
22 // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25 // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
26 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
27 // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 
30 #include "device_trezor_base.hpp"
31 #include <boost/algorithm/string/classification.hpp>
32 #include <boost/algorithm/string/split.hpp>
33 #include <boost/regex.hpp>
34 
35 namespace hw {
36 namespace trezor {
37 
38 #ifdef WITH_DEVICE_TREZOR
39 
40 #undef ELECTRONEUM_DEFAULT_LOG_CATEGORY
41 #define ELECTRONEUM_DEFAULT_LOG_CATEGORY "device.trezor"
42 #define TREZOR_BIP44_HARDENED_ZERO 0x80000000
43 
44  const uint32_t device_trezor_base::DEFAULT_BIP44_PATH[] = {0x8000002c, 0x80000080};
45 
46  device_trezor_base::device_trezor_base(): m_callback(nullptr), m_last_msg_type(messages::MessageType_Success) {
47 #ifdef WITH_TREZOR_DEBUGGING
48  m_debug = false;
49 #endif
50  }
51 
52  device_trezor_base::~device_trezor_base() {
53  try {
54  disconnect();
55  release();
56  } catch(std::exception const& e){
57  MERROR("Could not disconnect and release: " << e.what());
58  }
59  }
60 
61  /* ======================================================================= */
62  /* SETUP/TEARDOWN */
63  /* ======================================================================= */
64 
65  bool device_trezor_base::reset() {
66  return false;
67  }
68 
69  bool device_trezor_base::set_name(const std::string & name) {
70  this->m_full_name = name;
71  this->name = "";
72 
73  auto delim = name.find(':');
74  if (delim != std::string::npos && delim + 1 < name.length()) {
75  this->name = name.substr(delim + 1);
76  }
77 
78  return true;
79  }
80 
81  const std::string device_trezor_base::get_name() const {
82  if (this->m_full_name.empty()) {
83  return std::string("<disconnected:").append(this->name).append(">");
84  }
85  return this->m_full_name;
86  }
87 
89  if (!release()){
90  MERROR("Release failed");
91  return false;
92  }
93 
94  return true;
95  }
96 
97  bool device_trezor_base::release() {
98  try {
99  disconnect();
100  return true;
101 
102  } catch(std::exception const& e){
103  MERROR("Release exception: " << e.what());
104  return false;
105  }
106  }
107 
108  bool device_trezor_base::connect() {
109  disconnect();
110 
111  // Enumerate all available devices
113  try {
115 
116  MDEBUG("Enumerating Trezor devices...");
117  enumerate(trans);
118  sort_transports_by_env(trans);
119 
120  MDEBUG("Enumeration yielded " << trans.size() << " Trezor devices");
121  for (auto &cur : trans) {
122  MDEBUG(" device: " << *(cur.get()));
123  }
124 
125  for (auto &cur : trans) {
126  std::string cur_path = cur->get_path();
127  if (boost::starts_with(cur_path, this->name)) {
128  MDEBUG("Device Match: " << cur_path);
129  m_transport = cur;
130  break;
131  }
132  }
133 
134  if (!m_transport) {
135  MERROR("No matching Trezor device found. Device specifier: \"" + this->name + "\"");
136  return false;
137  }
138 
139  m_transport->open();
140 
141 #ifdef WITH_TREZOR_DEBUGGING
142  setup_debug();
143 #endif
144  return true;
145 
146  } catch(std::exception const& e){
147  MERROR("Open exception: " << e.what());
148  return false;
149  }
150  }
151 
152  bool device_trezor_base::disconnect() {
154  m_device_state.clear();
155  m_features.reset();
156 
157  if (m_transport){
158  try {
159  m_transport->close();
160  m_transport = nullptr;
161 
162  } catch(std::exception const& e){
163  MERROR("Disconnect exception: " << e.what());
164  m_transport = nullptr;
165  return false;
166  }
167  }
168 
169 #ifdef WITH_TREZOR_DEBUGGING
170  if (m_debug_callback) {
171  m_debug_callback->on_disconnect();
172  m_debug_callback = nullptr;
173  }
174 #endif
175  return true;
176  }
177 
178  /* ======================================================================= */
179  /* LOCKER */
180  /* ======================================================================= */
181 
182  //lock the device for a long sequence
183  void device_trezor_base::lock() {
184  MTRACE("Ask for LOCKING for device " << this->name << " in thread ");
185  device_locker.lock();
186  MTRACE("Device " << this->name << " LOCKed");
187  }
188 
189  //lock the device for a long sequence
190  bool device_trezor_base::try_lock() {
191  MTRACE("Ask for LOCKING(try) for device " << this->name << " in thread ");
192  bool r = device_locker.try_lock();
193  if (r) {
194  MTRACE("Device " << this->name << " LOCKed(try)");
195  } else {
196  MDEBUG("Device " << this->name << " not LOCKed(try)");
197  }
198  return r;
199  }
200 
201  //unlock the device
202  void device_trezor_base::unlock() {
203  MTRACE("Ask for UNLOCKING for device " << this->name << " in thread ");
204  device_locker.unlock();
205  MTRACE("Device " << this->name << " UNLOCKed");
206  }
207 
208  /* ======================================================================= */
209  /* Helpers */
210  /* ======================================================================= */
211 
212  void device_trezor_base::require_connected() const {
213  if (!m_transport){
214  throw exc::NotConnectedException();
215  }
216  }
217 
218  void device_trezor_base::require_initialized() const {
219  if (!m_features){
220  throw exc::TrezorException("Device state not initialized");
221  }
222 
223  if (m_features->has_bootloader_mode() && m_features->bootloader_mode()){
224  throw exc::TrezorException("Device is in the bootloader mode");
225  }
226 
227  if (m_features->has_firmware_present() && !m_features->firmware_present()){
228  throw exc::TrezorException("Device has no firmware loaded");
229  }
230 
231  // Hard requirement on initialized field, has to be there.
232  if (!m_features->has_initialized() || !m_features->initialized()){
233  throw exc::TrezorException("Device is not initialized");
234  }
235  }
236 
237  void device_trezor_base::call_ping_unsafe(){
238  auto pingMsg = std::make_shared<messages::management::Ping>();
239  pingMsg->set_message("PING");
240 
241  auto success = this->client_exchange<messages::common::Success>(pingMsg); // messages::MessageType_Success
242  MDEBUG("Ping response " << success->message());
243  (void)success;
244  }
245 
246  void device_trezor_base::test_ping(){
247  require_connected();
248 
249  try {
250  this->call_ping_unsafe();
251 
252  } catch(exc::TrezorException const& e){
253  MINFO("Trezor does not respond: " << e.what());
254  throw exc::DeviceNotResponsiveException(std::string("Trezor not responding: ") + e.what());
255  }
256  }
257 
258  void device_trezor_base::write_raw(const google::protobuf::Message * msg){
259  require_connected();
260  CHECK_AND_ASSERT_THROW_MES(msg, "Empty message");
261  this->get_transport()->write(*msg);
262  }
263 
264  GenericMessage device_trezor_base::read_raw(){
265  require_connected();
266  std::shared_ptr<google::protobuf::Message> msg_resp;
267  hw::trezor::messages::MessageType msg_resp_type;
268 
269  this->get_transport()->read(msg_resp, &msg_resp_type);
270  return GenericMessage(msg_resp_type, msg_resp);
271  }
272 
273  GenericMessage device_trezor_base::call_raw(const google::protobuf::Message * msg) {
274  write_raw(msg);
275  return read_raw();
276  }
277 
278  bool device_trezor_base::message_handler(GenericMessage & input){
279  // Later if needed this generic message handler can be replaced by a pointer to
280  // a protocol message handler which by default points to the device class which implements
281  // the default handler.
282 
283  if (m_last_msg_type == messages::MessageType_ButtonRequest){
284  on_button_pressed();
285  }
286  m_last_msg_type = input.m_type;
287 
288  switch(input.m_type){
289  case messages::MessageType_ButtonRequest:
290  on_button_request(input, dynamic_cast<const messages::common::ButtonRequest*>(input.m_msg.get()));
291  return true;
292  case messages::MessageType_PassphraseRequest:
293  on_passphrase_request(input, dynamic_cast<const messages::common::PassphraseRequest*>(input.m_msg.get()));
294  return true;
295  case messages::MessageType_PassphraseStateRequest:
296  on_passphrase_state_request(input, dynamic_cast<const messages::common::PassphraseStateRequest*>(input.m_msg.get()));
297  return true;
298  case messages::MessageType_PinMatrixRequest:
299  on_pin_request(input, dynamic_cast<const messages::common::PinMatrixRequest*>(input.m_msg.get()));
300  return true;
301  default:
302  return false;
303  }
304  }
305 
306  void device_trezor_base::ensure_derivation_path() noexcept {
307  if (m_wallet_deriv_path.empty()){
308  m_wallet_deriv_path.push_back(TREZOR_BIP44_HARDENED_ZERO); // default 0'
309  }
310  }
311 
312  void device_trezor_base::set_derivation_path(const std::string &deriv_path){
313  this->m_wallet_deriv_path.clear();
314 
315  if (deriv_path.empty() || deriv_path == "-"){
316  ensure_derivation_path();
317  return;
318  }
319 
320  CHECK_AND_ASSERT_THROW_MES(deriv_path.size() <= 255, "Derivation path is too long");
321 
322  std::vector<std::string> fields;
323  boost::split(fields, deriv_path, boost::is_any_of("/"));
324  CHECK_AND_ASSERT_THROW_MES(fields.size() <= 10, "Derivation path is too long");
325 
326  boost::regex rgx("^([0-9]+)'?$");
327  boost::cmatch match;
328 
329  this->m_wallet_deriv_path.reserve(fields.size());
330  for(const std::string & cur : fields){
331  const bool ok = boost::regex_match(cur.c_str(), match, rgx);
332  CHECK_AND_ASSERT_THROW_MES(ok, "Invalid wallet code: " << deriv_path << ". Invalid path element: " << cur);
333  CHECK_AND_ASSERT_THROW_MES(match[0].length() > 0, "Invalid wallet code: " << deriv_path << ". Invalid path element: " << cur);
334 
335  const unsigned long cidx = std::stoul(match[0].str()) | TREZOR_BIP44_HARDENED_ZERO;
336  this->m_wallet_deriv_path.push_back((unsigned int)cidx);
337  }
338  }
339 
340  /* ======================================================================= */
341  /* TREZOR PROTOCOL */
342  /* ======================================================================= */
343 
344  bool device_trezor_base::ping() {
346  if (!m_transport){
347  MINFO("Ping failed, device not connected");
348  return false;
349  }
350 
351  try {
352  this->call_ping_unsafe();
353  return true;
354 
355  } catch(std::exception const& e) {
356  MERROR("Ping failed, exception thrown " << e.what());
357  } catch(...){
358  MERROR("Ping failed, general exception thrown" << boost::current_exception_diagnostic_information());
359  }
360 
361  return false;
362  }
363 
364  void device_trezor_base::device_state_reset_unsafe()
365  {
366  require_connected();
367  auto initMsg = std::make_shared<messages::management::Initialize>();
368 
369  if(!m_device_state.empty()) {
370  initMsg->set_allocated_state(&m_device_state);
371  }
372 
373  m_features = this->client_exchange<messages::management::Features>(initMsg);
374  initMsg->release_state();
375  }
376 
377  void device_trezor_base::device_state_reset()
378  {
380  device_state_reset_unsafe();
381  }
382 
383 #ifdef WITH_TREZOR_DEBUGGING
384 #define TREZOR_CALLBACK(method, ...) do { \
385  if (m_debug_callback) m_debug_callback->method(__VA_ARGS__); \
386  if (m_callback) m_callback->method(__VA_ARGS__); \
387 }while(0)
388 #define TREZOR_CALLBACK_GET(VAR, method, ...) do { \
389  if (m_debug_callback) VAR = m_debug_callback->method(__VA_ARGS__); \
390  if (m_callback) VAR = m_callback->method(__VA_ARGS__); \
391 }while(0)
392 
393  void device_trezor_base::setup_debug(){
394  if (!m_debug){
395  return;
396  }
397 
398  if (!m_debug_callback){
399  CHECK_AND_ASSERT_THROW_MES(m_transport, "Transport does not exist");
400  auto debug_transport = m_transport->find_debug();
401  if (debug_transport) {
402  m_debug_callback = std::make_shared<trezor_debug_callback>(debug_transport);
403  } else {
404  MDEBUG("Transport does not have debug link option");
405  }
406  }
407  }
408 
409 #else
410 #define TREZOR_CALLBACK(method, ...) do { if (m_callback) m_callback->method(__VA_ARGS__); } while(0)
411 #define TREZOR_CALLBACK_GET(VAR, method, ...) VAR = (m_callback ? m_callback->method(__VA_ARGS__) : boost::none)
412 #endif
413 
414  void device_trezor_base::on_button_request(GenericMessage & resp, const messages::common::ButtonRequest * msg)
415  {
416  CHECK_AND_ASSERT_THROW_MES(msg, "Empty message");
417  MDEBUG("on_button_request, code: " << msg->code());
418 
419  TREZOR_CALLBACK(on_button_request, msg->code());
420 
421  messages::common::ButtonAck ack;
422  write_raw(&ack);
423 
424  resp = read_raw();
425  }
426 
427  void device_trezor_base::on_button_pressed()
428  {
429  TREZOR_CALLBACK(on_button_pressed);
430  }
431 
432  void device_trezor_base::on_pin_request(GenericMessage & resp, const messages::common::PinMatrixRequest * msg)
433  {
434  MDEBUG("on_pin_request");
435  CHECK_AND_ASSERT_THROW_MES(msg, "Empty message");
436 
437  boost::optional<epee::wipeable_string> pin;
438  TREZOR_CALLBACK_GET(pin, on_pin_request);
439 
440  if (!pin && m_pin){
441  pin = m_pin;
442  }
443 
444  // TODO: remove PIN from memory
445  messages::common::PinMatrixAck m;
446  if (pin) {
447  m.set_pin(pin.get().data(), pin.get().size());
448  }
449  resp = call_raw(&m);
450  }
451 
452  void device_trezor_base::on_passphrase_request(GenericMessage & resp, const messages::common::PassphraseRequest * msg)
453  {
454  CHECK_AND_ASSERT_THROW_MES(msg, "Empty message");
455  MDEBUG("on_passhprase_request, on device: " << msg->on_device());
456  boost::optional<epee::wipeable_string> passphrase;
457  TREZOR_CALLBACK_GET(passphrase, on_passphrase_request, msg->on_device());
458 
459  if (!passphrase && m_passphrase){
460  passphrase = m_passphrase;
461  }
462 
463  m_passphrase = boost::none;
464 
465  messages::common::PassphraseAck m;
466  if (!msg->on_device() && passphrase){
467  // TODO: remove passphrase from memory
468  m.set_passphrase(passphrase.get().data(), passphrase.get().size());
469  }
470 
471  if (!m_device_state.empty()){
472  m.set_allocated_state(&m_device_state);
473  }
474 
475  resp = call_raw(&m);
476  m.release_state();
477  }
478 
479  void device_trezor_base::on_passphrase_state_request(GenericMessage & resp, const messages::common::PassphraseStateRequest * msg)
480  {
481  MDEBUG("on_passhprase_state_request");
482  CHECK_AND_ASSERT_THROW_MES(msg, "Empty message");
483 
484  m_device_state = msg->state();
485  messages::common::PassphraseStateAck m;
486  resp = call_raw(&m);
487  }
488 
489 #ifdef WITH_TREZOR_DEBUGGING
490  void device_trezor_base::wipe_device()
491  {
492  auto msg = std::make_shared<messages::management::WipeDevice>();
493  auto ret = client_exchange<messages::common::Success>(msg);
494  (void)ret;
495  init_device();
496  }
497 
498  void device_trezor_base::init_device()
499  {
500  auto msg = std::make_shared<messages::management::Initialize>();
501  m_features = client_exchange<messages::management::Features>(msg);
502  }
503 
504  void device_trezor_base::load_device(const std::string & mnemonic, const std::string & pin,
505  bool passphrase_protection, const std::string & label, const std::string & language,
506  bool skip_checksum, bool expand)
507  {
508  if (m_features && m_features->initialized()){
509  throw std::runtime_error("Device is initialized already. Call device.wipe() and try again.");
510  }
511 
512  auto msg = std::make_shared<messages::management::LoadDevice>();
513  msg->set_mnemonic(mnemonic);
514  msg->set_pin(pin);
515  msg->set_passphrase_protection(passphrase_protection);
516  msg->set_label(label);
517  msg->set_language(language);
518  msg->set_skip_checksum(skip_checksum);
519  auto ret = client_exchange<messages::common::Success>(msg);
520  (void)ret;
521 
522  init_device();
523  }
524 
525  trezor_debug_callback::trezor_debug_callback(std::shared_ptr<Transport> & debug_transport){
526  m_debug_link = std::make_shared<DebugLink>();
527  m_debug_link->init(debug_transport);
528  }
529 
530  void trezor_debug_callback::on_button_request(uint64_t code) {
531  if (m_debug_link) m_debug_link->press_yes();
532  }
533 
534  boost::optional<epee::wipeable_string> trezor_debug_callback::on_pin_request() {
535  return boost::none;
536  }
537 
538  boost::optional<epee::wipeable_string> trezor_debug_callback::on_passphrase_request(bool on_device) {
539  return boost::none;
540  }
541 
542  void trezor_debug_callback::on_passphrase_state_request(const std::string &state) {
543 
544  }
545 
546  void trezor_debug_callback::on_disconnect(){
547  if (m_debug_link) m_debug_link->close();
548  }
549 #endif
550 
551 #endif //WITH_DEVICE_TREZOR
552 }}
#define MERROR(x)
Definition: misc_log_ex.h:73
#define CHECK_AND_ASSERT_THROW_MES(expr, message)
Definition: misc_log_ex.h:173
void enumerate(t_transport_vect &res)
Definition: transport.cpp:1144
#define MTRACE(x)
Definition: misc_log_ex.h:77
#define MINFO(x)
Definition: misc_log_ex.h:75
::std::string string
Definition: gtest-port.h:1097
const char * name
#define MDEBUG(x)
Definition: misc_log_ex.h:76
unsigned int uint32_t
Definition: stdint.h:126
unsigned __int64 uint64_t
Definition: stdint.h:136
Definition: device.cpp:38
void sort_transports_by_env(t_transport_vect &res)
Definition: transport.cpp:1171
expect< void > success() noexcept
Definition: expect.h:397
Definition: blake256.h:37
#define TREZOR_AUTO_LOCK_CMD()
std::vector< std::shared_ptr< Transport > > t_transport_vect
Definition: transport.hpp:135
#define TREZOR_AUTO_LOCK_DEVICE()