From ae678739e10b96c4bcdf0d00fb24a539df34473e Mon Sep 17 00:00:00 2001 From: Yves Richard Date: Fri, 27 Sep 2024 17:19:06 +0200 Subject: [PATCH] add --nfc flag to use NFC apdu media --- speculos/main.py | 3 +- speculos/mcu/nfc.py | 75 ++++++++++++++++++++++++++++++++++++++ speculos/mcu/seproxyhal.py | 22 ++++++++++- 3 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 speculos/mcu/nfc.py diff --git a/speculos/main.py b/speculos/main.py index f7ba54db..95fb5204 100644 --- a/speculos/main.py +++ b/speculos/main.py @@ -269,6 +269,7 @@ def main(prog=None) -> int: parser.add_argument('-t', '--trace', action='store_true', help='Trace syscalls') parser.add_argument('-u', '--usb', default='hid', help='Configure the USB transport protocol, ' 'either HID (default) or U2F') + parser.add_argument('--nfc', action='store_true', help='Use NFC transport instead of USB') group = parser.add_argument_group('network arguments') group.add_argument('--apdu-port', default=9999, type=int, help='ApduServer TCP port') @@ -473,7 +474,7 @@ def main(prog=None) -> int: use_bagl=use_bagl, automation=automation_path, automation_server=automation_server, - transport=args.usb) + transport='nfc' if args.nfc else args.usb) button = None if args.button_port: diff --git a/speculos/mcu/nfc.py b/speculos/mcu/nfc.py new file mode 100644 index 00000000..28c1bd14 --- /dev/null +++ b/speculos/mcu/nfc.py @@ -0,0 +1,75 @@ +""" +Forward NFC packets between the MCU and the SE +""" + +from abc import ABC, abstractmethod +from construct import Int8ub, Int16ub, Int16ul, Struct +import binascii +import enum +import logging + + +class SephNfcTag(enum.IntEnum): + NFC_APDU_EVENT = 0x1C + NFC_EVENT = 0x1E + + +class NFC: + def __init__(self, _queue_event_packet): + self._queue_event_packet = _queue_event_packet + self.packets_to_send = [] + self.MTU=140 + self.rx_sequence = 0 + self.rx_size = 0 + self.rx_data = [] + + self.logger = logging.getLogger("nfc") + + + def handle_rapdu_chunk(self, data): + """concatenate apdu chunks into full apdu""" + + # example of data + # 0000050000002b3330000409312e302e302d72633104e600000008362e312e302d646508352e312e302d6465010001009000 + + # only APDU packets are suported + if data[2] != 0x05: + return None + + sequence = int.from_bytes(data[3:5], 'big') + if self.rx_sequence != sequence: + print(f"Unexpected sequence number:{sequence}") + return None + + if sequence == 0: + self.rx_size = int.from_bytes(data[5:7], "big") + self.rx_data = data[7:] + else: + self.rx_data.append(data[5:]) + + if len(self.rx_data) == self.rx_size: + #prepare for next call + self.rx_sequence = 0 + return self.rx_data + + return None + + + def apdu(self, data): + chunks: List[bytes] = [] + data_len = len(data) + + while len(data) > 0: + size = self.MTU-5 + chunks.append(data[:size]) + data = data[size:] + + for i, chunk in enumerate(chunks): + # ledger protocol header + header = bytes([0x00, 0x00, 0x05]) # APDU + header += i.to_bytes(2, "big") + # first packet contains the size of full buffer + if i == 0: + header += data_len.to_bytes(2, "big") + + self._queue_event_packet(SephNfcTag.NFC_APDU_EVENT, header+chunk) diff --git a/speculos/mcu/seproxyhal.py b/speculos/mcu/seproxyhal.py index 8eed5ca3..37f1834e 100644 --- a/speculos/mcu/seproxyhal.py +++ b/speculos/mcu/seproxyhal.py @@ -9,6 +9,7 @@ from speculos.observer import BroadcastInterface, TextEvent from . import usb +from . import nfc from .automation import Automation from .display import DisplayNotifier, IODevice from .nbgl import NBGL @@ -30,6 +31,9 @@ class SephTag(IntEnum): USB_CONFIG = 0x4f USB_EP_PREPARE = 0x50 + NFC_RAPDU = 0x4A + NFC_POWER = 0x34 + REQUEST_STATUS = 0x52 RAPDU = 0x53 PLAY_TUNE = 0x56 @@ -261,6 +265,7 @@ def __init__(self, self.automation_server = automation_server self.events: List[TextEvent] = [] self.refreshed = False + self.transport = transport self.status_event = threading.Event() self.socket_helper = SocketHelper(self._socket, self.status_event) @@ -270,7 +275,10 @@ def __init__(self, self.socket_helper.wait_until_tick_is_processed) self.time_ticker_thread.start() - self.usb = usb.USB(self.socket_helper.queue_packet, transport=transport) + usb_transport = transport if transport in ['hid', 'u2f'] else 'hid' + self.usb = usb.USB(self.socket_helper.queue_packet, transport=usb_transport) + + self.nfc = nfc.NFC(self.socket_helper.queue_packet) self.ocr = OCR(model, use_bagl) @@ -449,6 +457,13 @@ def can_read(self, screen: DisplayNotifier): assert isinstance(screen.display.gl, NBGL) screen.display.gl.hal_draw_image_file(data) + elif tag == SephTag.NFC_RAPDU: + data = self.nfc.handle_rapdu_chunk(data) + if data: + for c in self.apdu_callbacks: + c(data) + screen.display.forward_to_apdu_client(data) + else: self.logger.error(f"unknown tag: {tag:#x}") sys.exit(0) @@ -506,7 +521,10 @@ def to_app(self, packet: bytes): tag, packet = packet[4], packet[5:] self.socket_helper.queue_packet(SephTag(tag), packet) else: - self.usb.xfer(packet) + if self.transport == 'nfc': + self.nfc.apdu(packet) + else: + self.usb.xfer(packet) def get_tick_count(self): return self.socket_helper.get_tick_count()