Handle outdated and current ledger-solana-apps (#9605)
* Add version check, handling for outdated+current ledger-solana-apps * Add derivation-path prefix
This commit is contained in:
parent
44cced3ffc
commit
41fec5bd5b
|
@ -10,16 +10,25 @@ use std::{cmp::min, fmt, sync::Arc};
|
||||||
|
|
||||||
static CHECK_MARK: Emoji = Emoji("✅ ", "");
|
static CHECK_MARK: Emoji = Emoji("✅ ", "");
|
||||||
|
|
||||||
|
const DEPRECATE_VERSION_BEFORE: FirmwareVersion = FirmwareVersion {
|
||||||
|
major: 0,
|
||||||
|
minor: 2,
|
||||||
|
patch: 0,
|
||||||
|
pre: Vec::new(),
|
||||||
|
build: Vec::new(),
|
||||||
|
};
|
||||||
|
|
||||||
const HARDENED_BIT: u32 = 1 << 31;
|
const HARDENED_BIT: u32 = 1 << 31;
|
||||||
|
|
||||||
const APDU_TAG: u8 = 0x05;
|
const APDU_TAG: u8 = 0x05;
|
||||||
const APDU_CLA: u8 = 0xe0;
|
const APDU_CLA: u8 = 0xe0;
|
||||||
const APDU_PAYLOAD_HEADER_LEN: usize = 8;
|
const APDU_PAYLOAD_HEADER_LEN: usize = 7;
|
||||||
|
const DEPRECATED_APDU_PAYLOAD_HEADER_LEN: usize = 8;
|
||||||
const P1_NON_CONFIRM: u8 = 0x00;
|
const P1_NON_CONFIRM: u8 = 0x00;
|
||||||
const P1_CONFIRM: u8 = 0x01;
|
const P1_CONFIRM: u8 = 0x01;
|
||||||
const P2_EXTEND: u8 = 0x01;
|
const P2_EXTEND: u8 = 0x01;
|
||||||
const P2_MORE: u8 = 0x02;
|
const P2_MORE: u8 = 0x02;
|
||||||
const MAX_CHUNK_SIZE: usize = 300;
|
const MAX_CHUNK_SIZE: usize = 255;
|
||||||
|
|
||||||
const SOL_DERIVATION_PATH_BE: [u8; 8] = [0x80, 0, 0, 44, 0x80, 0, 0x01, 0xF5]; // 44'/501', Solana
|
const SOL_DERIVATION_PATH_BE: [u8; 8] = [0x80, 0, 0, 44, 0x80, 0, 0x01, 0xF5]; // 44'/501', Solana
|
||||||
|
|
||||||
|
@ -46,16 +55,19 @@ const HID_PREFIX_ZERO: usize = 1;
|
||||||
const HID_PREFIX_ZERO: usize = 0;
|
const HID_PREFIX_ZERO: usize = 0;
|
||||||
|
|
||||||
mod commands {
|
mod commands {
|
||||||
#[allow(dead_code)]
|
pub const DEPRECATED_GET_APP_CONFIGURATION: u8 = 0x01;
|
||||||
pub const GET_APP_CONFIGURATION: u8 = 0x01;
|
pub const DEPRECATED_GET_PUBKEY: u8 = 0x02;
|
||||||
pub const GET_PUBKEY: u8 = 0x02;
|
pub const DEPRECATED_SIGN_MESSAGE: u8 = 0x03;
|
||||||
pub const SIGN_MESSAGE: u8 = 0x03;
|
pub const GET_APP_CONFIGURATION: u8 = 0x04;
|
||||||
|
pub const GET_PUBKEY: u8 = 0x05;
|
||||||
|
pub const SIGN_MESSAGE: u8 = 0x06;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ledger Wallet device
|
/// Ledger Wallet device
|
||||||
pub struct LedgerWallet {
|
pub struct LedgerWallet {
|
||||||
pub device: hidapi::HidDevice,
|
pub device: hidapi::HidDevice,
|
||||||
pub pretty_path: String,
|
pub pretty_path: String,
|
||||||
|
pub version: FirmwareVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for LedgerWallet {
|
impl fmt::Debug for LedgerWallet {
|
||||||
|
@ -69,6 +81,7 @@ impl LedgerWallet {
|
||||||
Self {
|
Self {
|
||||||
device,
|
device,
|
||||||
pretty_path: String::default(),
|
pretty_path: String::default(),
|
||||||
|
version: FirmwareVersion::new(0, 0, 0),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,10 +97,17 @@ impl LedgerWallet {
|
||||||
// * APDU_INS (1 byte)
|
// * APDU_INS (1 byte)
|
||||||
// * APDU_P1 (1 byte)
|
// * APDU_P1 (1 byte)
|
||||||
// * APDU_P2 (1 byte)
|
// * APDU_P2 (1 byte)
|
||||||
// * APDU_LENGTH (2 bytes)
|
// * APDU_LENGTH (1 byte (2 bytes DEPRECATED))
|
||||||
// * APDU_Payload (Variable)
|
// * APDU_Payload (Variable)
|
||||||
//
|
//
|
||||||
fn write(&self, command: u8, p1: u8, p2: u8, data: &[u8]) -> Result<(), RemoteWalletError> {
|
fn write(
|
||||||
|
&self,
|
||||||
|
command: u8,
|
||||||
|
p1: u8,
|
||||||
|
p2: u8,
|
||||||
|
data: &[u8],
|
||||||
|
outdated_app: bool,
|
||||||
|
) -> Result<(), RemoteWalletError> {
|
||||||
let data_len = data.len();
|
let data_len = data.len();
|
||||||
let mut offset = 0;
|
let mut offset = 0;
|
||||||
let mut sequence_number = 0;
|
let mut sequence_number = 0;
|
||||||
|
@ -95,7 +115,11 @@ impl LedgerWallet {
|
||||||
|
|
||||||
while sequence_number == 0 || offset < data_len {
|
while sequence_number == 0 || offset < data_len {
|
||||||
let header = if sequence_number == 0 {
|
let header = if sequence_number == 0 {
|
||||||
LEDGER_TRANSPORT_HEADER_LEN + APDU_PAYLOAD_HEADER_LEN
|
if outdated_app {
|
||||||
|
LEDGER_TRANSPORT_HEADER_LEN + DEPRECATED_APDU_PAYLOAD_HEADER_LEN
|
||||||
|
} else {
|
||||||
|
LEDGER_TRANSPORT_HEADER_LEN + APDU_PAYLOAD_HEADER_LEN
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
LEDGER_TRANSPORT_HEADER_LEN
|
LEDGER_TRANSPORT_HEADER_LEN
|
||||||
};
|
};
|
||||||
|
@ -111,17 +135,30 @@ impl LedgerWallet {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if sequence_number == 0 {
|
if sequence_number == 0 {
|
||||||
let data_len = data.len() + 6;
|
if outdated_app {
|
||||||
chunk[5..13].copy_from_slice(&[
|
let data_len = data.len() + 6;
|
||||||
(data_len >> 8) as u8,
|
chunk[5..13].copy_from_slice(&[
|
||||||
(data_len & 0xff) as u8,
|
(data_len >> 8) as u8,
|
||||||
APDU_CLA,
|
(data_len & 0xff) as u8,
|
||||||
command,
|
APDU_CLA,
|
||||||
p1,
|
command,
|
||||||
p2,
|
p1,
|
||||||
(data.len() >> 8) as u8,
|
p2,
|
||||||
data.len() as u8,
|
(data.len() >> 8) as u8,
|
||||||
]);
|
data.len() as u8,
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
let data_len = data.len() + 5;
|
||||||
|
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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
chunk[header..header + size].copy_from_slice(&data[offset..offset + size]);
|
chunk[header..header + size].copy_from_slice(&data[offset..offset + size]);
|
||||||
|
@ -233,7 +270,7 @@ impl LedgerWallet {
|
||||||
p2: u8,
|
p2: u8,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
) -> Result<Vec<u8>, RemoteWalletError> {
|
) -> Result<Vec<u8>, RemoteWalletError> {
|
||||||
self.write(command, p1, p2, data)?;
|
self.write(command, p1, p2, data, self.outdated_app())?;
|
||||||
if p1 == P1_CONFIRM && is_last_part(p2) {
|
if p1 == P1_CONFIRM && is_last_part(p2) {
|
||||||
println!(
|
println!(
|
||||||
"Waiting for your approval on {} {}",
|
"Waiting for your approval on {} {}",
|
||||||
|
@ -248,16 +285,31 @@ impl LedgerWallet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _get_firmware_version(&self) -> Result<FirmwareVersion, RemoteWalletError> {
|
fn get_firmware_version(&self) -> Result<FirmwareVersion, RemoteWalletError> {
|
||||||
let ver = self.send_apdu(commands::GET_APP_CONFIGURATION, 0, 0, &[])?;
|
if let Ok(version) = self.send_apdu(commands::GET_APP_CONFIGURATION, 0, 0, &[]) {
|
||||||
if ver.len() != 4 {
|
if version.len() != 5 {
|
||||||
return Err(RemoteWalletError::Protocol("Version packet size mismatch"));
|
return Err(RemoteWalletError::Protocol("Version packet size mismatch"));
|
||||||
|
}
|
||||||
|
Ok(FirmwareVersion::new(
|
||||||
|
version[2].into(),
|
||||||
|
version[3].into(),
|
||||||
|
version[4].into(),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
let version = self.send_apdu(commands::DEPRECATED_GET_APP_CONFIGURATION, 0, 0, &[])?;
|
||||||
|
if version.len() != 4 {
|
||||||
|
return Err(RemoteWalletError::Protocol("Version packet size mismatch"));
|
||||||
|
}
|
||||||
|
Ok(FirmwareVersion::new(
|
||||||
|
version[1].into(),
|
||||||
|
version[2].into(),
|
||||||
|
version[3].into(),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
Ok(FirmwareVersion::new(
|
}
|
||||||
ver[1].into(),
|
|
||||||
ver[2].into(),
|
fn outdated_app(&self) -> bool {
|
||||||
ver[3].into(),
|
self.version < DEPRECATE_VERSION_BEFORE
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,7 +319,7 @@ impl RemoteWallet for LedgerWallet {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_device(
|
fn read_device(
|
||||||
&self,
|
&mut self,
|
||||||
dev_info: &hidapi::DeviceInfo,
|
dev_info: &hidapi::DeviceInfo,
|
||||||
) -> Result<RemoteWalletInfo, RemoteWalletError> {
|
) -> Result<RemoteWalletInfo, RemoteWalletError> {
|
||||||
let manufacturer = dev_info
|
let manufacturer = dev_info
|
||||||
|
@ -287,6 +339,8 @@ impl RemoteWallet for LedgerWallet {
|
||||||
.clone()
|
.clone()
|
||||||
.unwrap_or("Unknown")
|
.unwrap_or("Unknown")
|
||||||
.to_string();
|
.to_string();
|
||||||
|
let version = self.get_firmware_version()?;
|
||||||
|
self.version = version;
|
||||||
let pubkey_result = self.get_pubkey(&DerivationPath::default(), false);
|
let pubkey_result = self.get_pubkey(&DerivationPath::default(), false);
|
||||||
let (pubkey, error) = match pubkey_result {
|
let (pubkey, error) = match pubkey_result {
|
||||||
Ok(pubkey) => (pubkey, None),
|
Ok(pubkey) => (pubkey, None),
|
||||||
|
@ -309,7 +363,11 @@ impl RemoteWallet for LedgerWallet {
|
||||||
let derivation_path = extend_and_serialize(derivation_path);
|
let derivation_path = extend_and_serialize(derivation_path);
|
||||||
|
|
||||||
let key = self.send_apdu(
|
let key = self.send_apdu(
|
||||||
commands::GET_PUBKEY,
|
if self.outdated_app() {
|
||||||
|
commands::DEPRECATED_GET_PUBKEY
|
||||||
|
} else {
|
||||||
|
commands::GET_PUBKEY
|
||||||
|
},
|
||||||
if confirm_key {
|
if confirm_key {
|
||||||
P1_CONFIRM
|
P1_CONFIRM
|
||||||
} else {
|
} else {
|
||||||
|
@ -329,7 +387,11 @@ impl RemoteWallet for LedgerWallet {
|
||||||
derivation_path: &DerivationPath,
|
derivation_path: &DerivationPath,
|
||||||
data: &[u8],
|
data: &[u8],
|
||||||
) -> Result<Signature, RemoteWalletError> {
|
) -> Result<Signature, RemoteWalletError> {
|
||||||
let mut payload = extend_and_serialize(derivation_path);
|
let mut payload = if self.outdated_app() {
|
||||||
|
extend_and_serialize(derivation_path)
|
||||||
|
} else {
|
||||||
|
extend_and_serialize_multiple(&[derivation_path])
|
||||||
|
};
|
||||||
if data.len() > u16::max_value() as usize {
|
if data.len() > u16::max_value() as usize {
|
||||||
return Err(RemoteWalletError::InvalidInput(
|
return Err(RemoteWalletError::InvalidInput(
|
||||||
"Message to sign is too long".to_string(),
|
"Message to sign is too long".to_string(),
|
||||||
|
@ -347,8 +409,10 @@ impl RemoteWallet for LedgerWallet {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pack the first chunk
|
// Pack the first chunk
|
||||||
for byte in (data.len() as u16).to_be_bytes().iter() {
|
if self.outdated_app() {
|
||||||
payload.push(*byte);
|
for byte in (data.len() as u16).to_be_bytes().iter() {
|
||||||
|
payload.push(*byte);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
payload.extend_from_slice(data);
|
payload.extend_from_slice(data);
|
||||||
trace!("Serialized payload length {:?}", payload.len());
|
trace!("Serialized payload length {:?}", payload.len());
|
||||||
|
@ -360,14 +424,27 @@ impl RemoteWallet for LedgerWallet {
|
||||||
};
|
};
|
||||||
|
|
||||||
let p1 = P1_CONFIRM;
|
let p1 = P1_CONFIRM;
|
||||||
let mut result = self.send_apdu(commands::SIGN_MESSAGE, p1, p2, &payload)?;
|
let mut result = self.send_apdu(
|
||||||
|
if self.outdated_app() {
|
||||||
|
commands::DEPRECATED_SIGN_MESSAGE
|
||||||
|
} else {
|
||||||
|
commands::SIGN_MESSAGE
|
||||||
|
},
|
||||||
|
p1,
|
||||||
|
p2,
|
||||||
|
&payload,
|
||||||
|
)?;
|
||||||
|
|
||||||
// Pack and send the remaining chunks
|
// Pack and send the remaining chunks
|
||||||
if !remaining_data.is_empty() {
|
if !remaining_data.is_empty() {
|
||||||
let mut chunks: Vec<_> = remaining_data
|
let mut chunks: Vec<_> = remaining_data
|
||||||
.chunks(MAX_CHUNK_SIZE)
|
.chunks(MAX_CHUNK_SIZE)
|
||||||
.map(|data| {
|
.map(|data| {
|
||||||
let mut payload = (data.len() as u16).to_be_bytes().to_vec();
|
let mut payload = if self.outdated_app() {
|
||||||
|
(data.len() as u16).to_be_bytes().to_vec()
|
||||||
|
} else {
|
||||||
|
vec![]
|
||||||
|
};
|
||||||
payload.extend_from_slice(data);
|
payload.extend_from_slice(data);
|
||||||
let p2 = P2_EXTEND | P2_MORE;
|
let p2 = P2_EXTEND | P2_MORE;
|
||||||
(p2, payload)
|
(p2, payload)
|
||||||
|
@ -419,6 +496,14 @@ fn extend_and_serialize(derivation_path: &DerivationPath) -> Vec<u8> {
|
||||||
concat_derivation
|
concat_derivation
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn extend_and_serialize_multiple(derivation_paths: &[&DerivationPath]) -> Vec<u8> {
|
||||||
|
let mut concat_derivation = vec![derivation_paths.len() as u8];
|
||||||
|
for derivation_path in derivation_paths {
|
||||||
|
concat_derivation.append(&mut extend_and_serialize(derivation_path));
|
||||||
|
}
|
||||||
|
concat_derivation
|
||||||
|
}
|
||||||
|
|
||||||
/// Choose a Ledger wallet based on matching info fields
|
/// Choose a Ledger wallet based on matching info fields
|
||||||
pub fn get_ledger_from_info(
|
pub fn get_ledger_from_info(
|
||||||
info: RemoteWalletInfo,
|
info: RemoteWalletInfo,
|
||||||
|
|
|
@ -179,7 +179,7 @@ pub trait RemoteWallet {
|
||||||
|
|
||||||
/// Parse device info and get device base pubkey
|
/// Parse device info and get device base pubkey
|
||||||
fn read_device(
|
fn read_device(
|
||||||
&self,
|
&mut self,
|
||||||
dev_info: &hidapi::DeviceInfo,
|
dev_info: &hidapi::DeviceInfo,
|
||||||
) -> Result<RemoteWalletInfo, RemoteWalletError>;
|
) -> Result<RemoteWalletInfo, RemoteWalletError>;
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue