Skip to content

Commit

Permalink
Linux: Use udev backend; add udev rules tool
Browse files Browse the repository at this point in the history
  • Loading branch information
Tyera Eulberg committed Feb 6, 2020
1 parent 5c2fc08 commit 3fad635
Show file tree
Hide file tree
Showing 4 changed files with 89 additions and 90 deletions.
60 changes: 10 additions & 50 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions remote-wallet/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@ homepage = "https://solana.com/"
[dependencies]
base32 = "0.4.0"
dialoguer = "0.5.0"
hidapi = "1.1.1"
hidapi = { version = "1.1.1", default-features = false, features = ["linux-static-hidraw"] }
log = "0.4.8"
parking_lot = "0.7"
rusb = "0.5.5"
semver = "0.9"
solana-sdk = { path = "../sdk", version = "0.24.0" }
thiserror = "1.0"

[dev-dependencies]
[[bin]]
name = "solana-ledger-udev"
path = "src/bin/ledger-udev.rs"
42 changes: 42 additions & 0 deletions remote-wallet/src/bin/ledger-udev.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/// Implements udev rules on Linux for supported Ledger devices
/// This script must be run with sudo privileges
use std::{
error,
fs::{File, OpenOptions},
io::{Read, Write},
path::Path,
};

const LEDGER_UDEV_RULES: &'static str = r#"
# Nano S
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0001|1000|1001|1002|1003|1004|1005|1006|1007|1008|1009|100a|100b|100c|100d|100e|100f|1010|1011|1012|1013|1014|1015|1016|1017|1018|1019|101a|101b|101c|101d|101e|101f", TAG+="uaccess", TAG+="udev-acl", MODE="0666"
# Nano X
SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c97", ATTRS{idProduct}=="0004|4000|4001|4002|4003|4004|4005|4006|4007|4008|4009|400a|400b|400c|400d|400e|400f|4010|4011|4012|4013|4014|4015|4016|4017|4018|4019|401a|401b|401c|401d|401e|401f", TAG+="uaccess", TAG+="udev-acl", MODE="0666"
"#;

fn main() -> Result<(), Box<dyn error::Error>> {
if cfg!(target_os = "linux") {
let mut contents = String::new();
if Path::new("/etc/udev/rules.d/20-hw1.rules").exists() {
let mut file = File::open("/etc/udev/rules.d/20-hw1.rules")?;
file.read_to_string(&mut contents)?;
}
if !contents.contains(LEDGER_UDEV_RULES) {
let mut file = OpenOptions::new()
.append(true)
.create(true)
.open("/etc/udev/rules.d/20-hw1.rules")
.map_err(|e| {
println!("Could not write to file; this script requires sudo privileges");
e
})?;
file.write(LEDGER_UDEV_RULES.as_bytes())?;
println!("Ledger udev rules written");
} else {
println!("Ledger udev rules already in place");
}
} else {
println!("Mismatched target_os; udev rules only required on linux os");
}
Ok(())
}
70 changes: 33 additions & 37 deletions remote-wallet/src/ledger.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,17 @@ const SOL_DERIVATION_PATH_BE: [u8; 8] = [0x80, 0, 0, 44, 0x80, 0, 0x01, 0xF5]; /

/// Ledger vendor ID
const LEDGER_VID: u16 = 0x2c97;
/// Ledger product IDs: [Nano S and Nano X]
// TODO: do we need to support Blue?
const _LEDGER_PIDS: [u16; 2] = [0x0001, 0x0004]; // TODO: Nano S pid doesn't match expected LEDGER_PIDS value...
/// Ledger product IDs: Nano S and Nano X
const LEDGER_NANO_S_PIDS: [u16; 33] = [
0x0001, 0x1000, 0x1001, 0x1002, 0x1003, 0x1004, 0x1005, 0x1006, 0x1007, 0x1008, 0x1009, 0x100a,
0x100b, 0x100c, 0x100d, 0x100e, 0x100f, 0x1010, 0x1011, 0x1012, 0x1013, 0x1014, 0x1015, 0x1016,
0x1017, 0x1018, 0x1019, 0x101a, 0x101b, 0x101c, 0x101d, 0x101e, 0x101f,
];
const LEDGER_NANO_X_PIDS: [u16; 33] = [
0x0004, 0x4000, 0x4001, 0x4002, 0x4003, 0x4004, 0x4005, 0x4006, 0x4007, 0x4008, 0x4009, 0x400a,
0x400b, 0x400c, 0x400d, 0x400e, 0x400f, 0x4010, 0x4011, 0x4012, 0x4013, 0x4014, 0x4015, 0x4016,
0x4017, 0x4018, 0x4019, 0x401a, 0x401b, 0x401c, 0x401d, 0x401e, 0x401f,
];
const LEDGER_TRANSPORT_HEADER_LEN: usize = 5;

