Skip to content

Commit

Permalink
Configure CCID ATR
Browse files Browse the repository at this point in the history
  • Loading branch information
nickray committed Jun 10, 2021
1 parent f4b35b2 commit 742620c
Show file tree
Hide file tree
Showing 3 changed files with 63 additions and 73 deletions.
8 changes: 7 additions & 1 deletion components/usbd-ccid/src/class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,15 @@ where
I: 'static + Interchange<REQUEST = Bytes<N>, RESPONSE = Bytes<N>>,
N: heapless::ArrayLength<u8>,
{
/// Class constructor.
///
/// The optional card issuer's data may be of length at most 13 bytes,
/// and allows personalizing the Answer-to-Reset, for instance by
/// ASCII-encoding vendor or model information.
pub fn new(
allocator: &'static UsbBusAllocator<Bus>,
request_pipe: Requester<I>,
card_issuers_data: Option<&[u8]>,
) -> Self {
let read = allocator.bulk(PACKET_SIZE as _);
let write = allocator.bulk(PACKET_SIZE as _);
Expand All @@ -48,7 +54,7 @@ where
// PROBLEM: We don't have enough endpoints on the peripheral :/
// (USBHS should have one more)
// let interrupt = allocator.interrupt(8 as _, 32);
let pipe = Pipe::new(write, request_pipe);
let pipe = Pipe::new(write, request_pipe, card_issuers_data);
let interface_number = allocator.interface();
let string_index = allocator.string();
Self { interface_number, string_index, read, /* interrupt, */ pipe }
Expand Down
109 changes: 44 additions & 65 deletions components/usbd-ccid/src/pipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ where
long_packet_missing: usize,
in_chain: usize,
pub(crate) started_processing: bool,
atr: Bytes<heapless::consts::U32>,
}

impl<Bus, I, N> Pipe<Bus, I, N>
Expand All @@ -73,6 +74,7 @@ where
pub(crate) fn new(
write: EndpointIn<'static, Bus>,
request_pipe: Requester<I>,
card_issuers_data: Option<&[u8]>,
) -> Self {

assert!(MAX_MSG_LENGTH >= PACKET_SIZE);
Expand All @@ -91,9 +93,45 @@ where
long_packet_missing: 0,
in_chain: 0,
started_processing: false,
// later on, we only signal T=1 support
// if for some reason not signaling T=0 support leads to issues,
// we can enable it here.
atr: Self::construct_atr(card_issuers_data, false),
}
}

fn construct_atr(card_issuers_data: Option<&[u8]>, signal_t_equals_0: bool) -> Bytes<heapless::consts::U32> {
assert!(card_issuers_data.map_or(true, |data| data.len() <= 13));
let k = card_issuers_data.map_or(0u8, |data| 2 + data.len() as u8);
let mut atr = Bytes::new();
// TS: direct convention
atr.push(0x3B).ok();
// T0: encode length of historical bytes
atr.push(0x80 | k).ok();
if signal_t_equals_0 {
// T=0, more to follow
atr.push(0x80).ok();
}
// T=1
atr.push(0x01).ok();

if let Some(data) = card_issuers_data {
// no status indicator
atr.push(0x80).ok();
// tag 5: card issuer's data
atr.push(0x50 | data.len() as u8).ok();
atr.extend_from_slice(data).ok();
}
// xor of all bytes except TS
let mut checksum = 0;
for byte in atr.iter().skip(1) {
checksum ^= *byte;
}
atr.push(checksum).ok();

atr
}

pub fn busy(&self) -> bool {
// need more states, but if we're waiting
// to send, we can't accept new packets
Expand Down Expand Up @@ -410,80 +448,21 @@ where
}

