diff --git a/components/usbd-ccid/src/class.rs b/components/usbd-ccid/src/class.rs index 0bf7592a..43537eb7 100644 --- a/components/usbd-ccid/src/class.rs +++ b/components/usbd-ccid/src/class.rs @@ -36,9 +36,15 @@ where I: 'static + Interchange, RESPONSE = Bytes>, N: heapless::ArrayLength, { + /// 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, request_pipe: Requester, + card_issuers_data: Option<&[u8]>, ) -> Self { let read = allocator.bulk(PACKET_SIZE as _); let write = allocator.bulk(PACKET_SIZE as _); @@ -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 } diff --git a/components/usbd-ccid/src/pipe.rs b/components/usbd-ccid/src/pipe.rs index c8e829e8..43d3566d 100644 --- a/components/usbd-ccid/src/pipe.rs +++ b/components/usbd-ccid/src/pipe.rs @@ -62,6 +62,7 @@ where long_packet_missing: usize, in_chain: usize, pub(crate) started_processing: bool, + atr: Bytes, } impl Pipe @@ -73,6 +74,7 @@ where pub(crate) fn new( write: EndpointIn<'static, Bus>, request_pipe: Requester, + card_issuers_data: Option<&[u8]>, ) -> Self { assert!(MAX_MSG_LENGTH >= PACKET_SIZE); @@ -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 { + 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 @@ -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()); } diff --git a/runners/lpc55/src/initializer.rs b/runners/lpc55/src/initializer.rs index 1c158f67..2fb04dbc 100644 --- a/runners/lpc55/src/initializer.rs +++ b/runners/lpc55/src/initializer.rs @@ -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 @@ -100,6 +100,8 @@ fn get_product_string(pfr: &mut Pfr) -> &' } // 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)" } @@ -228,7 +230,7 @@ impl Initializer { } fn try_enable_fm11nc08 >( - &mut self, + &mut self, clocks: &Clocks, iocon: &mut hal::Iocon, gpio: &mut hal::Gpio, @@ -415,7 +417,7 @@ impl Initializer { } else { None }; - + let mut iso14443: Option> = None; let (contactless_requester, contactless_responder) = apdu_dispatch::interchanges::Contactless::claim() @@ -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() @@ -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), @@ -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 { if self.is_nfc_passive { @@ -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; }