diff --git a/src/class.rs b/src/class.rs index 0bf7592..43537eb 100644 --- a/src/class.rs +++ b/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/src/pipe.rs b/src/pipe.rs index c8e829e..43d3566 100644 --- a/src/pipe.rs +++ b/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()); }