From 63813fe69f12b6a968a074ccef4526c7d4bf91ba Mon Sep 17 00:00:00 2001 From: Tyera Eulberg Date: Thu, 14 May 2020 22:52:11 -0600 Subject: [PATCH] Add Ledger error codes (#10056) automerge --- Cargo.lock | 2 + remote-wallet/Cargo.toml | 2 + remote-wallet/src/ledger.rs | 60 +++++++++++---------- remote-wallet/src/ledger_error.rs | 86 ++++++++++++++++++++++++++++++ remote-wallet/src/lib.rs | 1 + remote-wallet/src/remote_wallet.rs | 66 ++++++++++++++++------- sdk/src/signature.rs | 2 +- 7 files changed, 169 insertions(+), 50 deletions(-) create mode 100644 remote-wallet/src/ledger_error.rs diff --git a/Cargo.lock b/Cargo.lock index 52fc9c9b7..1af04e345 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4525,6 +4525,8 @@ dependencies = [ "dialoguer", "hidapi", "log 0.4.8", + "num-derive 0.3.0", + "num-traits", "parking_lot 0.10.2", "semver", "solana-sdk", diff --git a/remote-wallet/Cargo.toml b/remote-wallet/Cargo.toml index f94a82a6b..76742441c 100644 --- a/remote-wallet/Cargo.toml +++ b/remote-wallet/Cargo.toml @@ -14,6 +14,8 @@ console = "0.10.1" dialoguer = "0.6.2" hidapi = { version = "1.2.1", default-features = false } log = "0.4.8" +num-derive = { version = "0.3" } +num-traits = { version = "0.2" } parking_lot = "0.10" semver = "0.9" solana-sdk = { path = "../sdk", version = "1.2.0" } diff --git a/remote-wallet/src/ledger.rs b/remote-wallet/src/ledger.rs index 763e4f310..de8c5ceb6 100644 --- a/remote-wallet/src/ledger.rs +++ b/remote-wallet/src/ledger.rs @@ -1,9 +1,13 @@ -use crate::remote_wallet::{ - DerivationPath, RemoteWallet, RemoteWalletError, RemoteWalletInfo, RemoteWalletManager, +use crate::{ + ledger_error::LedgerError, + remote_wallet::{ + DerivationPath, RemoteWallet, RemoteWalletError, RemoteWalletInfo, RemoteWalletManager, + }, }; use console::Emoji; use dialoguer::{theme::ColorfulTheme, Select}; use log::*; +use num_traits::FromPrimitive; use semver::Version as FirmwareVersion; use solana_sdk::{pubkey::Pubkey, signature::Signature}; use std::{cmp::min, fmt, sync::Arc}; @@ -30,6 +34,8 @@ const P2_EXTEND: u8 = 0x01; const P2_MORE: u8 = 0x02; const MAX_CHUNK_SIZE: usize = 255; +const APDU_SUCCESS_CODE: usize = 0x9000; + const SOL_DERIVATION_PATH_BE: [u8; 8] = [0x80, 0, 0, 44, 0x80, 0, 0x01, 0xF5]; // 44'/501', Solana /// Ledger vendor ID @@ -231,33 +237,7 @@ impl LedgerWallet { let status = (message[message.len() - 2] as usize) << 8 | (message[message.len() - 1] as usize); trace!("Read status {:x}", status); - #[allow(clippy::match_overlapping_arm)] - match status { - // These need to be aligned with solana Ledger app error codes, and clippy allowance removed - 0x6700 => Err(RemoteWalletError::Protocol( - "Solana app not open on Ledger device", - )), - 0x6802 => Err(RemoteWalletError::Protocol("Invalid parameter")), - 0x6803 => Err(RemoteWalletError::Protocol( - "Overflow: message longer than MAX_MESSAGE_LENGTH", - )), - 0x6982 => Err(RemoteWalletError::Protocol( - "Security status not satisfied (Canceled by user)", - )), - 0x6985 => Err(RemoteWalletError::UserCancel), - 0x6a80 => Err(RemoteWalletError::Protocol("Invalid data")), - 0x6a82 => Err(RemoteWalletError::Protocol("File not found")), - 0x6b00 => Err(RemoteWalletError::Protocol("Incorrect parameters")), - 0x6d00 => Err(RemoteWalletError::Protocol( - "Not implemented. Make sure the Ledger Solana Wallet app is running.", - )), - 0x6faa => Err(RemoteWalletError::Protocol( - "Your Ledger device needs to be unplugged", - )), - 0x6f00..=0x6fff => Err(RemoteWalletError::Protocol("Internal error")), - 0x9000 => Ok(()), - _ => Err(RemoteWalletError::Protocol("Unknown error")), - }?; + Self::parse_status(status)?; let new_len = message.len() - 2; message.truncate(new_len); Ok(message) @@ -311,6 +291,16 @@ impl LedgerWallet { fn outdated_app(&self) -> bool { self.version < DEPRECATE_VERSION_BEFORE } + + fn parse_status(status: usize) -> Result<(), RemoteWalletError> { + if status == APDU_SUCCESS_CODE { + Ok(()) + } else if let Some(err) = LedgerError::from_usize(status) { + Err(err.into()) + } else { + Err(RemoteWalletError::Protocol("Unknown error")) + } + } } impl RemoteWallet for LedgerWallet { @@ -582,4 +572,16 @@ mod tests { assert!(!is_last_part(p2)); assert!(is_last_part(p2 & !P2_MORE)); } + + #[test] + fn test_parse_status() { + assert_eq!(LedgerWallet::parse_status(APDU_SUCCESS_CODE).unwrap(), ()); + if let RemoteWalletError::LedgerError(err) = LedgerWallet::parse_status(0x6985).unwrap_err() + { + assert_eq!(err, LedgerError::UserCancel); + } + if let RemoteWalletError::Protocol(err) = LedgerWallet::parse_status(0x6fff).unwrap_err() { + assert_eq!(err, "Unknown error"); + } + } } diff --git a/remote-wallet/src/ledger_error.rs b/remote-wallet/src/ledger_error.rs new file mode 100644 index 000000000..6c714cb9c --- /dev/null +++ b/remote-wallet/src/ledger_error.rs @@ -0,0 +1,86 @@ +use num_derive::FromPrimitive; +use thiserror::Error; + +#[derive(Error, Debug, Clone, FromPrimitive, PartialEq)] +pub enum LedgerError { + #[error("Solana app not open on Ledger device")] + NoAppResponse = 0x6700, + + #[error("Ledger sdk exception")] + SdkException = 0x6801, + + #[error("Ledger invalid parameter")] + SdkInvalidParameter = 0x6802, + + #[error("Ledger overflow")] + SdkExceptionOverflow = 0x6803, + + #[error("Ledger security exception")] + SdkExceptionSecurity = 0x6804, + + #[error("Ledger invalid CRC")] + SdkInvalidCrc = 0x6805, + + #[error("Ledger invalid checksum")] + SdkInvalidChecksum = 0x6806, + + #[error("Ledger invalid counter")] + SdkInvalidCounter = 0x6807, + + #[error("Ledger operation not supported")] + SdkNotSupported = 0x6808, + + #[error("Ledger invalid state")] + SdkInvalidState = 0x6809, + + #[error("Ledger timeout")] + SdkTimeout = 0x6810, + + #[error("Ledger PIC exception")] + SdkExceptionPIC = 0x6811, + + #[error("Ledger app exit exception")] + SdkExceptionAppExit = 0x6812, + + #[error("Ledger IO overflow exception")] + SdkExceptionIoOverflow = 0x6813, + + #[error("Ledger IO header exception")] + SdkExceptionIoHeader = 0x6814, + + #[error("Ledger IO state exception")] + SdkExceptionIoState = 0x6815, + + #[error("Ledger IO reset exception")] + SdkExceptionIoReset = 0x6816, + + #[error("Ledger CX port exception")] + SdkExceptionCxPort = 0x6817, + + #[error("Ledger system exception")] + SdkExceptionSystem = 0x6818, + + #[error("Ledger out of space")] + SdkNotEnoughSpace = 0x6819, + + #[error("Ledger invalid counter")] + NoApduReceived = 0x6982, + + #[error("Ledger operation rejected by the user")] + UserCancel = 0x6985, + + #[error("Ledger received invalid Solana message")] + SolanaInvalidMessage = 0x6a80, + + #[error("Solana summary finalization failed on Ledger device")] + SolanaSummaryFinalizeFailed = 0x6f00, + + #[error("Solana summary update failed on Ledger device")] + SolanaSummaryUpdateFailed = 0x6f01, + + #[error("Ledger received unimplemented instruction")] + UnimplementedInstruction = 0x6d00, + + #[error("Ledger received invalid CLA")] + InvalidCla = 0x6e00, +} diff --git a/remote-wallet/src/lib.rs b/remote-wallet/src/lib.rs index 2831721d8..a9308ff95 100644 --- a/remote-wallet/src/lib.rs +++ b/remote-wallet/src/lib.rs @@ -1,3 +1,4 @@ pub mod ledger; +pub mod ledger_error; pub mod remote_keypair; pub mod remote_wallet; diff --git a/remote-wallet/src/remote_wallet.rs b/remote-wallet/src/remote_wallet.rs index b326cff1f..5105a7905 100644 --- a/remote-wallet/src/remote_wallet.rs +++ b/remote-wallet/src/remote_wallet.rs @@ -1,4 +1,7 @@ -use crate::ledger::{is_valid_ledger, LedgerWallet}; +use crate::{ + ledger::{is_valid_ledger, LedgerWallet}, + ledger_error::LedgerError, +}; use log::*; use parking_lot::{Mutex, RwLock}; use solana_sdk::{ @@ -38,6 +41,9 @@ pub enum RemoteWalletError { #[error("invalid path: {0}")] InvalidPath(String), + #[error(transparent)] + LedgerError(#[from] LedgerError), + #[error("no device found")] NoDeviceFound, @@ -64,6 +70,7 @@ impl From for SignerError { RemoteWalletError::DeviceTypeMismatch => SignerError::Connection(err.to_string()), RemoteWalletError::InvalidDevice => SignerError::Connection(err.to_string()), RemoteWalletError::InvalidInput(input) => SignerError::InvalidInput(input), + RemoteWalletError::LedgerError(e) => SignerError::Protocol(e.to_string()), RemoteWalletError::NoDeviceFound => SignerError::NoDeviceFound, RemoteWalletError::Protocol(e) => SignerError::Protocol(e.to_string()), RemoteWalletError::UserCancel => { @@ -97,35 +104,54 @@ impl RemoteWalletManager { let devices = usb.device_list(); let num_prev_devices = self.devices.read().len(); - let detected_devices = devices + let (detected_devices, errors) = devices .filter(|&device_info| { is_valid_hid_device(device_info.usage_page(), device_info.interface_number()) }) - .fold(Vec::new(), |mut v, device_info| { - if is_valid_ledger(device_info.vendor_id(), device_info.product_id()) { - match usb.open_path(&device_info.path()) { - Ok(device) => { - let mut ledger = LedgerWallet::new(device); - if let Ok(info) = ledger.read_device(&device_info) { - ledger.pretty_path = info.get_pretty_path(); - let path = device_info.path().to_str().unwrap().to_string(); - trace!("Found device: {:?}", info); - v.push(Device { - path, - info, - wallet_type: RemoteWalletType::Ledger(Arc::new(ledger)), - }) + .fold( + (Vec::new(), Vec::new()), + |(mut devices, mut errors), device_info| { + if is_valid_ledger(device_info.vendor_id(), device_info.product_id()) { + match usb.open_path(&device_info.path()) { + Ok(device) => { + let mut ledger = LedgerWallet::new(device); + let result = ledger.read_device(&device_info); + match result { + Ok(info) => { + ledger.pretty_path = info.get_pretty_path(); + let path = device_info.path().to_str().unwrap().to_string(); + trace!("Found device: {:?}", info); + devices.push(Device { + path, + info, + wallet_type: RemoteWalletType::Ledger(Arc::new(ledger)), + }) + } + Err(err) => { + error!( + "Error connecting to ledger device to read info: {}", + err + ); + errors.push(err) + } + } + } + Err(err) => { + error!("Error connecting to ledger device to read info: {}", err) } } - Err(e) => error!("Error connecting to ledger device to read info: {}", e), } - } - v - }); + (devices, errors) + }, + ); let num_curr_devices = detected_devices.len(); *self.devices.write() = detected_devices; + if num_curr_devices == 0 && !errors.is_empty() { + return Err(errors[0].clone()); + } + Ok(num_curr_devices - num_prev_devices) } diff --git a/sdk/src/signature.rs b/sdk/src/signature.rs index 9a4ad449d..c5f25ac27 100644 --- a/sdk/src/signature.rs +++ b/sdk/src/signature.rs @@ -220,7 +220,7 @@ pub enum SignerError { #[error("no device found")] NoDeviceFound, - #[error("device protocol error: {0}")] + #[error("{0}")] Protocol(String), #[error("{0}")]