fn send_atr(&mut self) {
let atr = self.atr.clone();
let packet = DataBlock::new(
self.seq,
Chain::BeginsAndEnds,
&atr,

// PivApp just uses:
// 3B 80 80 01 01
//
// 3B 88 80 01 80 57 53 6F 6C 6F 20 42 83
// T=0, T=1, card issuer's data "Solo B"
// https://smartcard-atr.apdu.fr/parse?ATR=3B+88+80+01+80+57+53+6F+6C+6F+20+42+83
//
// T=0, T=1, command chaining/extended Lc+Le/no logical channels, card issuer's data "Solo B"
// 3B 8C 80 01 80 73 C0 21 C0 56 53 6F 6C 6F 20 42 D4
// https://smartcard-atr.apdu.fr/parse?ATR=3B+8C+80+01+80+73+C0+21+C0+56+53+6F+6C+6F+20+42+D4
&[0x3B, 0x8C, 0x80, 0x01, 0x80, 0x73, 0xC0, 0x21, 0xC0, 0x56, 0x53, 0x6F, 0x6C, 0x6F, 0x20, 0x42, 0xD4]
// T=0, T=1, command chaining/extended Lc+Le/no logical channels, card issuer's data "Solo 2"
// 3B 8C 80 01 80 73 C0 21 C0 56 53 6F 6C 6F 20 32 A4
// https://smartcard-atr.apdu.fr/parse?ATR=3B+8C+80+01+80+73+C0+21+C0+56+53+6F+6C+6F+20+32+A4
// &[0x3B, 0x8C, 0x80, 0x01, 0x80, 0x73, 0xC0, 0x21, 0xC0, 0x56, 0x53, 0x6F, 0x6C, 0x6F, 0x20, 0x32, 0xA4]
//
// Not sure if we also need some TA/TB/TC data as in
// https://smartcard-atr.apdu.fr/parse?ATR=3B+F8+13+00+00+81+31+FE+15+59+75+62+69+6B+65+79+34+D4
// At least TB(1) is deprecated, so it makes no sense
// Also, there TD(1) = 0x81 and TD(2) = 0x31 both refer to protocol T=1 which seems wrong

// don't remember where i got this from
// &[0x3b, 0x8c,0x80,0x01],
// "corrected"?
// &[
// // TS
// 0x3b,
// // D1 follows, no historical bytes
// 0x80,
// // nothing more, T = 0
// 0x01,
// ],
// "simplified"?
// &[
// // TS
// 0x3b,
// // D1 follows, no historical bytes
// 0x00,
// ],
// Yubikey FIDO+CCID
// 3b:f8:13:00:00:81:31:fe:15:59:75:62:69:6b:65:79:34:d4
// &[
// // TS
// 0x3b,
// // TO = TA1, TB1, TB2, TB3 follow, 8 historical bytes
// 0xf8,

// // TA1 = default clock (5MHz), default clock rate conversion (372)o
// // But sets Di to 3 instead of default of 1
// 0x13,
// // TB1 deprecated, should not transmit
// 0x00,
// // TC1 = "extra guard time", default of 0
// 0x00,

// // TD1 = (Y2, T) -> follows D2, T = 1
// 0x81,
// // TD2 = (Y2, T)
// 0x31,
// // TA2
// 0xfe,
// // TB2
// 0x15,
// // T1 = first historical byte
// 0x59,

// // "SoloBee"
// 0x53, 0x6F, 0x6C, 0x6F, 0x42, 0x65 ,0x65,

// // Checksum
// 0x94,
// ],
// Yubikey NEO OTP+U2F+CCID
// 3b:fc:13:00:00:81:31:fe:15:59:75:62:69:6b:65:79:4e:45:4f:72:33:e1
);
self.send_packet_assuming_possible(packet.into());
}
Expand Down
19 changes: 12 additions & 7 deletions runners/lpc55/src/initializer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ pub struct Config {
/// Enable NFC operation.
pub nfc_enabled: bool,
/// Panic if prince has not been provisioned in CFPA.
pub require_prince: bool,
pub require_prince: bool,
/// If buttons are all activated for 5s, boot rom will boot. Otherwise ignore.
pub boot_to_bootrom: bool,
/// For Usb initialization
Expand Down Expand Up @@ -100,6 +100,8 @@ fn get_product_string(pfr: &mut Pfr<hal::typestates::init_state::Enabled>) -> &'
}

// Use a default string
// NB: If this were to be re-used as card issuer's data in CCID ATR,
// it would need to be limited or truncated to 13 bytes.
"Solo 2 (custom)"
}

Expand Down Expand Up @@ -228,7 +230,7 @@ impl Initializer {
}

fn try_enable_fm11nc08 <T: Ctimer<hal::Enabled>>(
&mut self,
&mut self,
clocks: &Clocks,
iocon: &mut hal::Iocon<hal::Enabled>,
gpio: &mut hal::Gpio<hal::Enabled>,
Expand Down Expand Up @@ -415,7 +417,7 @@ impl Initializer {
} else {
None
};

let mut iso14443: Option<nfc_device::Iso14443<board::nfc::NfcChip>> = None;

let (contactless_requester, contactless_responder) = apdu_dispatch::interchanges::Contactless::claim()
Expand Down Expand Up @@ -497,7 +499,10 @@ impl Initializer {
let usb_bus = unsafe { USB_BUS.as_ref().unwrap() };

// our USB classes (must be allocated in order that they're passed in `.poll(...)` later!)
let ccid = usbd_ccid::Ccid::new(usb_bus, contact_requester);
//
// NB: Card issuer's data can be at most 13 bytes (otherwise the constructor panics).
// So for instance "Hacker Solo 2" would work, but "Solo 2 (custom)" would not.
let ccid = usbd_ccid::Ccid::new(usb_bus, contact_requester, Some(b"Solo 2"));
let current_time = basic_stage.perf_timer.elapsed().0/1000;
let ctaphid = usbd_ctaphid::CtapHid::new(usb_bus, ctaphid_requester, current_time)
.implements_ctap1()
Expand Down Expand Up @@ -534,7 +539,7 @@ impl Initializer {
// Cancel any possible outstanding use in delay timing
basic_stage.delay_timer.cancel().ok();

stages::Usb {
stages::Usb {
usb_classes,
contact_responder: Some(contact_responder),
ctaphid_responder: Some(ctaphid_responder),
Expand Down Expand Up @@ -825,7 +830,7 @@ impl Initializer {
}

/// Consumes the initializer -- must be done last.
pub fn get_dynamic_clock_control(self, clock_stage: &mut stages::Clock, basic_stage: &mut stages::Basic)
pub fn get_dynamic_clock_control(self, clock_stage: &mut stages::Clock, basic_stage: &mut stages::Basic)
-> Option<clock_controller::DynamicClockController> {
if self.is_nfc_passive {

Expand All @@ -849,7 +854,7 @@ impl Initializer {
}

/// See if LPC55 will be in NFC passive operation. Requires first initialization stage have been done.
pub fn is_in_passive_operation(&self, _clock_stage: &stages::Clock)
pub fn is_in_passive_operation(&self, _clock_stage: &stages::Clock)
-> bool {
return self.is_nfc_passive;
}
Expand Down

0 comments on commit 742620c

Please sign in to comment.