From 17ff54b0d4968dd2d4e508d6d752c786d6fdec65 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Thu, 13 Jan 2022 13:27:37 -0500 Subject: [PATCH] RF testing for NFC --- testing/conftest.py | 88 +++++++++++++++++++++++++++++++++++++--- testing/requirements.txt | 4 ++ testing/test_nfc.py | 21 +++++++++- 3 files changed, 106 insertions(+), 7 deletions(-) diff --git a/testing/conftest.py b/testing/conftest.py index 5cff9d4b..526b86ad 100644 --- a/testing/conftest.py +++ b/testing/conftest.py @@ -1137,23 +1137,99 @@ def only_mk3(dev): if v[4] != 'mk3': raise pytest.skip("Mk3 only") +@pytest.fixture(scope='module') +def rf_interface(only_mk4, sim_exec): + # provide a read/write connection over NFC + # - requires pyscard module and NFC-V reader like HID OMNIKEY 5022CL + class RFHandler: + def __init__(self, want_atr=None): + from smartcard.System import readers as get_readers + from smartcard.Exceptions import CardConnectionException, NoCardException + + readers = get_readers() + if not readers: + raise pytest.fail("no card readers found") + + # search for our card + for r in readers: + try: + conn = r.createConnection() + except: + print(f"Fail: {r}"); + continue + + try: + conn.connect() + atr = conn.getATR() + except (CardConnectionException, NoCardException): + print(f"Empty reader: {r}") + continue + + if want_atr and atr != want_atr: + continue + + # accept first suitable "card" + break + else: + raise pytest.fail("did not find NFC target") + + self.conn = conn + + def apdu(self, cls, ins, data=b'', p1=0, p2=0): + # send APDU + lst = [ cls, ins, p1, p2, len(data)] + list(data) + resp, sw1, sw2 = self.conn.transmit(lst) + resp = bytes(resp) + return hex((sw1 << 8) | sw2), resp + + # XXX not simple; Omnikey wants secure channel (AES) for this + def read_nfc(self): + return b'helllo' + def write_nfc(self, ccfile): + pass + + # get the CC into NFC tap mode (but no UX) + sim_exec('glob.NFC.set_rf_disable(0)') + + time.sleep(3) + + yield RFHandler() + + sim_exec('glob.NFC.set_rf_disable(1)') + @pytest.fixture() -def nfc_read(sim_exec): +def nfc_read(request, only_mk4): # READ data from NFC chip - def doit(): + # - perfer to do over NFC reader, but can work over USB too + def doit_usb(): + sim_exec = request.getfixturevalue('sim_exec') rv = sim_exec('RV.write(glob.NFC.dump_ndef() if glob.NFC else b"")', binary=True) if b'Traceback' in rv: raise pytest.fail(rv.decode('utf-8')) return rv - return doit + + try: + raise NotImplementedError + rf = request.getfixturevalue('rf_interface') + return rf.read_nfc + except: + return doit_usb @pytest.fixture() -def nfc_write(sim_exec, need_keypress): +def nfc_write(request, only_mk4): # WRITE data into NFC "chip" - def doit(ccfile): + def doit_usb(ccfile): + sim_exec = request.getfixturevalue('sim_exec') + need_keypress = request.getfixturevalue('need_keypress') rv = sim_exec('list(glob.NFC.big_write(%r))' % ccfile) if 'Traceback' in rv: raise pytest.fail(rv) need_keypress('y') # to end the animation and have it check value immediately - return doit + + try: + raise NotImplementedError + rf = request.getfixturevalue('rf_interface') + return rf.write_nfc + except: + return doit_usb @pytest.fixture() def nfc_read_text(nfc_read): diff --git a/testing/requirements.txt b/testing/requirements.txt index a3fedf1b..fbe0d4e3 100644 --- a/testing/requirements.txt +++ b/testing/requirements.txt @@ -14,3 +14,7 @@ zbar-py==1.0.4 # NFC and NDEF handling nfcpy==1.0.3 ndef==0.2 + +# optional, and only helpful if you have a desktop NFC-V capable reader +pyscard==2.0.2 + diff --git a/testing/test_nfc.py b/testing/test_nfc.py index e7d1ac24..0785bbfd 100644 --- a/testing/test_nfc.py +++ b/testing/test_nfc.py @@ -2,7 +2,7 @@ # # Mk4 NFC feature related tests. # -import pytest, glob +import pytest, glob, pdb from helpers import B2A from binascii import b2a_hex, a2b_hex from struct import pack, unpack @@ -378,4 +378,23 @@ def test_nfc_after(num_outs, fake_txn, try_sign, nfc_read, need_keypress, cap_st else: raise ValueError(got.type) +def test_rf_uid(rf_interface, cap_story, goto_home, pick_menu_item): + # read UID of NFC chip over the air + sw, ident = rf_interface.apdu(0xff, 0xca) # PAPDU_GET_UID + assert sw == '0x9000' + assert ident[-2:] == b'\x02\xe0' # ST vendor + assert len(ident) == 8 + uid = ''.join('%02x'%i for i in reversed(ident)) + + # check UI is reporting same value + goto_home() + pick_menu_item('Advanced') + pick_menu_item('Upgrade') + pick_menu_item('Show Version') + _, story = cap_story() + + assert uid in story + print(uid) + + # EOF