const MAX_CHUNK_SIZE: usize = 255;
Expand Down Expand Up @@ -181,7 +189,7 @@ impl LedgerWallet {
trace!("Read status {:x}", status);
#[allow(clippy::match_overlapping_arm)]
match status {
// TODO: These need to be aligned with solana Ledger app error codes, and clippy allowance removed
// These need to be aligned with solana Ledger app error codes, and clippy allowance removed
0x6700 => Err(RemoteWalletError::Protocol("Incorrect length")),
0x6982 => Err(RemoteWalletError::Protocol(
"Security status not satisfied (Canceled by user)",
Expand Down Expand Up @@ -227,19 +235,6 @@ impl LedgerWallet {
ver[3].into(),
))
}

fn get_derivation_path(&self, derivation: DerivationPath) -> Vec<u8> {
let byte = if derivation.change.is_some() { 4 } else { 3 };
let mut concat_derivation = vec![byte];
concat_derivation.extend_from_slice(&SOL_DERIVATION_PATH_BE);
concat_derivation.extend_from_slice(&[0x80, 0]);
concat_derivation.extend_from_slice(&derivation.account.to_be_bytes());
if let Some(change) = derivation.change {
concat_derivation.extend_from_slice(&[0x80, 0]);
concat_derivation.extend_from_slice(&change.to_be_bytes());
}
concat_derivation
}
}

impl RemoteWallet for LedgerWallet {
Expand Down Expand Up @@ -273,14 +268,7 @@ impl RemoteWallet for LedgerWallet {
}

fn get_pubkey(&self, derivation: DerivationPath) -> Result<Pubkey, RemoteWalletError> {
// let ledger_version = self.get_firmware_version()?;
// if ledger_version < FirmwareVersion::new(1, 0, 3) {
// return Err(RemoteWalletError::Protocol(
// "Ledger version 1.0.3 is required",
// ));
// }

let derivation_path = self.get_derivation_path(derivation);
let derivation_path = get_derivation_path(derivation);

let key = self.send_apdu(commands::GET_SOL_PUBKEY, 0, 0, &derivation_path)?;
if key.len() != 32 {
Expand All @@ -294,18 +282,12 @@ impl RemoteWallet for LedgerWallet {
derivation: DerivationPath,
transaction: Transaction,
) -> Result<Signature, RemoteWalletError> {
// TODO: see if need version check here
let _version = self.get_firmware_version()?;
// if version < FirmwareVersion::new(1, 0, 8) {
// return Err(RemoteWalletError::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(derivation);
let derivation_path = get_derivation_path(derivation);
let data = transaction.message_data();

let _firmware_version = self.get_firmware_version();

// Copy the address of the key (only done once)
chunk[0..derivation_path.len()].copy_from_slice(&derivation_path);

Expand Down Expand Up @@ -346,12 +328,26 @@ impl RemoteWallet for LedgerWallet {
}

/// Check if the detected device is a valid `Ledger device` by checking both the product ID and the vendor ID
pub fn is_valid_ledger(vendor_id: u16, _product_id: u16) -> bool {
// vendor_id == LEDGER_VID && LEDGER_PIDS.contains(&product_id)
pub fn is_valid_ledger(vendor_id: u16, product_id: u16) -> bool {
vendor_id == LEDGER_VID
&& (LEDGER_NANO_S_PIDS.contains(&product_id) || LEDGER_NANO_X_PIDS.contains(&product_id))
}

/// Build the derivation path byte array from a DerivationPath selection
fn get_derivation_path(derivation: DerivationPath) -> Vec<u8> {
let byte = if derivation.change.is_some() { 4 } else { 3 };
let mut concat_derivation = vec![byte];
concat_derivation.extend_from_slice(&SOL_DERIVATION_PATH_BE);
concat_derivation.extend_from_slice(&[0x80, 0]);
concat_derivation.extend_from_slice(&derivation.account.to_be_bytes());
if let Some(change) = derivation.change {
concat_derivation.extend_from_slice(&[0x80, 0]);
concat_derivation.extend_from_slice(&change.to_be_bytes());
}
concat_derivation
}

///
/// Choose a Ledger wallet based on matching info fields
pub fn get_ledger_from_info(
info: RemoteWalletInfo,
) -> Result<Arc<LedgerWallet>, RemoteWalletError> {
Expand Down

0 comments on commit 3fad635

Please sign in to comment.