2018-06-04 10:19:50 +02:00
// Copyright 2015-2018 Parity Technologies (UK) Ltd.
2017-02-10 01:07:06 +01:00
// This file is part of Parity.
// Parity is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// Parity is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
//! Ledger hardware wallet module. Supports Ledger Blue and Nano S.
2018-06-18 11:55:23 +02:00
//! See <https://github.com/LedgerHQ/blue-app-eth/blob/master/doc/ethapp.asc> for protocol details.
use std ::cmp ::min ;
use std ::str ::FromStr ;
use std ::sync ::{ atomic , atomic ::AtomicBool , Arc , Weak } ;
use std ::time ::{ Duration , Instant } ;
use std ::{ fmt , thread } ;
2017-02-10 01:07:06 +01:00
2018-01-10 13:35:18 +01:00
use ethereum_types ::{ H256 , Address } ;
use ethkey ::Signature ;
use hidapi ;
2018-02-27 16:45:16 +01:00
use libusb ;
2018-01-10 13:35:18 +01:00
use parking_lot ::{ Mutex , RwLock } ;
2018-06-13 11:01:56 +02:00
use semver ::Version as FirmwareVersion ;
use super ::{ WalletInfo , KeyPath , Device , DeviceDirection , Wallet , USB_DEVICE_CLASS_DEVICE , POLLING_DURATION } ;
2018-01-10 13:35:18 +01:00
2018-06-13 11:01:56 +02:00
const APDU_TAG : u8 = 0x05 ;
const APDU_CLA : u8 = 0xe0 ;
const APDU_PAYLOAD_HEADER_LEN : usize = 7 ;
const ETH_DERIVATION_PATH_BE : [ u8 ; 17 ] = [ 4 , 0x80 , 0 , 0 , 44 , 0x80 , 0 , 0 , 60 , 0x80 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ; // 44'/60'/0'/0
const ETC_DERIVATION_PATH_BE : [ u8 ; 21 ] = [ 5 , 0x80 , 0 , 0 , 44 , 0x80 , 0 , 0 , 60 , 0x80 , 0x02 , 0x73 , 0xd0 , 0x80 , 0 , 0 , 0 , 0 , 0 , 0 , 0 ] ; // 44'/60'/160720'/0'/0
2018-01-10 13:35:18 +01:00
2018-02-27 16:45:16 +01:00
/// Ledger vendor ID
2018-05-01 15:01:49 +02:00
const LEDGER_VID : u16 = 0x2c97 ;
/// Ledger product IDs: [Nano S and Blue]
const LEDGER_PIDS : [ u16 ; 2 ] = [ 0x0000 , 0x0001 ] ;
2018-06-13 11:01:56 +02:00
const LEDGER_TRANSPORT_HEADER_LEN : usize = 5 ;
2018-02-27 16:45:16 +01:00
2018-06-13 11:01:56 +02:00
const MAX_CHUNK_SIZE : usize = 255 ;
2017-02-10 01:07:06 +01:00
2018-06-13 11:01:56 +02:00
const HID_PACKET_SIZE : usize = 64 + HID_PREFIX_ZERO ;
2017-02-10 01:07:06 +01:00
#[ cfg(windows) ] const HID_PREFIX_ZERO : usize = 1 ;
#[ cfg(not(windows)) ] const HID_PREFIX_ZERO : usize = 0 ;
mod commands {
pub const GET_APP_CONFIGURATION : u8 = 0x06 ;
pub const GET_ETH_PUBLIC_ADDRESS : u8 = 0x02 ;
pub const SIGN_ETH_TRANSACTION : u8 = 0x04 ;
2018-06-13 11:01:56 +02:00
pub const SIGN_ETH_PERSONAL_MESSAGE : u8 = 0x08 ;
2017-02-10 01:07:06 +01:00
}
2017-09-14 19:28:43 +02:00
/// Hardware wallet error.
2017-02-10 01:07:06 +01:00
#[ derive(Debug) ]
pub enum Error {
/// Ethereum wallet protocol error.
Protocol ( & 'static str ) ,
/// Hidapi error.
Usb ( hidapi ::HidError ) ,
2018-02-27 16:45:16 +01:00
/// Libusb error
LibUsb ( libusb ::Error ) ,
2017-02-10 01:07:06 +01:00
/// Device with request key is not available.
KeyNotFound ,
/// Signing has been cancelled by user.
UserCancel ,
2018-05-01 15:01:49 +02:00
/// Invalid device
2018-02-27 16:45:16 +01:00
InvalidDevice ,
2018-05-01 15:01:49 +02:00
/// Impossible error
2018-06-13 11:01:56 +02:00
Impossible ,
/// No device arrived
NoDeviceArrived ,
/// No device left
NoDeviceLeft ,
2017-02-10 01:07:06 +01:00
}
impl fmt ::Display for Error {
fn fmt ( & self , f : & mut fmt ::Formatter ) -> Result < ( ) , fmt ::Error > {
match * self {
Error ::Protocol ( ref s ) = > write! ( f , " Ledger protocol error: {} " , s ) ,
Error ::Usb ( ref e ) = > write! ( f , " USB communication error: {} " , e ) ,
2018-02-27 16:45:16 +01:00
Error ::LibUsb ( ref e ) = > write! ( f , " LibUSB communication error: {} " , e ) ,
2017-02-10 01:07:06 +01:00
Error ::KeyNotFound = > write! ( f , " Key not found " ) ,
Error ::UserCancel = > write! ( f , " Operation has been cancelled " ) ,
2018-02-27 16:45:16 +01:00
Error ::InvalidDevice = > write! ( f , " Unsupported product was entered " ) ,
2018-06-13 11:01:56 +02:00
Error ::Impossible = > write! ( f , " Placeholder error " ) ,
Error ::NoDeviceArrived = > write! ( f , " No device arrived " ) ,
Error ::NoDeviceLeft = > write! ( f , " No device left " ) ,
2017-02-10 01:07:06 +01:00
}
}
}
impl From < hidapi ::HidError > for Error {
2018-06-18 11:55:23 +02:00
fn from ( err : hidapi ::HidError ) -> Self {
2017-02-10 01:07:06 +01:00
Error ::Usb ( err )
}
}
2018-02-27 16:45:16 +01:00
impl From < libusb ::Error > for Error {
2018-06-18 11:55:23 +02:00
fn from ( err : libusb ::Error ) -> Self {
2018-02-27 16:45:16 +01:00
Error ::LibUsb ( err )
}
}
2017-02-10 01:07:06 +01:00
/// Ledger device manager.
2018-06-13 11:01:56 +02:00
pub ( crate ) struct Manager {
2017-09-14 19:28:43 +02:00
usb : Arc < Mutex < hidapi ::HidApi > > ,
devices : RwLock < Vec < Device > > ,
key_path : RwLock < KeyPath > ,
2017-02-10 01:07:06 +01:00
}
impl Manager {
/// Create a new instance.
2018-06-18 11:55:23 +02:00
pub fn new ( hidapi : Arc < Mutex < hidapi ::HidApi > > , exiting : Arc < AtomicBool > ) -> Result < Arc < Self > , libusb ::Error > {
let manager = Arc ::new ( Self {
2017-09-14 19:28:43 +02:00
usb : hidapi ,
devices : RwLock ::new ( Vec ::new ( ) ) ,
key_path : RwLock ::new ( KeyPath ::Ethereum ) ,
2018-05-01 15:01:49 +02:00
} ) ;
let usb_context = Arc ::new ( libusb ::Context ::new ( ) ? ) ;
let m = manager . clone ( ) ;
// Subscribe to all Ledger devices
// This means that we need to check that the given productID is supported
// None => LIBUSB_HOTPLUG_MATCH_ANY, in other words that all are subscribed to
// More info can be found: <http://libusb.sourceforge.net/api-1.0/group__hotplug.html#gae6c5f1add6cc754005549c7259dc35ea>
usb_context . register_callback (
Some ( LEDGER_VID ) , None , Some ( USB_DEVICE_CLASS_DEVICE ) ,
Box ::new ( EventHandler ::new ( Arc ::downgrade ( & manager ) ) ) ) . expect ( " usb_callback " ) ;
// Ledger event handler thread
thread ::Builder ::new ( )
. spawn ( move | | {
2018-06-13 11:01:56 +02:00
if let Err ( e ) = m . update_devices ( DeviceDirection ::Arrived ) {
2018-05-01 15:01:49 +02:00
debug! ( target : " hw " , " Ledger couldn't connect at startup, error: {} " , e ) ;
}
loop {
usb_context . handle_events ( Some ( Duration ::from_millis ( 500 ) ) )
2018-06-13 11:01:56 +02:00
. unwrap_or_else ( | e | debug! ( target : " hw " , " Ledger event handler error: {} " , e ) ) ;
2018-05-01 15:01:49 +02:00
if exiting . load ( atomic ::Ordering ::Acquire ) {
break ;
2017-02-10 01:07:06 +01:00
}
2017-09-14 19:28:43 +02:00
}
2018-05-01 15:01:49 +02:00
} )
. ok ( ) ;
2017-02-10 01:07:06 +01:00
2018-05-01 15:01:49 +02:00
Ok ( manager )
2017-02-10 01:07:06 +01:00
}
2018-06-13 11:01:56 +02:00
// Transport Protocol:
// * Communication Channel Id (2 bytes big endian )
// * Command Tag (1 byte)
// * Packet Sequence ID (2 bytes big endian)
// * Payload (Optional)
//
// Payload
// * APDU Total Length (2 bytes big endian)
// * APDU_CLA (1 byte)
// * APDU_INS (1 byte)
// * APDU_P1 (1 byte)
// * APDU_P2 (1 byte)
// * APDU_LENGTH (1 byte)
// * APDU_Payload (Variable)
//
fn write ( handle : & hidapi ::HidDevice , command : u8 , p1 : u8 , p2 : u8 , data : & [ u8 ] ) -> Result < ( ) , Error > {
let data_len = data . len ( ) ;
2017-02-10 01:07:06 +01:00
let mut offset = 0 ;
2018-06-13 11:01:56 +02:00
let mut sequence_number = 0 ;
let mut hid_chunk = [ 0_ u8 ; HID_PACKET_SIZE ] ;
while sequence_number = = 0 | | offset < data_len {
let header = if sequence_number = = 0 { LEDGER_TRANSPORT_HEADER_LEN + APDU_PAYLOAD_HEADER_LEN } else { LEDGER_TRANSPORT_HEADER_LEN } ;
let size = min ( 64 - header , data_len - offset ) ;
2017-09-14 19:28:43 +02:00
{
2017-10-15 15:10:59 +02:00
let chunk = & mut hid_chunk [ HID_PREFIX_ZERO .. ] ;
2018-06-18 11:55:23 +02:00
chunk [ 0 .. 5 ] . copy_from_slice ( & [ 0x01 , 0x01 , APDU_TAG , ( sequence_number > > 8 ) as u8 , ( sequence_number & 0xff ) as u8 ] ) ;
2017-09-14 19:28:43 +02:00
2018-06-13 11:01:56 +02:00
if sequence_number = = 0 {
2017-09-14 19:28:43 +02:00
let data_len = data . len ( ) + 5 ;
2018-06-18 11:55:23 +02:00
chunk [ 5 .. 12 ] . copy_from_slice ( & [ ( data_len > > 8 ) as u8 , ( data_len & 0xff ) as u8 , APDU_CLA , command , p1 , p2 , data . len ( ) as u8 ] ) ;
2017-02-10 01:07:06 +01:00
}
2017-09-14 19:28:43 +02:00
2018-06-18 11:55:23 +02:00
chunk [ header .. header + size ] . copy_from_slice ( & data [ offset .. offset + size ] ) ;
2017-09-14 19:28:43 +02:00
}
2018-06-13 11:01:56 +02:00
trace! ( target : " hw " , " Ledger write {:?} " , & hid_chunk [ .. ] ) ;
2017-09-14 19:28:43 +02:00
let n = handle . write ( & hid_chunk [ .. ] ) ? ;
2018-06-13 11:01:56 +02:00
if n < size + header {
2017-09-14 19:28:43 +02:00
return Err ( Error ::Protocol ( " Write data size mismatch " ) ) ;
2017-02-10 01:07:06 +01:00
}
2018-06-13 11:01:56 +02:00
offset + = size ;
sequence_number + = 1 ;
if sequence_number > = 0xffff {
return Err ( Error ::Protocol ( " Maximum sequence number reached " ) ) ;
2017-09-14 19:28:43 +02:00
}
}
2018-06-13 11:01:56 +02:00
Ok ( ( ) )
}
// Transport Protocol:
// * Communication Channel Id (2 bytes big endian )
// * Command Tag (1 byte)
// * Packet Sequence ID (2 bytes big endian)
// * Payload (Optional)
//
// Payload
// * APDU Total Length (2 bytes big endian)
// * APDU_CLA (1 byte)
// * APDU_INS (1 byte)
// * APDU_P1 (1 byte)
// * APDU_P2 (1 byte)
// * APDU_LENGTH (1 byte)
// * APDU_Payload (Variable)
//
fn read ( handle : & hidapi ::HidDevice ) -> Result < Vec < u8 > , Error > {
2017-02-10 01:07:06 +01:00
let mut message_size = 0 ;
let mut message = Vec ::new ( ) ;
2018-06-13 11:01:56 +02:00
// terminate the loop if `sequence_number` reaches its max_value and report error
for chunk_index in 0 ..= 0xffff {
2017-02-10 01:07:06 +01:00
let mut chunk : [ u8 ; HID_PACKET_SIZE ] = [ 0 ; HID_PACKET_SIZE ] ;
let chunk_size = handle . read ( & mut chunk ) ? ;
2018-06-13 11:01:56 +02:00
trace! ( target : " hw " , " Ledger read {:?} " , & chunk [ .. ] ) ;
if chunk_size < LEDGER_TRANSPORT_HEADER_LEN | | chunk [ 0 ] ! = 0x01 | | chunk [ 1 ] ! = 0x01 | | chunk [ 2 ] ! = APDU_TAG {
2017-02-10 01:07:06 +01:00
return Err ( Error ::Protocol ( " Unexpected chunk header " ) ) ;
}
let seq = ( chunk [ 3 ] as usize ) < < 8 | ( chunk [ 4 ] as usize ) ;
if seq ! = chunk_index {
return Err ( Error ::Protocol ( " Unexpected chunk header " ) ) ;
}
let mut offset = 5 ;
if seq = = 0 {
2018-05-01 15:01:49 +02:00
// Read message size and status word.
2017-02-10 01:07:06 +01:00
if chunk_size < 7 {
return Err ( Error ::Protocol ( " Unexpected chunk header " ) ) ;
}
2017-09-14 19:28:43 +02:00
message_size = ( chunk [ 5 ] as usize ) < < 8 | ( chunk [ 6 ] as usize ) ;
2017-02-10 01:07:06 +01:00
offset + = 2 ;
}
message . extend_from_slice ( & chunk [ offset .. chunk_size ] ) ;
message . truncate ( message_size ) ;
if message . len ( ) = = message_size {
break ;
}
}
if message . len ( ) < 2 {
return Err ( Error ::Protocol ( " No status word " ) ) ;
}
2017-09-14 19:28:43 +02:00
let status = ( message [ message . len ( ) - 2 ] as usize ) < < 8 | ( message [ message . len ( ) - 1 ] as usize ) ;
2018-05-01 15:01:49 +02:00
debug! ( target : " hw " , " Read status {:x} " , status ) ;
2017-02-10 01:07:06 +01:00
match status {
0x6700 = > Err ( Error ::Protocol ( " Incorrect length " ) ) ,
0x6982 = > Err ( Error ::Protocol ( " Security status not satisfied (Canceled by user) " ) ) ,
0x6a80 = > Err ( Error ::Protocol ( " Invalid data " ) ) ,
0x6a82 = > Err ( Error ::Protocol ( " File not found " ) ) ,
0x6a85 = > Err ( Error ::UserCancel ) ,
0x6b00 = > Err ( Error ::Protocol ( " Incorrect parameters " ) ) ,
2018-06-18 11:55:23 +02:00
0x6d00 = > Err ( Error ::Protocol ( " Not implemented. Make sure the Ledger Ethereum Wallet app is running. " ) ) ,
2018-06-13 11:01:56 +02:00
0x6faa = > Err ( Error ::Protocol ( " Your Ledger need to be unplugged " ) ) ,
2017-02-10 01:07:06 +01:00
0x6f00 .. . 0x6fff = > Err ( Error ::Protocol ( " Internal error " ) ) ,
0x9000 = > Ok ( ( ) ) ,
_ = > Err ( Error ::Protocol ( " Unknown error " ) ) ,
} ? ;
let new_len = message . len ( ) - 2 ;
message . truncate ( new_len ) ;
Ok ( message )
}
2018-02-27 16:45:16 +01:00
2018-06-13 11:01:56 +02:00
fn send_apdu ( handle : & hidapi ::HidDevice , command : u8 , p1 : u8 , p2 : u8 , data : & [ u8 ] ) -> Result < Vec < u8 > , Error > {
Self ::write ( & handle , command , p1 , p2 , data ) ? ;
Self ::read ( & handle )
}
2018-02-27 16:45:16 +01:00
fn is_valid_ledger ( device : & libusb ::Device ) -> Result < ( ) , Error > {
let desc = device . device_descriptor ( ) ? ;
let vendor_id = desc . vendor_id ( ) ;
let product_id = desc . product_id ( ) ;
if vendor_id = = LEDGER_VID & & LEDGER_PIDS . contains ( & product_id ) {
Ok ( ( ) )
} else {
Err ( Error ::InvalidDevice )
}
}
2018-06-13 11:01:56 +02:00
fn get_firmware_version ( handle : & hidapi ::HidDevice ) -> Result < FirmwareVersion , Error > {
let ver = Self ::send_apdu ( & handle , commands ::GET_APP_CONFIGURATION , 0 , 0 , & [ ] ) ? ;
if ver . len ( ) ! = 4 {
return Err ( Error ::Protocol ( " Version packet size mismatch " ) ) ;
2018-03-02 18:30:25 +01:00
}
2018-06-13 11:01:56 +02:00
Ok ( FirmwareVersion ::new ( ver [ 1 ] . into ( ) , ver [ 2 ] . into ( ) , ver [ 3 ] . into ( ) ) )
2018-03-02 18:30:25 +01:00
}
2018-02-27 16:45:16 +01:00
2018-06-13 11:01:56 +02:00
fn get_derivation_path ( & self ) -> & [ u8 ] {
match * self . key_path . read ( ) {
KeyPath ::Ethereum = > & ETH_DERIVATION_PATH_BE ,
KeyPath ::EthereumClassic = > & ETC_DERIVATION_PATH_BE ,
}
}
fn signer_helper ( & self , address : & Address , data : & [ u8 ] , command : u8 ) -> Result < Signature , Error > {
2018-05-01 15:01:49 +02:00
let usb = self . usb . lock ( ) ;
let devices = self . devices . read ( ) ;
let device = devices . iter ( ) . find ( | d | & d . info . address = = address ) . ok_or ( Error ::KeyNotFound ) ? ;
let handle = self . open_path ( | | usb . open_path ( & device . path ) ) ? ;
2018-06-13 11:01:56 +02:00
// Signing personal messages are only support by Ledger firmware version 1.0.8 or newer
if command = = commands ::SIGN_ETH_PERSONAL_MESSAGE {
let version = Self ::get_firmware_version ( & handle ) ? ;
if version < FirmwareVersion ::new ( 1 , 0 , 8 ) {
return Err ( Error ::Protocol ( " Signing personal messages with Ledger requires version 1.0.8 " ) ) ;
}
}
let mut chunk = [ 0_ u8 ; MAX_CHUNK_SIZE ] ;
let derivation_path = self . get_derivation_path ( ) ;
// Copy the address of the key (only done once)
2018-06-18 11:55:23 +02:00
chunk [ 0 .. derivation_path . len ( ) ] . copy_from_slice ( derivation_path ) ;
2018-06-13 11:01:56 +02:00
let key_length = derivation_path . len ( ) ;
let max_payload_size = MAX_CHUNK_SIZE - key_length ;
let data_len = data . len ( ) ;
let mut result = Vec ::new ( ) ;
let mut offset = 0 ;
while offset < data_len {
let p1 = if offset = = 0 { 0 } else { 0x80 } ;
let take = min ( max_payload_size , data_len - offset ) ;
// Fetch piece of data and copy it!
{
let ( _key , d ) = & mut chunk . split_at_mut ( key_length ) ;
let ( dst , _rem ) = & mut d . split_at_mut ( take ) ;
dst . copy_from_slice ( & data [ offset .. ( offset + take ) ] ) ;
2018-05-01 15:01:49 +02:00
}
2018-06-13 11:01:56 +02:00
result = Self ::send_apdu ( & handle , command , p1 , 0 , & chunk [ 0 .. ( key_length + take ) ] ) ? ;
offset + = take ;
2018-05-01 15:01:49 +02:00
}
if result . len ( ) ! = 65 {
return Err ( Error ::Protocol ( " Signature packet size mismatch " ) ) ;
}
let v = ( result [ 0 ] + 1 ) % 2 ;
let r = H256 ::from_slice ( & result [ 1 .. 33 ] ) ;
let s = H256 ::from_slice ( & result [ 33 .. 65 ] ) ;
Ok ( Signature ::from_rsv ( & r , & s , v ) )
}
2018-06-13 11:01:56 +02:00
pub fn sign_message ( & self , address : & Address , msg : & [ u8 ] ) -> Result < Signature , Error > {
self . signer_helper ( address , msg , commands ::SIGN_ETH_PERSONAL_MESSAGE )
}
}
// Try to connect to the device using polling in at most the time specified by the `timeout`
2018-06-18 11:55:23 +02:00
fn try_connect_polling ( ledger : & Manager , timeout : & Duration , device_direction : DeviceDirection ) -> bool {
2018-06-13 11:01:56 +02:00
let start_time = Instant ::now ( ) ;
while start_time . elapsed ( ) < = * timeout {
if let Ok ( num_devices ) = ledger . update_devices ( device_direction ) {
trace! ( target : " hw " , " {} number of Ledger(s) {} " , num_devices , device_direction ) ;
return true ;
}
}
false
}
impl < ' a > Wallet < ' a > for Manager {
type Error = Error ;
type Transaction = & ' a [ u8 ] ;
fn sign_transaction ( & self , address : & Address , transaction : Self ::Transaction ) -> Result < Signature , Self ::Error > {
self . signer_helper ( address , transaction , commands ::SIGN_ETH_TRANSACTION )
}
2018-05-01 15:01:49 +02:00
fn set_key_path ( & self , key_path : KeyPath ) {
* self . key_path . write ( ) = key_path ;
}
2018-06-13 11:01:56 +02:00
fn update_devices ( & self , device_direction : DeviceDirection ) -> Result < usize , Self ::Error > {
2018-05-01 15:01:49 +02:00
let mut usb = self . usb . lock ( ) ;
usb . refresh_devices ( ) ;
let devices = usb . devices ( ) ;
2018-06-13 11:01:56 +02:00
let num_prev_devices = self . devices . read ( ) . len ( ) ;
let detected_devices = devices . iter ( )
. filter ( | & d | d . vendor_id = = LEDGER_VID & & LEDGER_PIDS . contains ( & d . product_id ) )
. fold ( Vec ::new ( ) , | mut v , d | {
match self . read_device ( & usb , & d ) {
Ok ( info ) = > {
trace! ( target : " hw " , " Found device: {:?} " , info ) ;
v . push ( info ) ;
2018-05-01 15:01:49 +02:00
}
2018-06-13 11:01:56 +02:00
Err ( e ) = > trace! ( target : " hw " , " Error reading device info: {} " , e ) ,
} ;
v
} ) ;
let num_curr_devices = detected_devices . len ( ) ;
* self . devices . write ( ) = detected_devices ;
match device_direction {
DeviceDirection ::Arrived = > {
if num_curr_devices > num_prev_devices {
Ok ( num_curr_devices - num_prev_devices )
} else {
Err ( Error ::NoDeviceArrived )
}
}
DeviceDirection ::Left = > {
if num_prev_devices > num_curr_devices {
Ok ( num_prev_devices - num_curr_devices )
} else {
Err ( Error ::NoDeviceLeft )
2018-05-01 15:01:49 +02:00
}
2018-06-13 11:01:56 +02:00
}
2018-05-01 15:01:49 +02:00
}
}
fn read_device ( & self , usb : & hidapi ::HidApi , dev_info : & hidapi ::HidDeviceInfo ) -> Result < Device , Self ::Error > {
let handle = self . open_path ( | | usb . open_path ( & dev_info . path ) ) ? ;
2018-06-13 11:01:56 +02:00
let manufacturer = dev_info . manufacturer_string . clone ( ) . unwrap_or_else ( | | " Unknown " . to_owned ( ) ) ;
let name = dev_info . product_string . clone ( ) . unwrap_or_else ( | | " Unknown " . to_owned ( ) ) ;
let serial = dev_info . serial_number . clone ( ) . unwrap_or_else ( | | " Unknown " . to_owned ( ) ) ;
2018-05-01 15:01:49 +02:00
match self . get_address ( & handle ) {
Ok ( Some ( addr ) ) = > {
Ok ( Device {
path : dev_info . path . clone ( ) ,
info : WalletInfo {
2018-06-18 11:55:23 +02:00
name ,
manufacturer ,
serial ,
2018-05-01 15:01:49 +02:00
address : addr ,
} ,
} )
}
// This variant is not possible, but the trait forces this return type
2018-06-13 11:01:56 +02:00
Ok ( None ) = > Err ( Error ::Impossible ) ,
2018-05-01 15:01:49 +02:00
Err ( e ) = > Err ( e ) ,
}
}
fn list_devices ( & self ) -> Vec < WalletInfo > {
self . devices . read ( ) . iter ( ) . map ( | d | d . info . clone ( ) ) . collect ( )
}
// Not used because it is not supported by Ledger
fn list_locked_devices ( & self ) -> Vec < String > {
vec! [ ]
}
fn get_wallet ( & self , address : & Address ) -> Option < WalletInfo > {
self . devices . read ( ) . iter ( ) . find ( | d | & d . info . address = = address ) . map ( | d | d . info . clone ( ) )
}
fn get_address ( & self , device : & hidapi ::HidDevice ) -> Result < Option < Address > , Self ::Error > {
2018-06-13 11:01:56 +02:00
let ledger_version = Self ::get_firmware_version ( & device ) ? ;
if ledger_version < FirmwareVersion ::new ( 1 , 0 , 3 ) {
return Err ( Error ::Protocol ( " Ledger version 1.0.3 is required " ) ) ;
2018-05-01 15:01:49 +02:00
}
2018-06-13 11:01:56 +02:00
let derivation_path = self . get_derivation_path ( ) ;
2018-05-01 15:01:49 +02:00
let key_and_address = Self ::send_apdu ( device , commands ::GET_ETH_PUBLIC_ADDRESS , 0 , 0 , derivation_path ) ? ;
if key_and_address . len ( ) ! = 107 { // 1 + 65 PK + 1 + 40 Addr (ascii-hex)
return Err ( Error ::Protocol ( " Key packet size mismatch " ) ) ;
}
let address_string = ::std ::str ::from_utf8 ( & key_and_address [ 67 .. 107 ] )
. map_err ( | _ | Error ::Protocol ( " Invalid address string " ) ) ? ;
let address = Address ::from_str ( & address_string )
. map_err ( | _ | Error ::Protocol ( " Invalid address string " ) ) ? ;
Ok ( Some ( address ) )
}
fn open_path < R , F > ( & self , f : F ) -> Result < R , Self ::Error >
where F : Fn ( ) -> Result < R , & 'static str >
{
f ( ) . map_err ( Into ::into )
}
}
2018-02-27 16:45:16 +01:00
/// Ledger event handler
2018-05-01 15:01:49 +02:00
/// A separate thread is handling incoming events
2018-03-02 18:30:25 +01:00
///
/// Note, that this run to completion and race-conditions can't occur but this can
/// therefore starve other events for being process with a spinlock or similar
2018-05-01 15:01:49 +02:00
struct EventHandler {
2018-02-27 16:45:16 +01:00
ledger : Weak < Manager > ,
}
impl EventHandler {
2018-03-02 18:30:25 +01:00
/// Ledger event handler constructor
2018-05-01 15:01:49 +02:00
fn new ( ledger : Weak < Manager > ) -> Self {
2018-06-18 11:55:23 +02:00
Self { ledger }
2018-02-27 16:45:16 +01:00
}
}
impl libusb ::Hotplug for EventHandler {
fn device_arrived ( & mut self , device : libusb ::Device ) {
2018-03-02 18:30:25 +01:00
debug! ( target : " hw " , " Ledger arrived " ) ;
2018-02-27 16:45:16 +01:00
if let ( Some ( ledger ) , Ok ( _ ) ) = ( self . ledger . upgrade ( ) , Manager ::is_valid_ledger ( & device ) ) {
2018-06-18 11:55:23 +02:00
if try_connect_polling ( & ledger , & POLLING_DURATION , DeviceDirection ::Arrived ) ! = true {
2018-06-13 11:01:56 +02:00
debug! ( target : " hw " , " No Ledger device was connected " ) ;
2018-02-27 16:45:16 +01:00
}
}
}
fn device_left ( & mut self , device : libusb ::Device ) {
2018-03-02 18:30:25 +01:00
debug! ( target : " hw " , " Ledger left " ) ;
2018-02-27 16:45:16 +01:00
if let ( Some ( ledger ) , Ok ( _ ) ) = ( self . ledger . upgrade ( ) , Manager ::is_valid_ledger ( & device ) ) {
2018-06-18 11:55:23 +02:00
if try_connect_polling ( & ledger , & POLLING_DURATION , DeviceDirection ::Left ) ! = true {
2018-06-13 11:01:56 +02:00
debug! ( target : " hw " , " No Ledger device was disconnected " ) ;
2018-02-27 16:45:16 +01:00
}
}
}
2017-02-10 01:07:06 +01:00
}
2018-06-13 11:01:56 +02:00
#[ cfg(test) ]
mod tests {
2017-07-06 11:26:14 +02:00
use rustc_hex ::FromHex ;
2018-06-13 11:01:56 +02:00
use super ::* ;
/// This test can't be run without an actual ledger device connected with the `Ledger Wallet Ethereum application` running
#[ test ]
#[ ignore ]
fn sign_personal_message ( ) {
let manager = Manager ::new (
Arc ::new ( Mutex ::new ( hidapi ::HidApi ::new ( ) . expect ( " HidApi " ) ) ) ,
Arc ::new ( AtomicBool ::new ( false ) )
) . expect ( " HardwareWalletManager " ) ;
// Update device list
manager . update_devices ( DeviceDirection ::Arrived ) . expect ( " No Ledger found, make sure you have a unlocked Ledger connected with the Ledger Wallet Ethereum running " ) ;
// Fetch the ethereum address of a connected ledger device
let address = manager . list_devices ( )
. iter ( )
. filter ( | d | d . manufacturer = = " Ledger " . to_string ( ) )
. nth ( 0 )
. map ( | d | d . address . clone ( ) )
. expect ( " No ledger device detected " ) ;
// 44 bytes transaction
let tx = FromHex ::from_hex ( " eb018504a817c80082520894a6ca2e6707f2cc189794a9dd459d5b05ed1bcd1c8703f26fcfb7a22480018080 " ) . unwrap ( ) ;
let signature = manager . sign_transaction ( & address , & tx ) ;
assert! ( signature . is_ok ( ) ) ;
}
/// This test can't be run without an actual ledger device connected with the `Ledger Wallet Ethereum application` running
#[ test ]
#[ ignore ]
fn smoke ( ) {
let manager = Manager ::new (
Arc ::new ( Mutex ::new ( hidapi ::HidApi ::new ( ) . expect ( " HidApi " ) ) ) ,
Arc ::new ( AtomicBool ::new ( false ) )
) . expect ( " HardwareWalletManager " ) ;
// Update device list
manager . update_devices ( DeviceDirection ::Arrived ) . expect ( " No Ledger found, make sure you have a unlocked Ledger connected with the Ledger Wallet Ethereum running " ) ;
// Fetch the ethereum address of a connected ledger device
let address = manager . list_devices ( )
. iter ( )
. filter ( | d | d . manufacturer = = " Ledger " . to_string ( ) )
. nth ( 0 )
. map ( | d | d . address . clone ( ) )
. expect ( " No ledger device detected " ) ;
// 44 bytes transaction
let tx = FromHex ::from_hex ( " eb018504a817c80082520894a6ca2e6707f2cc189794a9dd459d5b05ed1bcd1c8703f26fcfb7a22480018080 " ) . unwrap ( ) ;
let signature = manager . sign_transaction ( & address , & tx ) ;
println! ( " Got {:?} " , signature ) ;
assert! ( signature . is_ok ( ) ) ;
// 218 bytes transaction
let large_tx = FromHex ::from_hex ( " f86b028511cfc15d00825208940975ca9f986eee35f5cbba2d672ad9bc8d2a08448766c92c5cf830008026a0d2b0d401b543872d2a6a50de92455decbb868440321bf63a13b310c069e2ba5ba03c6d51bcb2e1653be86546b87f8a12ddb45b6d4e568420299b96f64c19701040f86b028511cfc15d00825208940975ca9f986eee35f5cbba2d672ad9bc8d2a08448766c92c5cf830008026a0d2b0d401b543872d2a6a50de92455decbb868440321bf63a13b310c069e2ba5ba03c6d51bcb2e1653be86546b87f8a12ddb45b6d4e568420299b96f64c19701040 " ) . unwrap ( ) ;
let signature = manager . sign_transaction ( & address , & large_tx ) ;
println! ( " Got {:?} " , signature ) ;
assert! ( signature . is_ok ( ) ) ;
// 36206 bytes transaction (You need to confirm many transaction on your `Ledger` for this)
let huge_tx = FromHex ::from_hex ( " f86b028511cfc15d00825208940975ca9f986eee35f5cbba2d672ad9bc8d2a08448766c92c5cf830008026a0d2b0d401b543872d2a6a50de92455decbb868440321bf63a13b310c069e2ba5ba03c6d51bcb2e1653be86546b87f8a12ddb45b6d4e568420299b96f64c19701040f86b028511cfc15d00825208940975ca9f986eee35f5cbba2d672ad9bc8d2a08448766c92c5cf830008026a0d2b0d401b543872d2a6a50de92455decbb868440321bf63a13b310c069e2ba5ba03c6d51bcb2e1653be86546b87f8a12ddb45b6d4e568420299b96f64c1970104000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7cd58ab9190c2792714ab06df5b67e66d9e3873eed251d7beb4fa252d6fed6a0ab1e5fabd284f40878d38f6e63d72eec55c6e1aa8d79c06adf714e3523a1f83da763f4bcc9d34424aba82981534066379c1cba244352042de13168556be761f8b1000807b6a6cd340b97a93cd850ee54335b1043bac153c1b0736a88919bb1a21d6befba34d9af51a9b3eb39164c64fe88efe62f136d0bc83cad1f963aec6344b9e406f7381ad2462dcf1434c90c426ee907e6a05abe39c2b36d1dfb966bcf5a4de5af9f07819256357489365c96b21d92a103a776b656fc10ad1083cf679d240bf09bf2eb7635d7bfa969ce7fbb4e0cd5835f79ca9f5583e3a9eca219fab2f773d9c7e838a7a9ef8755dc22e4880367c2b5e40795fe526fc5d1461e50d5cb053e001206460fc6617a38499db525112a7edde38b9547853ad6e5ab359233611148f196501deafae414acde9df81efd7c4144b8fd27f63ac252ecede9609b3f9e634ae95c13058ad2b4529bbb07b5d7ac567c2da994084c3c73ef7c453fc139fcdb3939461da5bf0fa3f2a83517463d02b903af5d845929cf12c9a1479f6801f20085887a94d72814671dac994e14b2faa3251d465ce16d855f33259d94fcc9553b25b488d5c45fe74de60c303bc75bcdde9374ca268767f5767638d1aec5f6f95cab8e9e27b9a80ddf3dbbe24f790debd9e3baa30145d499dd1afb5662a11788b1bb3dedc1ebc5eff9641fa6918d958e4738bae3854e4cd43f9173cd4c9c821190ec287c18035a530c2dc63077d292b3a35b3756ba9e08295a02e37d332552f9f4fdbb945df004aa5b072f9f0e9fc2e4ed6fe455d95b003e5e593dcbfad0b3b47aa855b34008e0e9a2e1cc23b975a3e6808be59dcaa8a87145c1d5183c799d06100d500227e6a758757b4f7d042b3485aa0ce5e91b2b2e67d3cfdf1c226b7ab90e40f0a0d30cbbf425f495bd5a80202909ad419745a59210e2c42a1846e656f67a764ee307abbd76fbb0c99a702253b7a753c3b93e974881f3c97987856b57449e92ffa759da041a2acac59ea2d53836098196355ae0aa2a185dbb002a67c1a278a6032f156bc1e6d7f4ff6c674126af272fdfd1dcd6a810f42878164f1c7ae346b0dd91b678b363d0e33f4b81f2d7cc14da555dcbe4b9f80ac0fed6265a6ecce278888c9794373dcb0d20aa811a9fe9864fab25eaf12764bb2f1a68cd8756cd0b3583f6e5ec74ca5c327b3f6599fa9ec32ccd1831ae323689ef4a1b1a587cbbd2120e0bb8e59f9fc87d93e0365eb36557be6c45c30c1baeba33cdaa877a87e51fd70f2b5521078607d012d65f1fcca8051a01004a6d10f662dfa6445b2ac015cb3ce8fde56bbff93f5d620171e638c6e05504c2aeeeb74c7667aee1709846cb84d345a011c21c1b4e3fd09774ab4dcc63bda04bb0f4fc49d6145d202d807cc2d8eab29b3babe15e53a3656daf0b022ac37513f77660d43d60bdd3e882eef239bfe13dba2e12707733d56e49f638005e06019a7335d8184f1039ab18084de896a946c23045e5c164dc9d32f2f227c89f717a87d1243516b922e5f270c751f1bdb2b1d3a38a15b18a7b8b7e0818573f31320d496e14a348f979b7606c5124e007493f2f40c931f68e3483a46ab2b853a90bd38ae85e6252fece6fd36f7dad0d07b6763d8001a0d6abee62452904f979cc52fa15001b06eef08f17d6e16d493d227ce9277392337a1c71713603e03803d38d1c24184b52049bc029f4f00b22d2acdef91c776a74aa184cc84b0e764f463ed05c2e16a7a0dcb6c27dd4aeca8aeac1545b48896775ba3fe9de4ea36e946d8f4ec16ca7ae58165e8ddc9189d5cc56
" ).unwrap();
let signature = manager . sign_transaction ( & address , & huge_tx ) ;
println! ( " Got {:?} " , signature ) ;
assert! ( signature . is_ok ( ) ) ;
}
2017-02-10 01:07:06 +01:00
}