Skip to main content

ledger_apdu/
lib.rs

1/*******************************************************************************
2*   (c) 2022 Zondax AG
3*
4*  Licensed under the Apache License, Version 2.0 (the "License");
5*  you may not use this file except in compliance with the License.
6*  You may obtain a copy of the License at
7*
8*      http://www.apache.org/licenses/LICENSE-2.0
9*
10*  Unless required by applicable law or agreed to in writing, software
11*  distributed under the License is distributed on an "AS IS" BASIS,
12*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*  See the License for the specific language governing permissions and
14*  limitations under the License.
15********************************************************************************/
16//! This crate contains a couple of utilities to talk via the APDU protocol to Ledger devices
17
18#![no_std]
19#![deny(missing_docs)]
20
21extern crate no_std_compat as std;
22use core::ops::Deref;
23use std::convert::{TryFrom, TryInto};
24
25use snafu::prelude::*;
26
27#[cfg(test)]
28mod tests;
29
30#[derive(Debug, Clone)]
31/// An APDU command
32pub struct APDUCommand<B> {
33    ///APDU Class
34    ///
35    /// An incorrect APDU Class will prevent you from communicating with the device
36    pub cla: u8,
37    ///APDU Instruction
38    pub ins: u8,
39    ///First parameter of instruction
40    pub p1: u8,
41    ///Second parameter of instruction
42    pub p2: u8,
43    ///Payload of the instruction, can be empty
44    pub data: B,
45}
46
47#[cfg(feature = "std")]
48impl<B> APDUCommand<B>
49where
50    B: Deref<Target = [u8]>,
51{
52    /// Serialize this [APDUCommand] to be sent to the device
53    pub fn serialize(&self) -> std::vec::Vec<u8> {
54        let mut v = std::vec![self.cla, self.ins, self.p1, self.p2, self.data.len() as u8];
55        v.extend(self.data.iter());
56        v
57    }
58}
59
60#[derive(Debug)]
61/// An APDU answer, whole last 2 bytes are interpreted as `retcode`
62pub struct APDUAnswer<B> {
63    data: B,
64    retcode: u16,
65}
66
67#[derive(Debug, Snafu, PartialEq, Eq)]
68/// Error interpreting bytes as an APDU answer
69pub enum APDUAnswerError {
70    #[snafu(display("answer too short (< 2 bytes)"))]
71    /// Passed APDU answer was less than the minimum 2 bytes required for the return code
72    TooShort,
73}
74
75impl<B> APDUAnswer<B>
76where
77    B: Deref<Target = [u8]>,
78{
79    /// Attempt to interpret the given slice as an APDU answer
80    pub fn from_answer(answer: B) -> Result<Self, APDUAnswerError> {
81        ensure!(answer.len() >= 2, TooShortSnafu);
82        let retcode = arrayref::array_ref!(answer, answer.len() - 2, 2);
83        let retcode = u16::from_be_bytes(*retcode);
84
85        Ok(APDUAnswer { data: answer, retcode })
86    }
87
88    /// Will return the answer's payload
89    #[inline(always)]
90    pub fn apdu_data(&self) -> &[u8] {
91        &self.data[.. self.data.len() - 2]
92    }
93
94    /// Will return the answer's payload
95    #[inline(always)]
96    pub fn data(&self) -> &[u8] {
97        self.apdu_data()
98    }
99
100    /// Will attempt to interpret the error code as an [APDUErrorCode],
101    /// returning the code as is otherwise
102    pub fn error_code(&self) -> Result<APDUErrorCode, u16> {
103        self.retcode
104            .try_into()
105            .map_err(|_| self.retcode)
106    }
107
108    /// Returns the raw return code
109    #[inline(always)]
110    pub fn retcode(&self) -> u16 {
111        self.retcode
112    }
113}
114
115#[derive(Copy, Clone, Debug, Snafu, PartialEq, Eq)]
116#[repr(u16)]
117/// Common known APDU error codes
118pub enum APDUErrorCode {
119    ///success
120    NoError = 0x9000,
121    ///error during apdu execution
122    ExecutionError = 0x6400,
123    ///apdu command wrong length
124    WrongLength = 0x6700,
125    ///empty apdu buffer
126    EmptyBuffer = 0x6982,
127    ///apdu buffer too small
128    OutputBufferTooSmall = 0x6983,
129    ///apdu parameters invalid
130    DataInvalid = 0x6984,
131    ///apdu preconditions not satisfied
132    ConditionsNotSatisfied = 0x6985,
133    ///apdu command not allowed
134    CommandNotAllowed = 0x6986,
135    ///apdu data field incorrect (bad key)
136    BadKeyHandle = 0x6A80,
137    ///apdu p1 or p2 incorrect
138    InvalidP1P2 = 0x6B00,
139    ///apdu instruction not supported or invalid
140    InsNotSupported = 0x6D00,
141    ///apdu class not supported or invalid
142    ClaNotSupported = 0x6E00,
143    ///unknown apdu error
144    Unknown = 0x6F00,
145    ///apdu sign verify error
146    SignVerifyError = 0x6F01,
147}
148
149#[cfg(feature = "std")]
150impl APDUErrorCode {
151    /// Quick-hand to retrieve the error code's description / display
152    pub fn description(&self) -> std::string::String {
153        std::format!("{}", self)
154    }
155}
156
157impl From<APDUErrorCode> for u16 {
158    fn from(code: APDUErrorCode) -> Self {
159        code as u16
160    }
161}
162
163impl TryFrom<u16> for APDUErrorCode {
164    type Error = ();
165
166    fn try_from(value: u16) -> Result<Self, Self::Error> {
167        let this = match value {
168            0x9000 => Self::NoError,
169            0x6400 => Self::ExecutionError,
170            0x6700 => Self::WrongLength,
171            0x6982 => Self::EmptyBuffer,
172            0x6983 => Self::OutputBufferTooSmall,
173            0x6984 => Self::DataInvalid,
174            0x6985 => Self::ConditionsNotSatisfied,
175            0x6986 => Self::CommandNotAllowed,
176            0x6A80 => Self::BadKeyHandle,
177            0x6B00 => Self::InvalidP1P2,
178            0x6D00 => Self::InsNotSupported,
179            0x6E00 => Self::ClaNotSupported,
180            0x6F00 => Self::Unknown,
181            0x6F01 => Self::SignVerifyError,
182            _ => return Err(()),
183        };
184
185        Ok(this)
186    }
187}