netget/server/usb/smartcard/
apdu.rs1use anyhow::{bail, Result};
24
25pub mod ins {
27 pub const SELECT: u8 = 0xA4;
28 pub const READ_BINARY: u8 = 0xB0;
29 pub const READ_RECORD: u8 = 0xB2;
30 pub const UPDATE_BINARY: u8 = 0xD6;
31 pub const UPDATE_RECORD: u8 = 0xDC;
32 pub const ERASE_BINARY: u8 = 0x0E;
33 pub const VERIFY: u8 = 0x20;
34 pub const CHANGE_REFERENCE_DATA: u8 = 0x24;
35 pub const RESET_RETRY_COUNTER: u8 = 0x2C;
36 pub const GET_RESPONSE: u8 = 0xC0;
37 pub const GET_DATA: u8 = 0xCA;
38 pub const PUT_DATA: u8 = 0xDA;
39 pub const ENVELOPE: u8 = 0xC2;
40 pub const INTERNAL_AUTHENTICATE: u8 = 0x88;
41 pub const EXTERNAL_AUTHENTICATE: u8 = 0x82;
42 pub const GET_CHALLENGE: u8 = 0x84;
43 pub const MANAGE_SECURITY_ENVIRONMENT: u8 = 0x22;
44 pub const PERFORM_SECURITY_OPERATION: u8 = 0x2A;
45 pub const GENERATE_ASYMMETRIC_KEY_PAIR: u8 = 0x46;
46}
47
48pub const SELECT_P1_BY_AID: u8 = 0x04;
50
51pub const SW_NO_PRECISE_DIAGNOSIS: (u8, u8) = (0x6F, 0x00);
57
58pub const SW_WRONG_LENGTH: (u8, u8) = (0x67, 0x00);
60
61#[derive(Debug, Clone)]
63pub struct ApduCommand {
64 pub cla: u8,
65 pub ins: u8,
66 pub p1: u8,
67 pub p2: u8,
68 pub data: Vec<u8>,
70 pub le: Option<u32>,
73}
74
75impl ApduCommand {
76 pub fn parse(bytes: &[u8]) -> Result<Self> {
78 if bytes.len() < 4 {
79 bail!("APDU too short: {} byte(s), need at least 4", bytes.len());
80 }
81
82 let cla = bytes[0];
83 let ins = bytes[1];
84 let p1 = bytes[2];
85 let p2 = bytes[3];
86 let body = &bytes[4..];
87
88 let (data, le) = match body.len() {
89 0 => (Vec::new(), None),
91 1 => (Vec::new(), Some(expand_le_short(body[0]))),
93 _ if body[0] != 0 => {
94 let lc = body[0] as usize;
96 let data_end = 1 + lc;
97 if body.len() < data_end {
98 bail!(
99 "APDU truncated: Lc={} but only {} byte(s) follow",
100 lc,
101 body.len() - 1
102 );
103 }
104 let data = body[1..data_end].to_vec();
105 match body.len() - data_end {
106 0 => (data, None),
107 1 => (data, Some(expand_le_short(body[data_end]))),
108 extra => bail!("APDU has {} trailing byte(s) after Lc/Data/Le", extra),
109 }
110 }
111 _ => {
113 if body.len() < 3 {
114 bail!(
115 "APDU truncated: extended-length marker needs 3 byte(s), got {}",
116 body.len()
117 );
118 }
119 let extended = u16::from_be_bytes([body[1], body[2]]);
120 if body.len() == 3 {
121 return Ok(Self {
123 cla,
124 ins,
125 p1,
126 p2,
127 data: Vec::new(),
128 le: Some(expand_le_extended(extended)),
129 });
130 }
131 let lc = extended as usize;
132 if lc == 0 {
133 bail!("APDU has an extended-length marker with Lc=0 and trailing bytes");
134 }
135 let data_end = 3 + lc;
136 if body.len() < data_end {
137 bail!(
138 "APDU truncated: extended Lc={} but only {} byte(s) follow",
139 lc,
140 body.len() - 3
141 );
142 }
143 let data = body[3..data_end].to_vec();
144 match body.len() - data_end {
145 0 => (data, None),
146 2 => (
147 data,
148 Some(expand_le_extended(u16::from_be_bytes([
149 body[data_end],
150 body[data_end + 1],
151 ]))),
152 ),
153 extra => bail!(
154 "APDU has {} trailing byte(s) after extended Lc/Data/Le",
155 extra
156 ),
157 }
158 }
159 };
160
161 Ok(Self {
162 cla,
163 ins,
164 p1,
165 p2,
166 data,
167 le,
168 })
169 }
170
171 pub fn ins_name(&self) -> &'static str {
175 if self.is_select_by_aid() {
176 return "SELECT_BY_AID";
177 }
178 match self.ins {
179 ins::SELECT => "SELECT",
180 ins::READ_BINARY => "READ_BINARY",
181 ins::READ_RECORD => "READ_RECORD",
182 ins::UPDATE_BINARY => "UPDATE_BINARY",
183 ins::UPDATE_RECORD => "UPDATE_RECORD",
184 ins::ERASE_BINARY => "ERASE_BINARY",
185 ins::VERIFY => "VERIFY",
186 ins::CHANGE_REFERENCE_DATA => "CHANGE_REFERENCE_DATA",
187 ins::RESET_RETRY_COUNTER => "RESET_RETRY_COUNTER",
188 ins::GET_RESPONSE => "GET_RESPONSE",
189 ins::GET_DATA => "GET_DATA",
190 ins::PUT_DATA => "PUT_DATA",
191 ins::ENVELOPE => "ENVELOPE",
192 ins::INTERNAL_AUTHENTICATE => "INTERNAL_AUTHENTICATE",
193 ins::EXTERNAL_AUTHENTICATE => "EXTERNAL_AUTHENTICATE",
194 ins::GET_CHALLENGE => "GET_CHALLENGE",
195 ins::MANAGE_SECURITY_ENVIRONMENT => "MANAGE_SECURITY_ENVIRONMENT",
196 ins::PERFORM_SECURITY_OPERATION => "PERFORM_SECURITY_OPERATION",
197 ins::GENERATE_ASYMMETRIC_KEY_PAIR => "GENERATE_ASYMMETRIC_KEY_PAIR",
198 _ => "UNKNOWN",
199 }
200 }
201
202 pub fn is_select_by_aid(&self) -> bool {
205 self.ins == ins::SELECT && self.p1 == SELECT_P1_BY_AID && !self.data.is_empty()
206 }
207}
208
209fn expand_le_short(value: u8) -> u32 {
211 if value == 0 {
212 256
213 } else {
214 value as u32
215 }
216}
217
218fn expand_le_extended(value: u16) -> u32 {
220 if value == 0 {
221 65536
222 } else {
223 value as u32
224 }
225}
226
227#[derive(Debug, Clone)]
229pub struct ApduResponse {
230 pub data: Vec<u8>,
231 pub sw1: u8,
232 pub sw2: u8,
233}
234
235impl ApduResponse {
236 pub fn new(data: Vec<u8>, sw1: u8, sw2: u8) -> Self {
237 Self { data, sw1, sw2 }
238 }
239
240 pub fn card_error() -> Self {
242 Self::new(
243 Vec::new(),
244 SW_NO_PRECISE_DIAGNOSIS.0,
245 SW_NO_PRECISE_DIAGNOSIS.1,
246 )
247 }
248
249 pub fn wrong_length() -> Self {
251 Self::new(Vec::new(), SW_WRONG_LENGTH.0, SW_WRONG_LENGTH.1)
252 }
253
254 pub fn status_word(&self) -> String {
255 format!("{:02X}{:02X}", self.sw1, self.sw2)
256 }
257
258 pub fn into_bytes(self) -> Vec<u8> {
259 let mut bytes = self.data;
260 bytes.push(self.sw1);
261 bytes.push(self.sw2);
262 bytes
263 }
264}
265
266pub fn printable_text(data: &[u8]) -> Option<String> {
269 if data.is_empty() {
270 return None;
271 }
272 if data
273 .iter()
274 .all(|&b| b.is_ascii_graphic() || b == b' ' || b == b'\t')
275 {
276 Some(String::from_utf8_lossy(data).to_string())
277 } else {
278 None
279 }
280}