Add Ledger error codes (#10056)

automerge
This commit is contained in:
Tyera Eulberg 2020-05-14 22:52:11 -06:00 committed by GitHub
parent adcd2f14a5
commit 63813fe69f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 169 additions and 50 deletions

2
Cargo.lock generated
View File

@ -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",

View File

@ -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" }

View File

@ -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");
}
}
}

View File

@ -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,
}

View File

@ -1,3 +1,4 @@
pub mod ledger;
pub mod ledger_error;
pub mod remote_keypair;
pub mod remote_wallet;

View File

@ -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<RemoteWalletError> 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)
}

View File

@ -220,7 +220,7 @@ pub enum SignerError {
#[error("no device found")]
NoDeviceFound,
#[error("device protocol error: {0}")]
#[error("{0}")]
Protocol(String),
#[error("{0}")]