-
Notifications
You must be signed in to change notification settings - Fork 7
/
dispatch.rs
470 lines (432 loc) · 17.2 KB
/
dispatch.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
//! This "APDU dispatch" consumes APDUs from either a contactless or contact interface, or both.
//! Each APDU will be sent to an "App". The dispatch will manage selecting and deselecting apps,
//! and will gauruntee only one app will be selected at a time. Only the selected app will
//! receive APDU's. Apps are selected based on their AID.
//!
//! Additionally, the APDU dispatch could repeatedly call "poll" on the selected App. If this was in place, the App
//! could choose to reply at time of APDU, or can defer and reply later (during one of the poll calls).
//!
//! Apps need to implement the App trait to be managed.
//!
use crate::command::SIZE as CommandSize;
use crate::response::SIZE as ResponseSize;
use crate::App;
use crate::{
interchanges::{self, Responder},
response, Command,
};
use core::convert::TryInto;
use iso7816::{command::FromSliceError, Aid, Instruction, Result, Status};
/// Maximum length of a data field of a response that can fit in an interchange message after
/// concatenation of SW1SW2
const MAX_INTERCHANGE_DATA: usize = if interchanges::SIZE < ResponseSize {
interchanges::SIZE
} else {
ResponseSize
} - 2;
pub use iso7816::Interface;
pub enum RequestType {
Select(Aid),
/// Get Response including the Le field of the command
GetResponse,
NewCommand,
/// Incorrect command, which means an error should be returned
BadCommand(Status),
None,
}
#[derive(PartialEq)]
enum RawApduBuffer {
None,
Request(Command),
Response(response::Data),
}
struct ApduBuffer {
pub raw: RawApduBuffer,
}
impl ApduBuffer {
fn request<const S: usize>(&mut self, command: &iso7816::Command<S>) {
match &mut self.raw {
RawApduBuffer::Request(buffered) => {
buffered.extend_from_command(command).ok();
}
_ => {
if self.raw != RawApduBuffer::None {
info!("Was buffering the last response, but aborting that now for this new request.");
}
let mut new_cmd = iso7816::Command::try_from(&[0, 0, 0, 0]).unwrap();
new_cmd.extend_from_command(command).ok();
self.raw = RawApduBuffer::Request(new_cmd);
}
}
}
fn response(&mut self, response: &response::Data) {
self.raw = RawApduBuffer::Response(response.clone());
}
}
pub struct ApduDispatch<'pipe> {
// or currently_selected_aid, or...
current_aid: Option<Aid>,
contact: Responder<'pipe>,
contactless: Responder<'pipe>,
current_interface: Interface,
buffer: ApduBuffer,
response_len_expected: usize,
was_request_chained: bool,
}
impl<'pipe> ApduDispatch<'pipe> {
fn apdu_type<const S: usize>(apdu: &iso7816::Command<S>) -> RequestType {
info!("instruction: {:?} {}", apdu.instruction(), apdu.p1);
if apdu.instruction() == Instruction::Select && (apdu.p1 & 0x04) != 0 {
Aid::try_new(apdu.data()).map_or_else(
|_err| {
warn!("Failed to parse AID: {:?}", _err);
RequestType::BadCommand(Status::IncorrectDataParameter)
},
RequestType::Select,
)
} else if apdu.instruction() == Instruction::GetResponse {
RequestType::GetResponse
} else {
RequestType::NewCommand
}
}
pub fn new(contact: Responder<'pipe>, contactless: Responder<'pipe>) -> Self {
ApduDispatch {
current_aid: None,
contact,
contactless,
current_interface: Interface::Contact,
was_request_chained: false,
response_len_expected: 0,
buffer: ApduBuffer {
raw: RawApduBuffer::None,
},
}
}
// It would be nice to store `current_app` instead of constantly looking up by AID,
// but that won't work due to ownership rules
fn find_app<'a, 'b>(
aid: Option<&Aid>,
apps: &'a mut [&'b mut dyn App<CommandSize, ResponseSize>],
) -> Option<&'a mut &'b mut dyn App<CommandSize, ResponseSize>> {
// match aid {
// Some(aid) => apps.iter_mut().find(|app| aid.starts_with(app.rid())),
// None => None,
// }
aid.and_then(move |aid| {
debug!("matching {:?}", aid);
apps.iter_mut().find(|app| {
// aid.starts_with(app.aid().truncated())
debug!("...against {:?}", app.aid());
app.aid().matches(aid)
})
})
}
fn busy(&self) -> bool {
// the correctness of this relies on the properties of interchange - requester can only
// send request in the idle state.
use interchange::State::*;
let contact_busy = !matches!(self.contact.state(), Idle | Requested);
let contactless_busy = !matches!(self.contactless.state(), Idle | Requested);
contactless_busy || contact_busy
}
#[inline(never)]
fn buffer_chained_apdu_if_needed<const S: usize>(
&mut self,
command: iso7816::Command<S>,
interface: Interface,
) -> RequestType {
self.current_interface = interface;
// iso 7816-4 5.1.1
// check Apdu level chaining and buffer if necessary.
if !command.class().chain().not_the_last() {
let is_chaining = matches!(self.buffer.raw, RawApduBuffer::Request(_));
if is_chaining {
self.buffer.request(&command);
// Response now needs to be chained.
self.was_request_chained = true;
info!("combined chained commands.");
RequestType::NewCommand
} else {
if self.buffer.raw == RawApduBuffer::None {
self.was_request_chained = false;
}
let apdu_type = Self::apdu_type(&command);
match Self::apdu_type(&command) {
// Keep buffer the same in case of GetResponse
RequestType::GetResponse => (),
// Overwrite for everything else.
_ => self.buffer.request(&command),
}
apdu_type
}
} else {
match interface {
// acknowledge
Interface::Contact => {
self.contact
.respond(Status::Success.try_into().unwrap())
.expect("Could not respond");
}
Interface::Contactless => {
self.contactless
.respond(Status::Success.try_into().unwrap())
.expect("Could not respond");
}
}
if !command.data().is_empty() {
info!("chaining {} bytes", command.data().len());
self.buffer.request(&command);
}
// Nothing for the application to consume yet.
RequestType::None
}
}
fn parse_apdu<const S: usize>(message: &interchanges::Data) -> Result<iso7816::Command<S>> {
debug!(">> {}", hex_str!(message.as_slice(), sep:""));
match iso7816::Command::try_from(message) {
Ok(command) => Ok(command),
Err(_error) => {
info!("apdu bad");
match _error {
FromSliceError::TooShort => {
info!("TooShort");
}
FromSliceError::TooLong => {
info!("TooLong");
}
FromSliceError::InvalidClass => {
info!("InvalidClass");
}
FromSliceError::InvalidFirstBodyByteForExtended => {
info!("InvalidFirstBodyByteForExtended");
}
FromSliceError::InvalidSliceLength => {
info!("InvalidSliceLength");
}
}
Err(Status::UnspecifiedCheckingError)
}
}
}
#[inline(never)]
fn check_for_request(&mut self) -> RequestType {
if !self.busy() {
// Check to see if we have gotten a message, giving priority to contactless.
let (message, interface) = if let Some(message) = self.contactless.take_request() {
(message, Interface::Contactless)
} else if let Some(message) = self.contact.take_request() {
(message, Interface::Contact)
} else {
return RequestType::None;
};
// Parse the message as an APDU.
match Self::parse_apdu::<{ interchanges::SIZE }>(&message) {
Ok(command) => {
self.response_len_expected = command.expected();
// The Apdu may be standalone or part of a chain.
self.buffer_chained_apdu_if_needed(command, interface)
}
Err(response) => {
// If not a valid APDU, return error and don't pass to app.
info!("Invalid apdu");
match interface {
Interface::Contactless => self
.contactless
.respond(response.into())
.expect("cant respond"),
Interface::Contact => {
self.contact.respond(response.into()).expect("cant respond")
}
}
RequestType::None
}
}
} else {
RequestType::None
}
}
#[inline(never)]
fn reply_error(&mut self, status: Status) {
self.respond(status.into());
self.buffer.raw = RawApduBuffer::None;
}
#[inline(never)]
fn handle_reply(&mut self) {
// Consider if we need to reply via chaining method.
// If the reader is using chaining, we will simply
// reply 61XX, and put the response in a buffer.
// It is up to the reader to then send GetResponse
// requests, to which we will return up to `Le` bytes at a time.
let (new_state, response) = match &mut self.buffer.raw {
RawApduBuffer::Request(_) | RawApduBuffer::None => {
info!("Unexpected GetResponse request.");
(RawApduBuffer::None, Status::UnspecifiedCheckingError.into())
}
RawApduBuffer::Response(res) => {
let max_response_len = self.response_len_expected.min(MAX_INTERCHANGE_DATA);
if self.was_request_chained || res.len() > max_response_len {
// Do not send more than the expected bytes
let boundary = max_response_len.min(res.len());
let to_send = &res[..boundary];
let remaining = &res[boundary..];
let mut message = interchanges::Data::from_slice(to_send).unwrap();
let return_code = if remaining.len() > 255 {
// XX = 00 indicates more than 255 bytes of data
0x6100u16
} else if !remaining.is_empty() {
0x6100 + (remaining.len() as u16)
} else {
// Last chunk has success code
0x9000
};
message
.extend_from_slice(&return_code.to_be_bytes())
.expect("Failed add to status bytes");
if return_code == 0x9000 {
(RawApduBuffer::None, message)
} else {
info!("Still {} bytes in response buffer", remaining.len());
(
RawApduBuffer::Response(response::Data::from_slice(remaining).unwrap()),
message,
)
}
} else {
// Add success code
res.extend_from_slice(&[0x90, 00])
.expect("Failed to add the status bytes");
(
RawApduBuffer::None,
interchanges::Data::from_slice(res.as_slice()).unwrap(),
)
}
}
};
self.buffer.raw = new_state;
self.respond(response);
}
#[inline(never)]
fn handle_app_response(&mut self, response: &Result<()>, data: &response::Data) {
// put message into the response buffer
match response {
Ok(()) => {
info!("buffered the response of {} bytes.", data.len());
self.buffer.response(data);
self.handle_reply();
}
Err(status) => {
// Just reply the error immediately.
info!("buffered app error");
self.reply_error(*status);
}
}
}
#[inline(never)]
fn handle_app_select(
&mut self,
apps: &mut [&mut dyn App<CommandSize, ResponseSize>],
aid: Aid,
) {
// three cases:
// - currently selected app has different AID -> deselect it, to give it
// the chance to clear sensitive state
// - currently selected app has given AID (typical behaviour will be NOP,
// but pass along anyway) -> do not deselect it first
// - no currently selected app
//
// For PIV, "SELECT" is NOP if it was already selected, but this is
// not necessarily the case for other apps
// if there is a selected app with a different AID, deselect it
if let Some(current_aid) = self.current_aid {
if current_aid != aid {
let app = Self::find_app(self.current_aid.as_ref(), apps).unwrap();
// for now all apps will be happy with this.
app.deselect();
self.current_aid = None;
}
}
// select specified app in any case
if let Some(app) = Self::find_app(Some(&aid), apps) {
info!("Selected app");
let mut response = response::Data::new();
let result = match &self.buffer.raw {
RawApduBuffer::Request(apdu) => app.select(apdu, &mut response),
_ => panic!("Unexpected buffer state."),
};
if result.is_ok() {
self.current_aid = Some(aid);
}
self.handle_app_response(&result, &response);
} else {
info!("could not find app by aid: {}", hex_str!(&aid.as_bytes()));
self.reply_error(Status::NotFound);
};
}
#[inline(never)]
fn handle_app_command(&mut self, apps: &mut [&mut dyn App<CommandSize, ResponseSize>]) {
// if there is a selected app, send it the command
let mut response = response::Data::new();
if let Some(app) = Self::find_app(self.current_aid.as_ref(), apps) {
let result = match &self.buffer.raw {
RawApduBuffer::Request(apdu) => {
// TODO this isn't very clear
app.call(self.current_interface, apdu, &mut response)
}
_ => panic!("Unexpected buffer state."),
};
self.handle_app_response(&result, &response);
} else {
// TODO: correct error?
self.reply_error(Status::NotFound);
};
}
pub fn poll(
&mut self,
apps: &mut [&mut dyn App<CommandSize, ResponseSize>],
) -> Option<Interface> {
// Only take on one transaction at a time.
let request_type = self.check_for_request();
// if there is a new request:
// - if it's a select, handle appropriately
// - else pass it on to currently selected app
// if there is no new request, poll currently selected app
match request_type {
// SELECT case
RequestType::Select(aid) => {
info!("Select");
self.handle_app_select(apps, aid);
}
RequestType::GetResponse => {
info!("GetResponse");
self.handle_reply();
}
// command that is not a special command -- goes to app.
RequestType::NewCommand => {
info!("Command");
self.handle_app_command(apps);
}
RequestType::BadCommand(status) => {
info!("Bad command");
self.reply_error(status);
}
RequestType::None => {}
}
// slight priority to contactless.
if self.contactless.state() == interchange::State::Responded {
Some(Interface::Contactless)
} else if self.contact.state() == interchange::State::Responded {
Some(Interface::Contact)
} else {
None
}
}
#[inline(never)]
fn respond(&mut self, message: interchanges::Data) {
debug!("<< {}", hex_str!(message.as_slice(), sep:""));
match self.current_interface {
Interface::Contactless => self.contactless.respond(message).expect("cant respond"),
Interface::Contact => self.contact.respond(message).expect("cant respond"),
}
}
}