Electroneum
Loading...
Searching...
No Matches
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
31#include <boost/algorithm/string/classification.hpp>
32#include <boost/algorithm/string/split.hpp>
33#include <boost/regex.hpp>
34
35namespace hw {
36namespace 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
88 bool device_trezor_base::init() {
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);
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 TREZOR_AUTO_LOCK_DEVICE()
#define TREZOR_AUTO_LOCK_CMD()
#define MERROR(x)
Definition misc_log_ex.h:73
#define MDEBUG(x)
Definition misc_log_ex.h:76
#define CHECK_AND_ASSERT_THROW_MES(expr, message)
#define MTRACE(x)
Definition misc_log_ex.h:77
#define MINFO(x)
Definition misc_log_ex.h:75
const char * name
void sort_transports_by_env(t_transport_vect &res)
std::vector< std::shared_ptr< Transport > > t_transport_vect
void enumerate(t_transport_vect &res)
Definition device.cpp:38
unsigned int uint32_t
Definition stdint.h:126
unsigned __int64 uint64_t
Definition stdint.h:136