coins_ledger/
common.rs

1use std::convert::{TryFrom, TryInto};
2
3use crate::errors::LedgerError;
4
5const MAX_DATA_SIZE: usize = 255;
6
7/// APDU data blob, limited to 255 bytes. For simplicity, this data does not
8/// support 3-byte APDU prefixes.
9#[derive(Debug, Clone, Eq, PartialEq)]
10pub struct APDUData(Vec<u8>);
11
12impl std::ops::Deref for APDUData {
13    type Target = Vec<u8>;
14
15    fn deref(&self) -> &Self::Target {
16        &self.0
17    }
18}
19
20impl APDUData {
21    /// Instantiate a APDUData from a slice. If the slice contains more than 255 bytes, only the
22    /// first 255 are used.
23    pub fn new(buf: &[u8]) -> Self {
24        let length = std::cmp::min(buf.len(), MAX_DATA_SIZE);
25        APDUData(buf[..length].to_vec())
26    }
27
28    /// Resize the data, as a vec.
29    pub fn resize(&mut self, new_size: usize, fill_with: u8) {
30        self.0
31            .resize(std::cmp::min(new_size, MAX_DATA_SIZE), fill_with)
32    }
33
34    /// Consume the struct and get the underlying data
35    #[allow(clippy::missing_const_for_fn)] // false positive
36    pub fn data(self) -> Vec<u8> {
37        self.0
38    }
39}
40
41impl From<&[u8]> for APDUData {
42    fn from(buf: &[u8]) -> Self {
43        Self::new(buf)
44    }
45}
46
47impl From<Vec<u8>> for APDUData {
48    fn from(mut v: Vec<u8>) -> Self {
49        v.resize(std::cmp::min(v.len(), MAX_DATA_SIZE), 0);
50        Self(v)
51    }
52}
53
54impl AsRef<[u8]> for APDUData {
55    #[inline]
56    fn as_ref(&self) -> &[u8] {
57        &self.0[..]
58    }
59}
60
61/// An APDU Command packet, used to issue instructions to the smart card.
62/// See [wikipedia](https://en.wikipedia.org/wiki/Smart_card_application_protocol_data_unit) for
63/// additional format details
64#[derive(Debug, Clone, Eq, PartialEq)]
65pub struct APDUCommand {
66    /// The application identifier.
67    pub cla: u8,
68    /// The instruction code.
69    pub ins: u8,
70    /// Instruction parameter 1
71    pub p1: u8,
72    /// Instruction parameter 2
73    pub p2: u8,
74    /// Command data
75    pub data: APDUData,
76    /// The requested response length.
77    pub response_len: Option<u8>,
78}
79
80impl std::fmt::Display for APDUCommand {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        f.debug_struct("APDUCommand")
83            .field("cla", &self.cla)
84            .field("ins", &self.ins)
85            .field("p1", &self.p1)
86            .field("p2", &self.p2)
87            .field("data", &hex::encode(&*self.data))
88            .field("response_len", &self.response_len)
89            .finish()
90    }
91}
92
93impl APDUCommand {
94    /// Return the serialized length of the APDU packet
95    pub fn serialized_length(&self) -> usize {
96        let mut length = 4;
97        if !self.data.is_empty() {
98            length += 1;
99            length += self.data.len();
100        }
101        length += self.response_len.is_some() as usize;
102        length
103    }
104
105    /// Write the APDU packet to the specified Write interface
106    pub fn write_to<W: std::io::Write>(&self, w: &mut W) -> Result<usize, std::io::Error> {
107        w.write_all(&[self.cla, self.ins, self.p1, self.p2])?;
108        if !self.data.is_empty() {
109            w.write_all(&[self.data.len() as u8])?;
110            w.write_all(self.data.as_ref())?;
111        }
112        if let Some(response_len) = self.response_len {
113            w.write_all(&[response_len])?;
114        }
115        Ok(self.serialized_length())
116    }
117
118    /// Serialize the APDU to a vector
119    pub fn serialize(&self) -> Vec<u8> {
120        let mut v = Vec::with_capacity(self.serialized_length());
121        self.write_to(&mut v).unwrap();
122        v
123    }
124}
125
126/// An APDU response is a wrapper around some response bytes. To avoid unnecessary clones, it
127/// exposes the retcode and response data as getters.
128#[derive(Debug, Clone, Eq, PartialEq)]
129pub struct APDUAnswer {
130    response: Vec<u8>,
131}
132
133impl std::ops::Deref for APDUAnswer {
134    type Target = Vec<u8>;
135
136    fn deref(&self) -> &Self::Target {
137        &self.response
138    }
139}
140
141impl std::fmt::Display for APDUAnswer {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        write!(
144            f,
145            "APDUAnswer: {{\n\tResponse: {:?} \n\tData: {:?}\n}}",
146            self.response_status(),
147            self.data()
148        )
149    }
150}
151
152impl APDUAnswer {
153    /// instantiate a
154    pub fn from_answer(response: Vec<u8>) -> Result<APDUAnswer, LedgerError> {
155        if response.len() < 2 {
156            Err(LedgerError::ResponseTooShort(response.to_vec()))
157        } else {
158            Ok(Self { response })
159        }
160    }
161
162    /// Return false if the response status is an error.
163    pub fn is_success(&self) -> bool {
164        match self.response_status() {
165            Some(opcode) => opcode.is_success(),
166            None => false,
167        }
168    }
169
170    /// Get the integer response code from the response packet.
171    ///
172    /// Panics if the buffer is too short (some device error).
173    pub fn retcode(&self) -> u16 {
174        let mut buf = [0u8; 2];
175        buf.copy_from_slice(&self.response[self.len() - 2..]);
176        u16::from_be_bytes(buf)
177    }
178
179    /// Return the Response code
180    ///
181    /// Panics on unknown retcode.
182    pub fn response_status(&self) -> Option<APDUResponseCodes> {
183        self.retcode().try_into().ok()
184    }
185
186    /// Return a reference to the response data, or None if the response errored
187    pub fn data(&self) -> Option<&[u8]> {
188        if self.is_success() {
189            Some(&self.response[..self.len() - 2])
190        } else {
191            None
192        }
193    }
194}
195
196#[repr(u16)]
197#[derive(Copy, Clone, Debug, Eq, PartialEq)]
198/// APDU Response codes. These are the last 2 bytes of the APDU packet. Please see APDU and
199/// Ledger documentation for each error type.
200pub enum APDUResponseCodes {
201    /// No Error
202    NoError = 0x9000,
203    /// ExecutionError
204    ExecutionError = 0x6400,
205    /// WrongLength
206    WrongLength = 0x6700,
207    /// UnlockDeviceError
208    UnlockDeviceError = 0x6804,
209    /// EmptyBuffer
210    EmptyBuffer = 0x6982,
211    /// OutputBufferTooSmall
212    OutputBufferTooSmall = 0x6983,
213    /// DataInvalid
214    DataInvalid = 0x6984,
215    /// ConditionsNotSatisfied
216    ConditionsNotSatisfied = 0x6985,
217    /// CommandNotAllowed
218    CommandNotAllowed = 0x6986,
219    /// InvalidData
220    InvalidData = 0x6A80,
221    /// InvalidP1P2
222    InvalidP1P2 = 0x6B00,
223    /// InsNotSupported
224    InsNotSupported = 0x6D00,
225    /// ClaNotSupported
226    ClaNotSupported = 0x6E00,
227    /// Unknown
228    Unknown = 0x6F00,
229    /// SignVerifyError
230    SignVerifyError = 0x6F01,
231}
232
233impl std::fmt::Display for APDUResponseCodes {
234    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
235        write!(f, "Code {:x} ({})", *self as u16, self.description())
236    }
237}
238
239impl APDUResponseCodes {
240    /// True if the response is a success, else false.
241    pub const fn is_success(self) -> bool {
242        matches!(self, APDUResponseCodes::NoError)
243    }
244
245    /// Return a description of the response code.
246    pub const fn description(self) -> &'static str {
247        match self {
248            APDUResponseCodes::NoError => "[APDU_CODE_NOERROR]",
249            APDUResponseCodes::ExecutionError => {
250                "[APDU_CODE_EXECUTION_ERROR] No information given (NV-Ram not changed)"
251            }
252            APDUResponseCodes::WrongLength => "[APDU_CODE_WRONG_LENGTH] Wrong length",
253            APDUResponseCodes::UnlockDeviceError => {
254                "[APDU_CODE_UNLOCK_DEVICE_ERROR] Device is locked"
255            }
256            APDUResponseCodes::EmptyBuffer => "[APDU_CODE_EMPTY_BUFFER]",
257            APDUResponseCodes::OutputBufferTooSmall => "[APDU_CODE_OUTPUT_BUFFER_TOO_SMALL]",
258            APDUResponseCodes::DataInvalid => {
259                "[APDU_CODE_DATA_INVALID] data reversibly blocked (invalidated)"
260            }
261            APDUResponseCodes::ConditionsNotSatisfied => {
262                "[APDU_CODE_CONDITIONS_NOT_SATISFIED] Conditions of use not satisfied"
263            }
264            APDUResponseCodes::CommandNotAllowed => {
265                "[APDU_CODE_COMMAND_NOT_ALLOWED] Command not allowed (no current EF)"
266            }
267            APDUResponseCodes::InvalidData => {
268                "[APDU_CODE_INVALID_DATA] The parameters in the data field are incorrect"
269            }
270            APDUResponseCodes::InvalidP1P2 => "[APDU_CODE_INVALIDP1P2] Wrong parameter(s) P1-P2",
271            APDUResponseCodes::InsNotSupported => {
272                "[APDU_CODE_INS_NOT_SUPPORTED] Instruction code not supported or invalid. Hint: Is the correct application open on the device?"
273            }
274            APDUResponseCodes::ClaNotSupported => {
275                "[APDU_CODE_CLA_NOT_SUPPORTED] Class not supported"
276            }
277            APDUResponseCodes::Unknown => "[APDU_CODE_UNKNOWN]",
278            APDUResponseCodes::SignVerifyError => "[APDU_CODE_SIGN_VERIFY_ERROR]",
279        }
280    }
281}
282
283impl TryFrom<u16> for APDUResponseCodes {
284    type Error = LedgerError;
285
286    fn try_from(code: u16) -> Result<Self, Self::Error> {
287        match code {
288            0x9000 => Ok(APDUResponseCodes::NoError),
289            0x6400 => Ok(APDUResponseCodes::ExecutionError),
290            0x6700 => Ok(APDUResponseCodes::WrongLength),
291            0x6804 => Ok(APDUResponseCodes::UnlockDeviceError),
292            0x6982 => Ok(APDUResponseCodes::EmptyBuffer),
293            0x6983 => Ok(APDUResponseCodes::OutputBufferTooSmall),
294            0x6984 => Ok(APDUResponseCodes::DataInvalid),
295            0x6985 => Ok(APDUResponseCodes::ConditionsNotSatisfied),
296            0x6986 => Ok(APDUResponseCodes::CommandNotAllowed),
297            0x6A80 => Ok(APDUResponseCodes::InvalidData),
298            0x6B00 => Ok(APDUResponseCodes::InvalidP1P2),
299            0x6D00 => Ok(APDUResponseCodes::InsNotSupported),
300            0x6E00 => Ok(APDUResponseCodes::ClaNotSupported),
301            0x6F00 => Ok(APDUResponseCodes::Unknown),
302            0x6F01 => Ok(APDUResponseCodes::SignVerifyError),
303            _ => Err(LedgerError::UnknownAPDUCode(code)),
304        }
305    }
306}
307
308#[cfg(test)]
309mod test {
310    use super::*;
311
312    #[test]
313    fn serialize() {
314        let data: &[u8] = &[0, 0, 0, 1, 0, 0, 0, 1];
315
316        let command = APDUCommand {
317            cla: 0xe0,
318            ins: 0x01,
319            p1: 0x00,
320            p2: 0x00,
321            data: data.into(),
322            response_len: None,
323        };
324
325        let serialized_command = command.serialize();
326        let expected = vec![224, 1, 0, 0, 8, 0, 0, 0, 1, 0, 0, 0, 1];
327        assert_eq!(serialized_command, expected);
328
329        let command = APDUCommand {
330            cla: 0xe0,
331            ins: 0x01,
332            p1: 0x00,
333            p2: 0x00,
334            data: data.into(),
335            response_len: Some(13),
336        };
337
338        let serialized_command = command.serialize();
339        let expected = vec![224, 1, 0, 0, 8, 0, 0, 0, 1, 0, 0, 0, 1, 13];
340        assert_eq!(serialized_command, expected)
341    }
342}
343
344/*******************************************************************************
345*   (c) 2020 ZondaX GmbH
346*
347*  Licensed under the Apache License, Version 2.0 (the "License");
348*  you may not use this file except in compliance with the License.
349*  You may obtain a copy of the License at
350*
351*      http://www.apache.org/licenses/LICENSE-2.0
352*
353*  Unless required by applicable law or agreed to in writing, software
354*  distributed under the License is distributed on an "AS IS" BASIS,
355*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
356*  See the License for the specific language governing permissions and
357*  limitations under the License.
358********************************************************************************/