From 53fb0059a7cff3c5a35f655d0bbf82689f3f1da7 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 22 Mar 2019 00:20:20 -0400 Subject: [PATCH 1/7] break into separate files --- tools/testing/ctap_test.py | 67 ++ tools/testing/tests/__init__.py | 11 + .../{ctap_test.py => testing/tests/fido2.py} | 725 +----------------- tools/testing/tests/hid.py | 245 ++++++ tools/testing/tests/solo.py | 70 ++ tools/testing/tests/tester.py | 181 +++++ tools/testing/tests/u2f.py | 103 +++ tools/testing/tests/util.py | 12 + 8 files changed, 700 insertions(+), 714 deletions(-) create mode 100644 tools/testing/ctap_test.py create mode 100644 tools/testing/tests/__init__.py rename tools/{ctap_test.py => testing/tests/fido2.py} (64%) mode change 100755 => 100644 create mode 100644 tools/testing/tests/hid.py create mode 100644 tools/testing/tests/solo.py create mode 100644 tools/testing/tests/tester.py create mode 100644 tools/testing/tests/u2f.py create mode 100644 tools/testing/tests/util.py diff --git a/tools/testing/ctap_test.py b/tools/testing/ctap_test.py new file mode 100644 index 0000000..37c1e06 --- /dev/null +++ b/tools/testing/ctap_test.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +# +# Copyright 2019 SoloKeys Developers +# +# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be +# copied, modified, or distributed except according to those terms. +# + +# Script for testing correctness of CTAP2/CTAP1 security token + +import sys + +from solo.fido2 import force_udp_backend +from tests import Tester, FIDO2Tests, U2FTests, HIDTests, SoloTests + + +if __name__ == "__main__": + if len(sys.argv) < 2: + print("Usage: %s [sim] <[u2f]|[fido2]|[rk]|[hid]|[ping]>") + sys.exit(0) + + t = Tester() + t.set_user_count(3) + + if "sim" in sys.argv: + print("Using UDP backend.") + force_udp_backend() + t.set_sim(True) + t.set_user_count(10) + + t.find_device() + + if "solo" in sys.argv: + SoloTests(t).run() + + if "u2f" in sys.argv: + U2FTests(t).run() + + if "fido2" in sys.argv: + # t.test_fido2() + FIDO2Tests(t).run() + + if "fido2-ext" in sys.argv: + pass + + if "rk" in sys.argv: + pass + + if "ping" in sys.argv: + pass + + # hid tests are a bit invasive and should be done last + if "hid" in sys.argv: + HIDTests(t).run() + + if "bootloader" in sys.argv: + if t.is_sim: + raise RuntimeError("Cannot test bootloader in simulation yet.") + # print("Put device in bootloader mode and then hit enter") + # input() + # t.test_bootloader() + + # t.test_responses() + # t.test_fido2_brute_force() diff --git a/tools/testing/tests/__init__.py b/tools/testing/tests/__init__.py new file mode 100644 index 0000000..23cf5a8 --- /dev/null +++ b/tools/testing/tests/__init__.py @@ -0,0 +1,11 @@ +from . import fido2 +from . import hid +from . import solo +from . import u2f +from . import tester + +FIDO2Tests = fido2.FIDO2Tests +HIDTests = hid.HIDTests +U2FTests = u2f.U2FTests +SoloTests = solo.SoloTests +Tester = tester.Tester diff --git a/tools/ctap_test.py b/tools/testing/tests/fido2.py old mode 100755 new mode 100644 similarity index 64% rename from tools/ctap_test.py rename to tools/testing/tests/fido2.py index 984879a..ee8a7c7 --- a/tools/ctap_test.py +++ b/tools/testing/tests/fido2.py @@ -1,26 +1,12 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# -# Copyright 2019 SoloKeys Developers -# -# Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be -# copied, modified, or distributed except according to those terms. -# - -# Script for testing correctness of CTAP2/CTAP1 security token - from __future__ import print_function, absolute_import, unicode_literals -import sys, os, time, math +import sys, os, time from random import randint from binascii import hexlify import array, struct, socket -from fido2.hid import CtapHidDevice, CTAPHID -from fido2.client import Fido2Client, ClientError + from fido2.ctap import CtapError -from fido2.ctap1 import CTAP1, ApduError, APDU + from fido2.ctap2 import ES256, PinProtocolV1 from fido2.utils import Timeout, sha256, hmac_sha256 from fido2.attestation import Attestation @@ -28,18 +14,8 @@ from fido2.attestation import Attestation from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from solo.fido2 import force_udp_backend -from solo.client import SoloClient - - -# Set up a FIDO 2 client using the origin https://example.com - - -def ForceU2F(client, device): - client.ctap = CTAP1(device) - client.pin_protocol = None - client._do_make_credential = client._ctap1_make_credential - client._do_get_assertion = client._ctap1_get_assertion +from .tester import Tester, Test +from .util import shannon_entropy def VerifyAttestation(attest, data): @@ -47,485 +23,12 @@ def VerifyAttestation(attest, data): verifier().verify(attest.att_statement, attest.auth_data, data.hash) -def shannon_entropy(data): - sum = 0.0 - total = len(data) - for x in range(0, 256): - freq = data.count(x) - p = freq / total - if p > 0: - sum -= p * math.log2(p) - return sum +class FIDO2Tests(Tester): + def __init__(self, tester=None): + super().__init__(tester) - -class Packet(object): - def __init__(self, data): - l = len(data) - self.data = data - - def ToWireFormat(self,): - return self.data - - @staticmethod - def FromWireFormat(pkt_size, data): - return Packet(data) - - -class Test: - def __init__(self, msg): - self.msg = msg - - def __enter__(self,): - print(self.msg) - - def __exit__(self, a, b, c): - print("Pass") - - -class Tester: - def __init__(self,): - self.origin = "https://examplo.org" - self.host = "examplo.org" - self.user_count = 10 - self.is_sim = False - - def find_device(self,): - print(list(CtapHidDevice.list_devices())) - dev = next(CtapHidDevice.list_devices(), None) - if not dev: - raise RuntimeError("No FIDO device found") - self.dev = dev - self.client = Fido2Client(dev, self.origin) - self.ctap = self.client.ctap2 - self.ctap1 = CTAP1(dev) - - # consume timeout error - # cmd,resp = self.recv_raw() - - def set_user_count(self, count): - self.user_count = count - - def set_sim(self, b): - self.is_sim = b - - def send_data(self, cmd, data): - if type(data) != type(b""): - data = struct.pack("%dB" % len(data), *[ord(x) for x in data]) - with Timeout(1.0) as event: - return self.dev.call(cmd, data, event) - - def send_raw(self, data, cid=None): - if cid is None: - cid = self.dev._dev.cid - elif type(cid) != type(b""): - cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid]) - if type(data) != type(b""): - data = struct.pack("%dB" % len(data), *[ord(x) for x in data]) - data = cid + data - l = len(data) - if l != 64: - pad = "\x00" * (64 - l) - pad = struct.pack("%dB" % len(pad), *[ord(x) for x in pad]) - data = data + pad - data = list(data) - assert len(data) == 64 - self.dev._dev.InternalSendPacket(Packet(data)) - - def send_magic_reboot(self,): - """ - For use in simulation and testing. Random bytes that authentictor should detect - and then restart itself. - """ - magic_cmd = ( - b"\xac\x10\x52\xca\x95\xe5\x69\xde\x69\xe0\x2e\xbf" - + b"\xf3\x33\x48\x5f\x13\xf9\xb2\xda\x34\xc5\xa8\xa3" - + b"\x40\x52\x66\x97\xa9\xab\x2e\x0b\x39\x4d\x8d\x04" - + b"\x97\x3c\x13\x40\x05\xbe\x1a\x01\x40\xbf\xf6\x04" - + b"\x5b\xb2\x6e\xb7\x7a\x73\xea\xa4\x78\x13\xf6\xb4" - + b"\x9a\x72\x50\xdc" - ) - self.dev._dev.InternalSendPacket(Packet(magic_cmd)) - - def cid(self,): - return self.dev._dev.cid - - def set_cid(self, cid): - if type(cid) not in [type(b""), type(bytearray())]: - cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid]) - self.dev._dev.cid = cid - - def recv_raw(self,): - with Timeout(1.0) as t: - cmd, payload = self.dev._dev.InternalRecv() - return cmd, payload - - def check_error(self, data, err=None): - assert len(data) == 1 - if err is None: - if data[0] != 0: - raise CtapError(data[0]) - elif data[0] != err: - raise ValueError("Unexpected error: %02x" % data[0]) - - def testFunc(self, func, test, *args, **kwargs): - with Test(test): - res = None - expectedError = kwargs.get("expectedError", None) - otherArgs = kwargs.get("other", {}) - try: - res = func(*args, **otherArgs) - if expectedError != CtapError.ERR.SUCCESS: - raise RuntimeError("Expected error to occur for test: %s" % test) - except CtapError as e: - if expectedError is not None: - if e.code != expectedError: - raise RuntimeError( - "Got error code 0x%x, expected %x" % (e.code, expectedError) - ) - else: - print(e) - return res - - def testReset(self,): - print("Resetting Authenticator...") - self.ctap.reset() - - def testMC(self, test, *args, **kwargs): - return self.testFunc(self.ctap.make_credential, test, *args, **kwargs) - - def testGA(self, test, *args, **kwargs): - return self.testFunc(self.ctap.get_assertion, test, *args, **kwargs) - - def testCP(self, test, *args, **kwargs): - return self.testFunc(self.ctap.client_pin, test, *args, **kwargs) - - def testPP(self, test, *args, **kwargs): - return self.testFunc( - self.client.pin_protocol.get_pin_token, test, *args, **kwargs - ) - - def test_long_ping(self): - amt = 1000 - pingdata = os.urandom(amt) - with Test("Send %d byte ping" % amt): - try: - t1 = time.time() * 1000 - r = self.send_data(CTAPHID.PING, pingdata) - t2 = time.time() * 1000 - delt = t2 - t1 - # if (delt < 140 ): - # raise RuntimeError('Fob is too fast (%d ms)' % delt) - if delt > 555 * (amt / 1000): - raise RuntimeError("Fob is too slow (%d ms)" % delt) - if r != pingdata: - raise ValueError("Ping data not echo'd") - except CtapError as e: - raise RuntimeError("ping failed") - - sys.stdout.flush() - - def test_hid(self, check_timeouts=False): - if check_timeouts: - with Test("idle"): - try: - cmd, resp = self.recv_raw() - except socket.timeout: - pass - - with Test("init"): - r = self.send_data(CTAPHID.INIT, "\x11\x11\x11\x11\x11\x11\x11\x11") - - with Test("100 byte ping"): - pingdata = os.urandom(100) - try: - r = self.send_data(CTAPHID.PING, pingdata) - if r != pingdata: - raise ValueError("Ping data not echo'd") - except CtapError as e: - print("100 byte Ping failed:", e) - raise RuntimeError("ping failed") - - self.test_long_ping() - - with Test("Wink"): - r = self.send_data(CTAPHID.WINK, "") - - with Test("CBOR msg with no data"): - try: - r = self.send_data(CTAPHID.CBOR, "") - if len(r) > 1 or r[0] == 0: - raise RuntimeError("Cbor is supposed to have payload") - except CtapError as e: - assert e.code == CtapError.ERR.INVALID_LENGTH - - with Test("No data in U2F msg"): - try: - r = self.send_data(CTAPHID.MSG, "") - print(hexlify(r)) - if len(r) > 2: - raise RuntimeError("MSG is supposed to have payload") - except CtapError as e: - assert e.code == CtapError.ERR.INVALID_LENGTH - - with Test("Use init command to resync"): - r = self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - - with Test("Invalid HID command"): - try: - r = self.send_data(0x66, "") - raise RuntimeError("Invalid command did not return error") - except CtapError as e: - assert e.code == CtapError.ERR.INVALID_COMMAND - - with Test("Sending packet with too large of a length."): - self.send_raw("\x81\x1d\xba\x00") - cmd, resp = self.recv_raw() - self.check_error(resp, CtapError.ERR.INVALID_LENGTH) - - r = self.send_data(CTAPHID.PING, "\x44" * 200) - with Test("Sending packets that skip a sequence number."): - self.send_raw("\x81\x04\x90") - self.send_raw("\x00") - self.send_raw("\x01") - # skip 2 - self.send_raw("\x03") - cmd, resp = self.recv_raw() - self.check_error(resp, CtapError.ERR.INVALID_SEQ) - - with Test("Resync and send ping"): - try: - r = self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - pingdata = os.urandom(100) - r = self.send_data(CTAPHID.PING, pingdata) - if r != pingdata: - raise ValueError("Ping data not echo'd") - except CtapError as e: - raise RuntimeError("resync fail: ", e) - - with Test("Send ping and abort it"): - self.send_raw("\x81\x04\x00") - self.send_raw("\x00") - self.send_raw("\x01") - try: - r = self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - except CtapError as e: - raise RuntimeError("resync fail: ", e) - - with Test("Send ping and abort it with different cid, expect timeout"): - oldcid = self.cid() - newcid = "\x11\x22\x33\x44" - self.send_raw("\x81\x10\x00") - self.send_raw("\x00") - self.send_raw("\x01") - self.set_cid(newcid) - self.send_raw( - "\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88" - ) # init from different cid - print("wait for init response") - cmd, r = self.recv_raw() # init response - assert cmd == 0x86 - self.set_cid(oldcid) - if check_timeouts: - # print('wait for timeout') - cmd, r = self.recv_raw() # timeout response - assert cmd == 0xBF - - with Test("Test timeout"): - self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - t1 = time.time() * 1000 - self.send_raw("\x81\x04\x00") - self.send_raw("\x00") - self.send_raw("\x01") - cmd, r = self.recv_raw() # timeout response - t2 = time.time() * 1000 - delt = t2 - t1 - assert cmd == 0xBF - assert r[0] == CtapError.ERR.TIMEOUT - assert delt < 1000 and delt > 400 - - with Test("Test not cont"): - self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - self.send_raw("\x81\x04\x00") - self.send_raw("\x00") - self.send_raw("\x01") - self.send_raw("\x81\x10\x00") # init packet - cmd, r = self.recv_raw() # timeout response - assert cmd == 0xBF - assert r[0] == CtapError.ERR.INVALID_SEQ - - if check_timeouts: - with Test("Check random cont ignored"): - self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - self.send_raw("\x01\x10\x00") - try: - cmd, r = self.recv_raw() # timeout response - except socket.timeout: - pass - - with Test("Check busy"): - t1 = time.time() * 1000 - self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - oldcid = self.cid() - newcid = "\x11\x22\x33\x44" - self.send_raw("\x81\x04\x00") - self.set_cid(newcid) - self.send_raw("\x81\x04\x00") - cmd, r = self.recv_raw() # busy response - t2 = time.time() * 1000 - assert t2 - t1 < 100 - assert cmd == 0xBF - assert r[0] == CtapError.ERR.CHANNEL_BUSY - - self.set_cid(oldcid) - cmd, r = self.recv_raw() # timeout response - assert cmd == 0xBF - assert r[0] == CtapError.ERR.TIMEOUT - - with Test("Check busy interleaved"): - cid1 = "\x11\x22\x33\x44" - cid2 = "\x01\x22\x33\x44" - self.set_cid(cid2) - self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - self.set_cid(cid1) - self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") - self.send_raw("\x81\x00\x63") # echo 99 bytes first channel - - self.set_cid(cid2) # send ping on 2nd channel - self.send_raw("\x81\x00\x63") - time.sleep(0.1) - self.send_raw("\x00") - - cmd, r = self.recv_raw() # busy response - - self.set_cid(cid1) # finish 1st channel ping - self.send_raw("\x00") - - self.set_cid(cid2) - - assert cmd == 0xBF - assert r[0] == CtapError.ERR.CHANNEL_BUSY - - self.set_cid(cid1) - cmd, r = self.recv_raw() # ping response - assert cmd == 0x81 - assert len(r) == 0x63 - - if check_timeouts: - with Test("Test idle, wait for timeout"): - sys.stdout.flush() - try: - cmd, resp = self.recv_raw() - except socket.timeout: - pass - - with Test("Test cid 0 is invalid"): - self.set_cid("\x00\x00\x00\x00") - self.send_raw( - "\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88", cid="\x00\x00\x00\x00" - ) - cmd, r = self.recv_raw() # timeout - assert cmd == 0xBF - assert r[0] == CtapError.ERR.INVALID_CHANNEL - - with Test("Test invalid broadcast cid use"): - self.set_cid("\xff\xff\xff\xff") - self.send_raw( - "\x81\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88", cid="\xff\xff\xff\xff" - ) - cmd, r = self.recv_raw() # timeout - assert cmd == 0xBF - assert r[0] == CtapError.ERR.INVALID_CHANNEL - - def test_u2f(self,): - chal = sha256(b"AAA") - appid = sha256(b"BBB") - lastc = 0 - - regs = [] - - with Test("Check version"): - assert self.ctap1.get_version() == "U2F_V2" - - with Test("Check bad INS"): - try: - res = self.ctap1.send_apdu(0, 0, 0, 0, b"") - except ApduError as e: - assert e.code == 0x6D00 - - with Test("Check bad CLA"): - try: - res = self.ctap1.send_apdu(1, CTAP1.INS.VERSION, 0, 0, b"abc") - except ApduError as e: - assert e.code == 0x6E00 - - for i in range(0, self.user_count): - with Test( - "U2F reg + auth %d/%d (count: %02x)" % (i + 1, self.user_count, lastc) - ): - reg = self.ctap1.register(chal, appid) - reg.verify(appid, chal) - auth = self.ctap1.authenticate(chal, appid, reg.key_handle) - auth.verify(appid, chal, reg.public_key) - - regs.append(reg) - # check endianness - if lastc: - assert (auth.counter - lastc) < 10 - lastc = auth.counter - if lastc > 0x80000000: - print("WARNING: counter is unusually high: %04x" % lastc) - assert 0 - - for i in range(0, self.user_count): - with Test( - "Checking previous registration %d/%d" % (i + 1, self.user_count) - ): - auth = self.ctap1.authenticate(chal, appid, regs[i].key_handle) - auth.verify(appid, chal, regs[i].public_key) - - print("Check that all previous credentials are registered...") - for i in range(0, self.user_count): - with Test("Check that previous credential %d is registered" % i): - try: - auth = self.ctap1.authenticate( - chal, appid, regs[i].key_handle, check_only=True - ) - except ApduError as e: - # Indicates that key handle is registered - assert e.code == APDU.USE_NOT_SATISFIED - - with Test("Check an incorrect key handle is not registered"): - kh = bytearray(regs[0].key_handle) - kh[0] = kh[0] ^ (0x40) - try: - self.ctap1.authenticate(chal, appid, kh, check_only=True) - assert 0 - except ApduError as e: - assert e.code == APDU.WRONG_DATA - - with Test("Try to sign with incorrect key handle"): - try: - self.ctap1.authenticate(chal, appid, kh) - assert 0 - except ApduError as e: - assert e.code == APDU.WRONG_DATA - - with Test("Try to sign using an incorrect keyhandle length"): - try: - kh = regs[0].key_handle - self.ctap1.authenticate(chal, appid, kh[: len(kh) // 2]) - assert 0 - except ApduError as e: - assert e.code == APDU.WRONG_DATA - - with Test("Try to sign using an incorrect appid"): - badid = bytearray(appid) - badid[0] = badid[0] ^ (0x40) - try: - auth = self.ctap1.authenticate(chal, badid, regs[0].key_handle) - assert 0 - except ApduError as e: - assert e.code == APDU.WRONG_DATA + def run(self,): + self.test_fido2_other() def test_fido2_simple(self, pin_token=None): creds = [] @@ -955,7 +458,7 @@ class Tester: if self.is_sim: print("Sending restart command...") self.send_magic_reboot() - time.sleep(0.25) + self.delay(0.25) else: print("Please reboot authentictor and hit enter") input() @@ -1098,7 +601,6 @@ class Tester: rp, user, None, - expectedError=CtapError.ERR.MISSING_PARAMETER, ) self.testMC( @@ -1847,208 +1349,3 @@ class Tester: assert len(assertions) == len(users) for x, y in zip(assertions, creds): x.verify(client_data.hash, y.public_key) - - def test_solo(self,): - """ - Solo specific tests - """ - # RNG command - sc = SoloClient() - sc.find_device(self.dev) - sc.use_u2f() - memmap = (0x08005000, 0x08005000 + 198 * 1024 - 8) - - total = 1024 * 16 - with Test("Gathering %d random bytes..." % total): - entropy = b"" - while len(entropy) < total: - entropy += sc.get_rng() - - with Test("Test entropy is close to perfect"): - sum = shannon_entropy(entropy) - assert sum > 7.98 - print("Entropy is %.5f bits per byte." % sum) - - with Test("Test Solo version command"): - assert len(sc.solo_version()) == 3 - - with Test("Test bootloader is not active"): - try: - sc.write_flash(memmap[0], b"1234") - except ApduError: - pass - - sc.exchange = sc.exchange_fido2 - with Test("Test Solo version and random commands with fido2 layer"): - assert len(sc.solo_version()) == 3 - sc.get_rng() - - def test_bootloader(self,): - sc = SoloClient() - sc.find_device(self.dev) - sc.use_u2f() - - memmap = (0x08005000, 0x08005000 + 198 * 1024 - 8) - data = b"A" * 64 - - with Test("Test version command"): - assert len(sc.bootloader_version()) == 3 - - with Test("Test write command"): - sc.write_flash(memmap[0], data) - - for addr in (memmap[0] - 8, memmap[0] - 4, memmap[1], memmap[1] - 8): - with Test("Test out of bounds write command at 0x%04x" % addr): - try: - sc.write_flash(addr, data) - except CtapError as e: - assert e.code == CtapError.ERR.NOT_ALLOWED - - def test_responses(self,): - PIN = "1234" - RPID = self.host - for dev in CtapHidDevice.list_devices(): - print("dev", dev) - client = Fido2Client(dev, RPID) - ctap = client.ctap2 - # ctap.reset() - try: - if PIN: - client.pin_protocol.set_pin(PIN) - except: - pass - - inf = ctap.get_info() - # print (inf) - print("versions: ", inf.versions) - print("aaguid: ", inf.aaguid) - print("rk: ", inf.options["rk"]) - print("clientPin: ", inf.options["clientPin"]) - print("max_message_size: ", inf.max_msg_size) - - # rp = {'id': 'SelectDevice', 'name': 'SelectDevice'} - rp = {"id": RPID, "name": "ExaRP"} - user = {"id": os.urandom(10), "name": "SelectDevice"} - user = {"id": b"21first one", "name": "single User"} - challenge = "Y2hhbGxlbmdl" - - if 1: - attest, data = client.make_credential( - rp, user, challenge, exclude_list=[], pin=PIN, rk=True - ) - - cred = attest.auth_data.credential_data - creds = [cred] - - allow_list = [{"id": creds[0].credential_id, "type": "public-key"}] - allow_list = [] - assertions, client_data = client.get_assertion( - rp["id"], challenge, pin=PIN - ) - assertions[0].verify(client_data.hash, creds[0].public_key) - - if 0: - print("registering 1 user with RK") - t1 = time.time() * 1000 - attest, data = client.make_credential( - rp, user, challenge, pin=PIN, exclude_list=[], rk=True - ) - t2 = time.time() * 1000 - VerifyAttestation(attest, data) - creds = [attest.auth_data.credential_data] - print("Register valid (%d ms)" % (t2 - t1)) - - print("1 assertion") - t1 = time.time() * 1000 - assertions, client_data = client.get_assertion( - rp["id"], challenge, pin=PIN - ) - t2 = time.time() * 1000 - assertions[0].verify(client_data.hash, creds[0].public_key) - print("Assertion valid (%d ms)" % (t2 - t1)) - - # print('fmt:',attest.fmt) - # print('rp_id_hash',attest.auth_data.rp_id_hash) - # print('flags:', hex(attest.auth_data.flags)) - # print('count:', hex(attest.auth_data.counter)) - print("flags MC:", attest.auth_data) - print("flags GA:", assertions[0].auth_data) - # print('cred_id:',attest.auth_data.credential_data.credential_id) - # print('pubkey:',attest.auth_data.credential_data.public_key) - # print('aaguid:',attest.auth_data.credential_data.aaguid) - # print('cred data:',attest.auth_data.credential_data) - # print('auth_data:',attest.auth_data) - # print('auth_data:',attest.auth_data) - # print('alg:',attest.att_statement['alg']) - # print('sig:',attest.att_statement['sig']) - # print('x5c:',attest.att_statement['x5c']) - # print('data:',data) - - print("assertion:", assertions[0]) - print("clientData:", client_data) - - print() - # break - - -def test_find_brute_force(): - i = 0 - while 1: - t1 = time.time() * 1000 - t = Tester() - t.find_device() - t2 = time.time() * 1000 - print("connected %d (%d ms)" % (i, t2 - t1)) - i += 1 - time.sleep(0.01) - - -if __name__ == "__main__": - if len(sys.argv) < 2: - print("Usage: %s [sim] <[u2f]|[fido2]|[rk]|[hid]|[ping]>") - sys.exit(0) - - t = Tester() - t.set_user_count(3) - - if "sim" in sys.argv: - print("Using UDP backend.") - force_udp_backend() - t.set_sim(True) - t.set_user_count(10) - - t.find_device() - - if "solo" in sys.argv: - t.test_solo() - - if "u2f" in sys.argv: - t.test_u2f() - - if "fido2" in sys.argv: - t.test_fido2() - t.test_fido2_other() - - if "fido2-ext" in sys.argv: - t.test_extensions() - - if "rk" in sys.argv: - t.test_rk() - - if "ping" in sys.argv: - t.test_long_ping() - - # hid tests are a bit invasive and should be done last - if "hid" in sys.argv: - t.test_hid(check_timeouts=t.is_sim) - - if "bootloader" in sys.argv: - if t.is_sim: - raise RuntimeError("Cannot test bootloader in simulation yet.") - print("Put device in bootloader mode and then hit enter") - input() - t.test_bootloader() - - # t.test_responses() - # test_find_brute_force() - # t.test_fido2_brute_force() diff --git a/tools/testing/tests/hid.py b/tools/testing/tests/hid.py new file mode 100644 index 0000000..89a9e70 --- /dev/null +++ b/tools/testing/tests/hid.py @@ -0,0 +1,245 @@ +from .tester import Tester, Test + + +class HIDTests(Tester): + def __init__(self, tester=None): + super().__init__(tester) + self.check_timeouts = False + + def set_check_timeouts(self, en): + self.check_timeouts = en + + def run(self,): + self.test_hid(self.check_timeouts) + + def test_long_ping(self): + amt = 1000 + pingdata = os.urandom(amt) + with Test("Send %d byte ping" % amt): + try: + t1 = time.time() * 1000 + r = self.send_data(CTAPHID.PING, pingdata) + t2 = time.time() * 1000 + delt = t2 - t1 + # if (delt < 140 ): + # raise RuntimeError('Fob is too fast (%d ms)' % delt) + if delt > 555 * (amt / 1000): + raise RuntimeError("Fob is too slow (%d ms)" % delt) + if r != pingdata: + raise ValueError("Ping data not echo'd") + except CtapError as e: + raise RuntimeError("ping failed") + + sys.stdout.flush() + + def test_hid(self, check_timeouts=False): + if check_timeouts: + with Test("idle"): + try: + cmd, resp = self.recv_raw() + except socket.timeout: + pass + + with Test("init"): + r = self.send_data(CTAPHID.INIT, "\x11\x11\x11\x11\x11\x11\x11\x11") + + with Test("100 byte ping"): + pingdata = os.urandom(100) + try: + r = self.send_data(CTAPHID.PING, pingdata) + if r != pingdata: + raise ValueError("Ping data not echo'd") + except CtapError as e: + print("100 byte Ping failed:", e) + raise RuntimeError("ping failed") + + self.test_long_ping() + + with Test("Wink"): + r = self.send_data(CTAPHID.WINK, "") + + with Test("CBOR msg with no data"): + try: + r = self.send_data(CTAPHID.CBOR, "") + if len(r) > 1 or r[0] == 0: + raise RuntimeError("Cbor is supposed to have payload") + except CtapError as e: + assert e.code == CtapError.ERR.INVALID_LENGTH + + with Test("No data in U2F msg"): + try: + r = self.send_data(CTAPHID.MSG, "") + print(hexlify(r)) + if len(r) > 2: + raise RuntimeError("MSG is supposed to have payload") + except CtapError as e: + assert e.code == CtapError.ERR.INVALID_LENGTH + + with Test("Use init command to resync"): + r = self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") + + with Test("Invalid HID command"): + try: + r = self.send_data(0x66, "") + raise RuntimeError("Invalid command did not return error") + except CtapError as e: + assert e.code == CtapError.ERR.INVALID_COMMAND + + with Test("Sending packet with too large of a length."): + self.send_raw("\x81\x1d\xba\x00") + cmd, resp = self.recv_raw() + self.check_error(resp, CtapError.ERR.INVALID_LENGTH) + + r = self.send_data(CTAPHID.PING, "\x44" * 200) + with Test("Sending packets that skip a sequence number."): + self.send_raw("\x81\x04\x90") + self.send_raw("\x00") + self.send_raw("\x01") + # skip 2 + self.send_raw("\x03") + cmd, resp = self.recv_raw() + self.check_error(resp, CtapError.ERR.INVALID_SEQ) + + with Test("Resync and send ping"): + try: + r = self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") + pingdata = os.urandom(100) + r = self.send_data(CTAPHID.PING, pingdata) + if r != pingdata: + raise ValueError("Ping data not echo'd") + except CtapError as e: + raise RuntimeError("resync fail: ", e) + + with Test("Send ping and abort it"): + self.send_raw("\x81\x04\x00") + self.send_raw("\x00") + self.send_raw("\x01") + try: + r = self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") + except CtapError as e: + raise RuntimeError("resync fail: ", e) + + with Test("Send ping and abort it with different cid, expect timeout"): + oldcid = self.cid() + newcid = "\x11\x22\x33\x44" + self.send_raw("\x81\x10\x00") + self.send_raw("\x00") + self.send_raw("\x01") + self.set_cid(newcid) + self.send_raw( + "\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88" + ) # init from different cid + print("wait for init response") + cmd, r = self.recv_raw() # init response + assert cmd == 0x86 + self.set_cid(oldcid) + if check_timeouts: + # print('wait for timeout') + cmd, r = self.recv_raw() # timeout response + assert cmd == 0xBF + + with Test("Test timeout"): + self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") + t1 = time.time() * 1000 + self.send_raw("\x81\x04\x00") + self.send_raw("\x00") + self.send_raw("\x01") + cmd, r = self.recv_raw() # timeout response + t2 = time.time() * 1000 + delt = t2 - t1 + assert cmd == 0xBF + assert r[0] == CtapError.ERR.TIMEOUT + assert delt < 1000 and delt > 400 + + with Test("Test not cont"): + self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") + self.send_raw("\x81\x04\x00") + self.send_raw("\x00") + self.send_raw("\x01") + self.send_raw("\x81\x10\x00") # init packet + cmd, r = self.recv_raw() # timeout response + assert cmd == 0xBF + assert r[0] == CtapError.ERR.INVALID_SEQ + + if check_timeouts: + with Test("Check random cont ignored"): + self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") + self.send_raw("\x01\x10\x00") + try: + cmd, r = self.recv_raw() # timeout response + except socket.timeout: + pass + + with Test("Check busy"): + t1 = time.time() * 1000 + self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") + oldcid = self.cid() + newcid = "\x11\x22\x33\x44" + self.send_raw("\x81\x04\x00") + self.set_cid(newcid) + self.send_raw("\x81\x04\x00") + cmd, r = self.recv_raw() # busy response + t2 = time.time() * 1000 + assert t2 - t1 < 100 + assert cmd == 0xBF + assert r[0] == CtapError.ERR.CHANNEL_BUSY + + self.set_cid(oldcid) + cmd, r = self.recv_raw() # timeout response + assert cmd == 0xBF + assert r[0] == CtapError.ERR.TIMEOUT + + with Test("Check busy interleaved"): + cid1 = "\x11\x22\x33\x44" + cid2 = "\x01\x22\x33\x44" + self.set_cid(cid2) + self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") + self.set_cid(cid1) + self.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88") + self.send_raw("\x81\x00\x63") # echo 99 bytes first channel + + self.set_cid(cid2) # send ping on 2nd channel + self.send_raw("\x81\x00\x63") + self.delay(0.1) + self.send_raw("\x00") + + cmd, r = self.recv_raw() # busy response + + self.set_cid(cid1) # finish 1st channel ping + self.send_raw("\x00") + + self.set_cid(cid2) + + assert cmd == 0xBF + assert r[0] == CtapError.ERR.CHANNEL_BUSY + + self.set_cid(cid1) + cmd, r = self.recv_raw() # ping response + assert cmd == 0x81 + assert len(r) == 0x63 + + if check_timeouts: + with Test("Test idle, wait for timeout"): + sys.stdout.flush() + try: + cmd, resp = self.recv_raw() + except socket.timeout: + pass + + with Test("Test cid 0 is invalid"): + self.set_cid("\x00\x00\x00\x00") + self.send_raw( + "\x86\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88", cid="\x00\x00\x00\x00" + ) + cmd, r = self.recv_raw() # timeout + assert cmd == 0xBF + assert r[0] == CtapError.ERR.INVALID_CHANNEL + + with Test("Test invalid broadcast cid use"): + self.set_cid("\xff\xff\xff\xff") + self.send_raw( + "\x81\x00\x08\x11\x22\x33\x44\x55\x66\x77\x88", cid="\xff\xff\xff\xff" + ) + cmd, r = self.recv_raw() # timeout + assert cmd == 0xBF + assert r[0] == CtapError.ERR.INVALID_CHANNEL diff --git a/tools/testing/tests/solo.py b/tools/testing/tests/solo.py new file mode 100644 index 0000000..518389a --- /dev/null +++ b/tools/testing/tests/solo.py @@ -0,0 +1,70 @@ +from solo.client import SoloClient + +from fido2.ctap1 import ApduError, APDU + +from .util import shannon_entropy +from .tester import Tester, Test + + +class SoloTests(Tester): + def __init__(self, tester=None): + super().__init__(tester) + + def run(self,): + self.test_solo() + + def test_solo(self,): + """ + Solo specific tests + """ + # RNG command + sc = SoloClient() + sc.find_device(self.dev) + sc.use_u2f() + memmap = (0x08005000, 0x08005000 + 198 * 1024 - 8) + + total = 1024 * 16 + with Test("Gathering %d random bytes..." % total): + entropy = b"" + while len(entropy) < total: + entropy += sc.get_rng() + + with Test("Test entropy is close to perfect"): + sum = shannon_entropy(entropy) + assert sum > 7.98 + print("Entropy is %.5f bits per byte." % sum) + + with Test("Test Solo version command"): + assert len(sc.solo_version()) == 3 + + with Test("Test bootloader is not active"): + try: + sc.write_flash(memmap[0], b"1234") + except ApduError: + pass + + sc.exchange = sc.exchange_fido2 + with Test("Test Solo version and random commands with fido2 layer"): + assert len(sc.solo_version()) == 3 + sc.get_rng() + + def test_bootloader(self,): + sc = SoloClient() + sc.find_device(self.dev) + sc.use_u2f() + + memmap = (0x08005000, 0x08005000 + 198 * 1024 - 8) + data = b"A" * 64 + + with Test("Test version command"): + assert len(sc.bootloader_version()) == 3 + + with Test("Test write command"): + sc.write_flash(memmap[0], data) + + for addr in (memmap[0] - 8, memmap[0] - 4, memmap[1], memmap[1] - 8): + with Test("Test out of bounds write command at 0x%04x" % addr): + try: + sc.write_flash(addr, data) + except CtapError as e: + assert e.code == CtapError.ERR.NOT_ALLOWED diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py new file mode 100644 index 0000000..83ab4a0 --- /dev/null +++ b/tools/testing/tests/tester.py @@ -0,0 +1,181 @@ +import time + +from fido2.hid import CtapHidDevice, CTAPHID +from fido2.client import Fido2Client, ClientError +from fido2.ctap1 import CTAP1, ApduError, APDU +from fido2.ctap import CtapError + + +def ForceU2F(client, device): + client.ctap = CTAP1(device) + client.pin_protocol = None + client._do_make_credential = client._ctap1_make_credential + client._do_get_assertion = client._ctap1_get_assertion + + +class Packet(object): + def __init__(self, data): + l = len(data) + self.data = data + + def ToWireFormat(self,): + return self.data + + @staticmethod + def FromWireFormat(pkt_size, data): + return Packet(data) + + +class Test: + def __init__(self, msg): + self.msg = msg + + def __enter__(self,): + print(self.msg) + + def __exit__(self, a, b, c): + print("Pass") + + +class Tester: + def __init__(self, tester=None): + self.origin = "https://examplo.org" + self.host = "examplo.org" + self.user_count = 10 + self.is_sim = False + if tester: + self.initFromTester(tester) + + def initFromTester(self, tester): + self.user_count = tester.user_count + self.is_sim = tester.is_sim + self.dev = tester.dev + self.ctap = tester.ctap + self.ctap1 = tester.ctap1 + self.client = tester.client + + def find_device(self,): + print(list(CtapHidDevice.list_devices())) + dev = next(CtapHidDevice.list_devices(), None) + if not dev: + raise RuntimeError("No FIDO device found") + self.dev = dev + self.client = Fido2Client(dev, self.origin) + self.ctap = self.client.ctap2 + self.ctap1 = CTAP1(dev) + + # consume timeout error + # cmd,resp = self.recv_raw() + + def set_user_count(self, count): + self.user_count = count + + def set_sim(self, b): + self.is_sim = b + + def send_data(self, cmd, data): + if type(data) != type(b""): + data = struct.pack("%dB" % len(data), *[ord(x) for x in data]) + with Timeout(1.0) as event: + return self.dev.call(cmd, data, event) + + def send_raw(self, data, cid=None): + if cid is None: + cid = self.dev._dev.cid + elif type(cid) != type(b""): + cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid]) + if type(data) != type(b""): + data = struct.pack("%dB" % len(data), *[ord(x) for x in data]) + data = cid + data + l = len(data) + if l != 64: + pad = "\x00" * (64 - l) + pad = struct.pack("%dB" % len(pad), *[ord(x) for x in pad]) + data = data + pad + data = list(data) + assert len(data) == 64 + self.dev._dev.InternalSendPacket(Packet(data)) + + def send_magic_reboot(self,): + """ + For use in simulation and testing. Random bytes that authentictor should detect + and then restart itself. + """ + magic_cmd = ( + b"\xac\x10\x52\xca\x95\xe5\x69\xde\x69\xe0\x2e\xbf" + + b"\xf3\x33\x48\x5f\x13\xf9\xb2\xda\x34\xc5\xa8\xa3" + + b"\x40\x52\x66\x97\xa9\xab\x2e\x0b\x39\x4d\x8d\x04" + + b"\x97\x3c\x13\x40\x05\xbe\x1a\x01\x40\xbf\xf6\x04" + + b"\x5b\xb2\x6e\xb7\x7a\x73\xea\xa4\x78\x13\xf6\xb4" + + b"\x9a\x72\x50\xdc" + ) + self.dev._dev.InternalSendPacket(Packet(magic_cmd)) + + def cid(self,): + return self.dev._dev.cid + + def set_cid(self, cid): + if type(cid) not in [type(b""), type(bytearray())]: + cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid]) + self.dev._dev.cid = cid + + def recv_raw(self,): + with Timeout(1.0) as t: + cmd, payload = self.dev._dev.InternalRecv() + return cmd, payload + + def check_error(self, data, err=None): + assert len(data) == 1 + if err is None: + if data[0] != 0: + raise CtapError(data[0]) + elif data[0] != err: + raise ValueError("Unexpected error: %02x" % data[0]) + + def testFunc(self, func, test, *args, **kwargs): + with Test(test): + res = None + expectedError = kwargs.get("expectedError", None) + otherArgs = kwargs.get("other", {}) + try: + res = func(*args, **otherArgs) + if expectedError != CtapError.ERR.SUCCESS: + raise RuntimeError("Expected error to occur for test: %s" % test) + except CtapError as e: + if expectedError is not None: + if e.code != expectedError: + raise RuntimeError( + "Got error code 0x%x, expected %x" % (e.code, expectedError) + ) + else: + print(e) + return res + + def testReset(self,): + print("Resetting Authenticator...") + try: + self.ctap.reset() + except CtapError as e: + # Some authenticators need a power cycle + print("You must power cycle authentictor. Hit enter when done.") + input() + time.sleep(0.2) + self.find_device() + self.ctap.reset() + + def testMC(self, test, *args, **kwargs): + return self.testFunc(self.ctap.make_credential, test, *args, **kwargs) + + def testGA(self, test, *args, **kwargs): + return self.testFunc(self.ctap.get_assertion, test, *args, **kwargs) + + def testCP(self, test, *args, **kwargs): + return self.testFunc(self.ctap.client_pin, test, *args, **kwargs) + + def testPP(self, test, *args, **kwargs): + return self.testFunc( + self.client.pin_protocol.get_pin_token, test, *args, **kwargs + ) + + def delay(self, secs): + time.sleep(secs) diff --git a/tools/testing/tests/u2f.py b/tools/testing/tests/u2f.py new file mode 100644 index 0000000..1d0b817 --- /dev/null +++ b/tools/testing/tests/u2f.py @@ -0,0 +1,103 @@ +from fido2.ctap1 import CTAP1, ApduError, APDU +from fido2.utils import sha256 + +from .tester import Tester, Test + + +class U2FTests(Tester): + def __init__(self, tester=None): + super().__init__(tester) + + def run(self,): + self.test_u2f() + + def test_u2f(self,): + chal = sha256(b"AAA") + appid = sha256(b"BBB") + lastc = 0 + + regs = [] + + with Test("Check version"): + assert self.ctap1.get_version() == "U2F_V2" + + with Test("Check bad INS"): + try: + res = self.ctap1.send_apdu(0, 0, 0, 0, b"") + except ApduError as e: + assert e.code == 0x6D00 + + with Test("Check bad CLA"): + try: + res = self.ctap1.send_apdu(1, CTAP1.INS.VERSION, 0, 0, b"abc") + except ApduError as e: + assert e.code == 0x6E00 + + for i in range(0, self.user_count): + with Test( + "U2F reg + auth %d/%d (count: %02x)" % (i + 1, self.user_count, lastc) + ): + reg = self.ctap1.register(chal, appid) + reg.verify(appid, chal) + auth = self.ctap1.authenticate(chal, appid, reg.key_handle) + auth.verify(appid, chal, reg.public_key) + + regs.append(reg) + # check endianness + if lastc: + assert (auth.counter - lastc) < 10 + lastc = auth.counter + if lastc > 0x80000000: + print("WARNING: counter is unusually high: %04x" % lastc) + assert 0 + + for i in range(0, self.user_count): + with Test( + "Checking previous registration %d/%d" % (i + 1, self.user_count) + ): + auth = self.ctap1.authenticate(chal, appid, regs[i].key_handle) + auth.verify(appid, chal, regs[i].public_key) + + print("Check that all previous credentials are registered...") + for i in range(0, self.user_count): + with Test("Check that previous credential %d is registered" % i): + try: + auth = self.ctap1.authenticate( + chal, appid, regs[i].key_handle, check_only=True + ) + except ApduError as e: + # Indicates that key handle is registered + assert e.code == APDU.USE_NOT_SATISFIED + + with Test("Check an incorrect key handle is not registered"): + kh = bytearray(regs[0].key_handle) + kh[0] = kh[0] ^ (0x40) + try: + self.ctap1.authenticate(chal, appid, kh, check_only=True) + assert 0 + except ApduError as e: + assert e.code == APDU.WRONG_DATA + + with Test("Try to sign with incorrect key handle"): + try: + self.ctap1.authenticate(chal, appid, kh) + assert 0 + except ApduError as e: + assert e.code == APDU.WRONG_DATA + + with Test("Try to sign using an incorrect keyhandle length"): + try: + kh = regs[0].key_handle + self.ctap1.authenticate(chal, appid, kh[: len(kh) // 2]) + assert 0 + except ApduError as e: + assert e.code == APDU.WRONG_DATA + + with Test("Try to sign using an incorrect appid"): + badid = bytearray(appid) + badid[0] = badid[0] ^ (0x40) + try: + auth = self.ctap1.authenticate(chal, badid, regs[0].key_handle) + assert 0 + except ApduError as e: + assert e.code == APDU.WRONG_DATA diff --git a/tools/testing/tests/util.py b/tools/testing/tests/util.py new file mode 100644 index 0000000..d6b07ae --- /dev/null +++ b/tools/testing/tests/util.py @@ -0,0 +1,12 @@ +import math + + +def shannon_entropy(data): + sum = 0.0 + total = len(data) + for x in range(0, 256): + freq = data.count(x) + p = freq / total + if p > 0: + sum -= p * math.log2(p) + return sum From c4262b0f5b815f6d410112129e2996416590e89d Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 22 Mar 2019 00:20:48 -0400 Subject: [PATCH 2/7] rename --- tools/testing/{ctap_test.py => main.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename tools/testing/{ctap_test.py => main.py} (100%) diff --git a/tools/testing/ctap_test.py b/tools/testing/main.py similarity index 100% rename from tools/testing/ctap_test.py rename to tools/testing/main.py From 0a7845459c7bbacf2142f7943571240bd113785c Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 22 Mar 2019 00:45:28 -0400 Subject: [PATCH 3/7] breakup test_fido2 --- tools/testing/tests/fido2.py | 682 +++++++++++++--------------------- tools/testing/tests/tester.py | 10 + 2 files changed, 275 insertions(+), 417 deletions(-) diff --git a/tools/testing/tests/fido2.py b/tools/testing/tests/fido2.py index ee8a7c7..2838e78 100644 --- a/tools/testing/tests/fido2.py +++ b/tools/testing/tests/fido2.py @@ -17,6 +17,17 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from .tester import Tester, Test from .util import shannon_entropy +rp = {"id": "examplo.org", "name": "ExaRP"} +rp2 = {"id": "solokeys.com", "name": "ExaRP"} +user = {"id": b"usee_od", "name": "AB User"} +user1 = {"id": b"1234567890", "name": "Conor Patrick"} +user2 = {"id": b"oiewhfoi", "name": "Han Solo"} +user3 = {"id": b"23ohfpjwo@@", "name": "John Smith"} +challenge = "Y2hhbGxlbmdl" +pin_protocol = 1 +key_params = [{"type": "public-key", "alg": ES256.ALGORITHM}] +cdh = b"123456789abcdef0123456789abcdef0" + def VerifyAttestation(attest, data): verifier = Attestation.for_type(attest.fmt) @@ -28,14 +39,11 @@ class FIDO2Tests(Tester): super().__init__(tester) def run(self,): - self.test_fido2_other() + self.test_fido2() def test_fido2_simple(self, pin_token=None): creds = [] exclude_list = [] - rp = {"id": self.host, "name": "ExaRP"} - user = {"id": b"usee_od", "name": "AB User"} - challenge = "Y2hhbGxlbmdl" PIN = pin_token fake_id1 = array.array("B", [randint(0, 255) for i in range(0, 150)]).tobytes() @@ -68,8 +76,6 @@ class FIDO2Tests(Tester): def test_fido2_brute_force(self): creds = [] exclude_list = [] - rp = {"id": self.host, "name": "ExaRP"} - user = {"id": b"usee_od", "name": "AB User"} PIN = None abc = "abcdefghijklnmopqrstuvwxyz" abc += abc.upper() @@ -120,172 +126,9 @@ class FIDO2Tests(Tester): print("Assertion valid (%d ms)" % (t2 - t1)) sys.stdout.flush() - def test_fido2(self): - def test(self, pincode=None): - creds = [] - exclude_list = [] - rp = {"id": self.host, "name": "ExaRP"} - user = {"id": b"usee_od", "name": "AB User"} - challenge = "Y2hhbGxlbmdl" - PIN = pincode - - fake_id1 = array.array( - "B", [randint(0, 255) for i in range(0, 150)] - ).tobytes() - fake_id2 = array.array( - "B", [randint(0, 255) for i in range(0, 73)] - ).tobytes() - - exclude_list.append({"id": fake_id1, "type": "public-key"}) - exclude_list.append({"id": fake_id2, "type": "public-key"}) - - # test make credential - with Test("make %d credentials" % self.user_count): - lastc = 0 - for i in range(0, self.user_count): - attest, data = self.client.make_credential( - rp, user, challenge, pin=PIN, exclude_list=[] - ) - VerifyAttestation(attest, data) - - # verify counter is correct - if lastc > 0: - assert attest.auth_data.counter - lastc < 10 - assert attest.auth_data.counter - lastc > 0 - assert attest.auth_data.counter < 0x10000 - lastc = attest.auth_data.counter - - cred = attest.auth_data.credential_data - creds.append(cred) - print(cred) - - if PIN is not None: - with Test("make credential with wrong pin code"): - try: - attest, data = self.client.make_credential( - rp, user, challenge, pin=PIN + " ", exclude_list=[] - ) - except CtapError as e: - assert e.code == CtapError.ERR.PIN_INVALID - except ClientError as e: - assert e.cause.code == CtapError.ERR.PIN_INVALID - - with Test("make credential with exclude list"): - attest, data = self.client.make_credential( - rp, user, challenge, pin=PIN, exclude_list=exclude_list - ) - VerifyAttestation(attest, data) - cred = attest.auth_data.credential_data - creds.append(cred) - - with Test("make credential with exclude list including real credential"): - real_excl = [{"id": cred.credential_id, "type": "public-key"}] - try: - attest, data = self.client.make_credential( - rp, - user, - challenge, - pin=PIN, - exclude_list=exclude_list + real_excl, - ) - raise RuntimeError("Exclude list did not return expected error") - except CtapError as e: - assert e.code == CtapError.ERR.CREDENTIAL_EXCLUDED - except ClientError as e: - assert e.cause.code == CtapError.ERR.CREDENTIAL_EXCLUDED - - for i, x in enumerate(creds): - with Test("get assertion %d" % i): - allow_list = [{"id": x.credential_id, "type": "public-key"}] - assertions, client_data = self.client.get_assertion( - rp["id"], challenge, allow_list, pin=PIN - ) - assertions[0].verify(client_data.hash, x.public_key) - - if PIN is not None: - with Test("get assertion with wrong pin code"): - try: - assertions, client_data = self.client.get_assertion( - rp["id"], challenge, allow_list, pin=PIN + " " - ) - except CtapError as e: - assert e.code == CtapError.ERR.PIN_INVALID - except ClientError as e: - assert e.cause.code == CtapError.ERR.PIN_INVALID - - with Test("get multiple assertions"): - allow_list = [ - {"id": x.credential_id, "type": "public-key"} for x in creds - ] - assertions, client_data = self.client.get_assertion( - rp["id"], challenge, allow_list, pin=PIN - ) - - for ass, cred in zip(assertions, creds): - i += 1 - - ass.verify(client_data.hash, cred.public_key) - print("%d verified" % i) - - with Test("Reset device"): - try: - self.ctap.reset() - except CtapError as e: - print("Warning, reset failed: ", e) - pass - - test(self, None) - - with Test("Set a pin code"): - PIN = "1122aabbwfg0h9g !@#==" - self.client.pin_protocol.set_pin(PIN) - - with Test("Illegally set pin code again"): - try: - self.client.pin_protocol.set_pin(PIN) - except CtapError as e: - assert e.code == CtapError.ERR.NOT_ALLOWED - - with Test("Change pin code"): - PIN2 = PIN + "_pin2" - self.client.pin_protocol.change_pin(PIN, PIN2) - PIN = PIN2 - - with Test("Change pin code using wrong pin"): - try: - self.client.pin_protocol.change_pin(PIN.replace("a", "b"), "1234") - except CtapError as e: - assert e.code == CtapError.ERR.PIN_INVALID - - with Test("MC using wrong pin"): - try: - self.test_fido2_simple("abcd3") - except ClientError as e: - assert e.cause.code == CtapError.ERR.PIN_INVALID - - with Test("get info"): - inf = self.ctap.get_info() - - self.test_fido2_simple(PIN) - - with Test("Re-run make_credential and get_assertion tests with pin code"): - test(self, PIN) - - with Test("Reset device"): - try: - self.ctap.reset() - except CtapError as e: - print("Warning, reset failed: ", e) - def test_extensions(self,): creds = [] exclude_list = [] - rp = {"id": self.host, "name": "ExaRP"} - user = {"id": b"usee_od", "name": "AB User"} - challenge = "Y2hhbGxlbmdl" - pin_protocol = 1 - key_params = [{"type": "public-key", "alg": ES256.ALGORITHM}] - cdh = b"123456789abcdef0123456789abcdef0" salt1 = b"\x5a" * 32 salt2 = b"\x96" * 32 @@ -439,33 +282,7 @@ class FIDO2Tests(Tester): expectedError=CtapError.ERR.INVALID_LENGTH, ) - def test_fido2_other(self,): - - creds = [] - exclude_list = [] - rp = {"id": self.host, "name": "ExaRP"} - rp2 = {"id": "solokeys.com", "name": "ExaRP"} - user = {"id": b"usee_od", "name": "AB User"} - user1 = {"id": b"1234567890", "name": "Conor Patrick"} - user2 = {"id": b"oiewhfoi", "name": "Han Solo"} - user3 = {"id": b"23ohfpjwo@@", "name": "John Smith"} - challenge = "Y2hhbGxlbmdl" - pin_protocol = 1 - key_params = [{"type": "public-key", "alg": ES256.ALGORITHM}] - cdh = b"123456789abcdef0123456789abcdef0" - - def reboot(): - if self.is_sim: - print("Sending restart command...") - self.send_magic_reboot() - self.delay(0.25) - else: - print("Please reboot authentictor and hit enter") - input() - self.find_device() - - self.testReset() - + def test_get_info(self,): with Test("Get info"): info = self.ctap.get_info() @@ -480,6 +297,31 @@ class FIDO2Tests(Tester): for x in info.options: assert info.options[x] in [True, False] + if "uv" in info.options: + if info.options["uv"]: + self.testMC( + "Send MC request with uv set to true, expect SUCCESS", + cdh, + rp, + user, + key_params, + other={"options": {"uv": True}}, + expectedError=CtapError.ERR.SUCCESS, + ) + if "up" in info.options: + if info.options["up"]: + self.testMC( + "Send MC request with up set to true, expect INVALID_OPTION", + cdh, + rp, + user, + key_params, + other={"options": {"up": True}}, + expectedError=CtapError.ERR.INVALID_OPTION, + ) + + def test_make_credential(self,): + prev_reg = self.testMC( "Send MC request, expect success", cdh, @@ -488,61 +330,17 @@ class FIDO2Tests(Tester): key_params, expectedError=CtapError.ERR.SUCCESS, ) - - with Test("Check attestation format is correct"): - assert prev_reg.fmt in ["packed", "tpm", "android-key", "adroid-safetynet"] - - with Test("Check auth_data is at least 77 bytes"): - assert len(prev_reg.auth_data) >= 77 - allow_list = [ { "id": prev_reg.auth_data.credential_data.credential_id, "type": "public-key", } ] + with Test("Check attestation format is correct"): + assert prev_reg.fmt in ["packed", "tpm", "android-key", "adroid-safetynet"] - prev_auth = self.testGA( - "Send GA request, expect success", - rp["id"], - cdh, - allow_list, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Test auth_data is 37 bytes"): - assert len(prev_auth.auth_data) == 37 - - with Test("Test that auth_data.rpIdHash is correct"): - assert sha256(rp["id"].encode()) == prev_auth.auth_data.rp_id_hash - - with Test("Check that AT flag is not set"): - assert (prev_auth.auth_data.flags & 0xF8) == 0 - - with Test("Test that user, credential and numberOfCredentials are not present"): - assert prev_auth.user == None - assert prev_auth.number_of_credentials == None - - self.testGA( - "Send GA request with empty allow_list, expect NO_CREDENTIALS", - rp["id"], - cdh, - [], - expectedError=CtapError.ERR.NO_CREDENTIALS, - ) - - # apply bit flip - badid = list(prev_reg.auth_data.credential_data.credential_id[:]) - badid[len(badid) // 2] = badid[len(badid) // 2] ^ 1 - badid = bytes(badid) - - self.testGA( - "Send GA request with corrupt credId in allow_list, expect NO_CREDENTIALS", - rp["id"], - cdh, - [{"id": badid, "type": "public-key"}], - expectedError=CtapError.ERR.NO_CREDENTIALS, - ) + with Test("Check auth_data is at least 77 bytes"): + assert len(prev_reg.auth_data) >= 77 self.testMC( "Send MC request with missing clientDataHash, expect error", @@ -836,28 +634,77 @@ class FIDO2Tests(Tester): expectedError=CtapError.ERR.SUCCESS, ) - if "uv" in info.options: - if info.options["uv"]: - self.testMC( - "Send MC request with uv set to true, expect SUCCESS", - cdh, - rp, - user, - key_params, - other={"options": {"uv": True}}, - expectedError=CtapError.ERR.SUCCESS, - ) - if "up" in info.options: - if info.options["up"]: - self.testMC( - "Send MC request with up set to true, expect INVALID_OPTION", - cdh, - rp, - user, - key_params, - other={"options": {"up": True}}, - expectedError=CtapError.ERR.INVALID_OPTION, - ) + self.testReset() + + self.testGA( + "Send GA request with reset auth, expect NO_CREDENTIALS", + rp["id"], + cdh, + allow_list, + expectedError=CtapError.ERR.NO_CREDENTIALS, + ) + + def test_get_assertion(self,): + + self.testReset() + + prev_reg = self.testMC( + "Send MC request, expect success", + cdh, + rp, + user, + key_params, + expectedError=CtapError.ERR.SUCCESS, + ) + + allow_list = [ + { + "id": prev_reg.auth_data.credential_data.credential_id, + "type": "public-key", + } + ] + + prev_auth = self.testGA( + "Send GA request, expect success", + rp["id"], + cdh, + allow_list, + expectedError=CtapError.ERR.SUCCESS, + ) + + with Test("Test auth_data is 37 bytes"): + assert len(prev_auth.auth_data) == 37 + + with Test("Test that auth_data.rpIdHash is correct"): + assert sha256(rp["id"].encode()) == prev_auth.auth_data.rp_id_hash + + with Test("Check that AT flag is not set"): + assert (prev_auth.auth_data.flags & 0xF8) == 0 + + with Test("Test that user, credential and numberOfCredentials are not present"): + assert prev_auth.user == None + assert prev_auth.number_of_credentials == None + + self.testGA( + "Send GA request with empty allow_list, expect NO_CREDENTIALS", + rp["id"], + cdh, + [], + expectedError=CtapError.ERR.NO_CREDENTIALS, + ) + + # apply bit flip + badid = list(prev_reg.auth_data.credential_data.credential_id[:]) + badid[len(badid) // 2] = badid[len(badid) // 2] ^ 1 + badid = bytes(badid) + + self.testGA( + "Send GA request with corrupt credId in allow_list, expect NO_CREDENTIALS", + rp["id"], + cdh, + [{"id": badid, "type": "public-key"}], + expectedError=CtapError.ERR.NO_CREDENTIALS, + ) self.testGA( "Send GA request with missing RPID, expect MISSING_PARAMETER", @@ -911,6 +758,8 @@ class FIDO2Tests(Tester): other={"options": {"unknown": True}}, expectedError=CtapError.ERR.SUCCESS, ) + with Test("Get info"): + info = self.ctap.get_info() if "uv" in info.options: if info.options["uv"]: @@ -973,89 +822,81 @@ class FIDO2Tests(Tester): allow_list + [{"type": b"public-key"}], ) - self.testReset() + def test_rk(self, pin_code=None): - def testRk(pin_code=None): - self.testGA( - "Send GA request with reset auth, expect NO_CREDENTIALS", - rp["id"], - cdh, - allow_list, - expectedError=CtapError.ERR.NO_CREDENTIALS, - ) + pin_auth = None + if pin_code: + with Test("Set pin code"): + self.client.pin_protocol.set_pin(pin_code) + pin_token = self.client.pin_protocol.get_pin_token(pin_code) + pin_auth = hmac_sha256(pin_token, cdh)[:16] - pin_auth = None - if pin_code: - with Test("Set pin code"): - self.client.pin_protocol.set_pin(pin_code) - pin_token = self.client.pin_protocol.get_pin_token(pin_code) - pin_auth = hmac_sha256(pin_token, cdh)[:16] + self.testMC( + "Send MC request with rk option set to true, expect SUCCESS", + cdh, + rp, + user, + key_params, + other={"options": {"rk": True}, "pin_auth": pin_auth}, + expectedError=CtapError.ERR.SUCCESS, + ) + with Test("Get info"): + info = self.ctap.get_info() + + options = {"rk": True} + if "uv" in info.options and info.options["uv"]: + options["uv"] = False + + for i, x in enumerate([user1, user2, user3]): self.testMC( - "Send MC request with rk option set to true, expect SUCCESS", + "Send MC request with rk option set to true, expect SUCCESS %d/3" + % (i + 1), cdh, - rp, - user, + rp2, + x, key_params, - other={"options": {"rk": True}, "pin_auth": pin_auth}, - expectedError=CtapError.ERR.SUCCESS, - ) - - options = {"rk": True} - if "uv" in info.options and info.options["uv"]: - options["uv"] = False - - for i, x in enumerate([user1, user2, user3]): - self.testMC( - "Send MC request with rk option set to true, expect SUCCESS %d/3" - % (i + 1), - cdh, - rp2, - x, - key_params, - other={"options": options, "pin_auth": pin_auth}, - expectedError=CtapError.ERR.SUCCESS, - ) - - auth1 = self.testGA( - "Send GA request with no allow_list, expect SUCCESS", - rp2["id"], - cdh, other={"options": options, "pin_auth": pin_auth}, expectedError=CtapError.ERR.SUCCESS, ) - with Test("Check that there are 3 credentials returned"): - assert auth1.number_of_credentials == 3 + auth1 = self.testGA( + "Send GA request with no allow_list, expect SUCCESS", + rp2["id"], + cdh, + other={"options": options, "pin_auth": pin_auth}, + expectedError=CtapError.ERR.SUCCESS, + ) - with Test("Get the next 2 assertions"): - auth2 = self.ctap.get_next_assertion() - auth3 = self.ctap.get_next_assertion() + with Test("Check that there are 3 credentials returned"): + assert auth1.number_of_credentials == 3 - if not pin_code: - with Test("Check only the user ID was returned"): - assert "id" in auth1.user.keys() and len(auth1.user.keys()) == 1 - assert "id" in auth2.user.keys() and len(auth2.user.keys()) == 1 - assert "id" in auth3.user.keys() and len(auth3.user.keys()) == 1 - else: - with Test("Check that all user info was returned"): - for x in (auth1, auth2, auth3): - for y in ("name", "icon", "displayName", "id"): - assert y in x.user.keys() - assert len(x.user.keys()) == 4 + with Test("Get the next 2 assertions"): + auth2 = self.ctap.get_next_assertion() + auth3 = self.ctap.get_next_assertion() - with Test("Send an extra getNextAssertion request, expect error"): - try: - auth4 = self.ctap.get_next_assertion() - assert 0 - except CtapError as e: - print(e) + if not pin_code: + with Test("Check only the user ID was returned"): + assert "id" in auth1.user.keys() and len(auth1.user.keys()) == 1 + assert "id" in auth2.user.keys() and len(auth2.user.keys()) == 1 + assert "id" in auth3.user.keys() and len(auth3.user.keys()) == 1 + else: + with Test("Check that all user info was returned"): + for x in (auth1, auth2, auth3): + for y in ("name", "icon", "displayName", "id"): + assert y in x.user.keys() + assert len(x.user.keys()) == 4 - testRk(None) - # - # print("Assuming authenticator does NOT have a display.") + with Test("Send an extra getNextAssertion request, expect error"): + try: + auth4 = self.ctap.get_next_assertion() + assert 0 + except CtapError as e: + print(e) + + def test_client_pin(self,): pin1 = "1234567890" - testRk("1234567890") + self.test_rk(pin1) # PinProtocolV1 res = self.testCP( @@ -1116,14 +957,6 @@ class FIDO2Tests(Tester): self.client.pin_protocol.set_pin(pin1) self.testReset() - - # print("Setting pin code <4 bytes, expect POLICY_VIOLATION ") - # try: - # self.client.pin_protocol.set_pin("123") - # except CtapError as e: - # assert e.code == CtapError.ERR.POLICY_VIOLATION - # print("Pass") - with Test("Setting pin code >63 bytes, expect POLICY_VIOLATION "): try: self.client.pin_protocol.set_pin("A" * 64) @@ -1212,7 +1045,7 @@ class FIDO2Tests(Tester): expectedError=CtapError.ERR.PIN_AUTH_BLOCKED, ) - reboot() + self.reboot() with Test("Get pin_token, expect SUCCESS"): pin_token = self.client.pin_protocol.get_pin_token(pin1) @@ -1253,7 +1086,7 @@ class FIDO2Tests(Tester): assert res[3] == attempts if err == CtapError.ERR.PIN_AUTH_BLOCKED: - reboot() + self.reboot() res_mc = self.testMC( "Send MC request with correct pin_auth, expect PIN_BLOCKED", @@ -1265,7 +1098,7 @@ class FIDO2Tests(Tester): expectedError=CtapError.ERR.PIN_BLOCKED, ) - reboot() + self.reboot() self.testPP( "Get pin_token with correct pin code, expect PIN_BLOCKED", @@ -1273,79 +1106,94 @@ class FIDO2Tests(Tester): expectedError=CtapError.ERR.PIN_BLOCKED, ) + def test_fido2(self,): + + creds = [] + exclude_list = [] + + self.test_get_info() + + self.test_get_assertion() + + self.test_make_credential() + + self.test_rk(None) + + self.test_client_pin() + self.testReset() print("Done") - def test_rk(self,): - creds = [] - rp = {"id": self.host, "name": "ExaRP"} - - users = [ - {"id": b"user" + os.urandom(16), "name": "Username%d" % i} - for i in range(0, self.user_count) - ] - challenge = "Y2hhbGxlbmdl" - PIN = None - self.ctap.reset() - # if PIN: self.client.pin_protocol.set_pin(PIN) - - with Test("registering 1 user with RK"): - t1 = time.time() * 1000 - attest, data = self.client.make_credential( - rp, users[-1], challenge, pin=PIN, exclude_list=[], rk=True - ) - t2 = time.time() * 1000 - VerifyAttestation(attest, data) - creds.append(attest.auth_data.credential_data) - - with Test("1 assertion"): - t1 = time.time() * 1000 - assertions, client_data = self.client.get_assertion( - rp["id"], challenge, pin=PIN - ) - t2 = time.time() * 1000 - assertions[0].verify(client_data.hash, creds[0].public_key) - - with Test("registering %d users with RK" % len(users)): - for i in range(0, len(users) - 1): - t1 = time.time() * 1000 - attest, data = self.client.make_credential( - rp, users[i], challenge, pin=PIN, exclude_list=[], rk=True - ) - t2 = time.time() * 1000 - VerifyAttestation(attest, data) - creds.append(attest.auth_data.credential_data) - - t1 = time.time() * 1000 - assertions, client_data = self.client.get_assertion( - rp["id"], challenge, pin=PIN - ) - t2 = time.time() * 1000 - - print("Got %d assertions for %d users" % (len(assertions), len(users))) - assert len(assertions) == len(users) - - for x, y in zip(assertions, creds): - x.verify(client_data.hash, y.public_key) - - print("Assertion(s) valid (%d ms)" % (t2 - t1)) - - with Test("register a duplicate user "): - t1 = time.time() * 1000 - attest, data = self.client.make_credential( - rp, users[1], challenge, pin=PIN, exclude_list=[], rk=True - ) - t2 = time.time() * 1000 - VerifyAttestation(attest, data) - creds = creds[:2] + creds[3:] + [attest.auth_data.credential_data] - - t1 = time.time() * 1000 - assertions, client_data = self.client.get_assertion( - rp["id"], challenge, pin=PIN - ) - t2 = time.time() * 1000 - with Test("check %d assertions, %d users" % (len(assertions), len(users))): - assert len(assertions) == len(users) - for x, y in zip(assertions, creds): - x.verify(client_data.hash, y.public_key) + # def test_rk(self,): + # creds = [] + # rp = {"id": self.host, "name": "ExaRP"} + # + # users = [ + # {"id": b"user" + os.urandom(16), "name": "Username%d" % i} + # for i in range(0, self.user_count) + # ] + # challenge = "Y2hhbGxlbmdl" + # PIN = None + # self.ctap.reset() + # # if PIN: self.client.pin_protocol.set_pin(PIN) + # + # with Test("registering 1 user with RK"): + # t1 = time.time() * 1000 + # attest, data = self.client.make_credential( + # rp, users[-1], challenge, pin=PIN, exclude_list=[], rk=True + # ) + # t2 = time.time() * 1000 + # VerifyAttestation(attest, data) + # creds.append(attest.auth_data.credential_data) + # + # with Test("1 assertion"): + # t1 = time.time() * 1000 + # assertions, client_data = self.client.get_assertion( + # rp["id"], challenge, pin=PIN + # ) + # t2 = time.time() * 1000 + # assertions[0].verify(client_data.hash, creds[0].public_key) + # + # with Test("registering %d users with RK" % len(users)): + # for i in range(0, len(users) - 1): + # t1 = time.time() * 1000 + # attest, data = self.client.make_credential( + # rp, users[i], challenge, pin=PIN, exclude_list=[], rk=True + # ) + # t2 = time.time() * 1000 + # VerifyAttestation(attest, data) + # creds.append(attest.auth_data.credential_data) + # + # t1 = time.time() * 1000 + # assertions, client_data = self.client.get_assertion( + # rp["id"], challenge, pin=PIN + # ) + # t2 = time.time() * 1000 + # + # print("Got %d assertions for %d users" % (len(assertions), len(users))) + # assert len(assertions) == len(users) + # + # for x, y in zip(assertions, creds): + # x.verify(client_data.hash, y.public_key) + # + # print("Assertion(s) valid (%d ms)" % (t2 - t1)) + # + # with Test("register a duplicate user "): + # t1 = time.time() * 1000 + # attest, data = self.client.make_credential( + # rp, users[1], challenge, pin=PIN, exclude_list=[], rk=True + # ) + # t2 = time.time() * 1000 + # VerifyAttestation(attest, data) + # creds = creds[:2] + creds[3:] + [attest.auth_data.credential_data] + # + # t1 = time.time() * 1000 + # assertions, client_data = self.client.get_assertion( + # rp["id"], challenge, pin=PIN + # ) + # t2 = time.time() * 1000 + # with Test("check %d assertions, %d users" % (len(assertions), len(users))): + # assert len(assertions) == len(users) + # for x, y in zip(assertions, creds): + # x.verify(client_data.hash, y.public_key) diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index 83ab4a0..271f657 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -73,6 +73,16 @@ class Tester: def set_sim(self, b): self.is_sim = b + def reboot(self,): + if self.is_sim: + print("Sending restart command...") + self.send_magic_reboot() + self.delay(0.25) + else: + print("Please reboot authentictor and hit enter") + input() + self.find_device() + def send_data(self, cmd, data): if type(data) != type(b""): data = struct.pack("%dB" % len(data), *[ord(x) for x in data]) From 5076af1be4f0ddfefcec80123e5ca99238711d09 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 22 Mar 2019 01:22:55 -0400 Subject: [PATCH 4/7] works with yubikey5 --- tools/testing/main.py | 9 --- tools/testing/tests/fido2.py | 139 +++++++++++----------------------- tools/testing/tests/hid.py | 7 ++ tools/testing/tests/tester.py | 9 ++- 4 files changed, 60 insertions(+), 104 deletions(-) diff --git a/tools/testing/main.py b/tools/testing/main.py index 37c1e06..7e105e0 100644 --- a/tools/testing/main.py +++ b/tools/testing/main.py @@ -43,15 +43,6 @@ if __name__ == "__main__": # t.test_fido2() FIDO2Tests(t).run() - if "fido2-ext" in sys.argv: - pass - - if "rk" in sys.argv: - pass - - if "ping" in sys.argv: - pass - # hid tests are a bit invasive and should be done last if "hid" in sys.argv: HIDTests(t).run() diff --git a/tools/testing/tests/fido2.py b/tools/testing/tests/fido2.py index 2838e78..ac50378 100644 --- a/tools/testing/tests/fido2.py +++ b/tools/testing/tests/fido2.py @@ -8,7 +8,7 @@ import array, struct, socket from fido2.ctap import CtapError from fido2.ctap2 import ES256, PinProtocolV1 -from fido2.utils import Timeout, sha256, hmac_sha256 +from fido2.utils import sha256, hmac_sha256 from fido2.attestation import Attestation from cryptography.hazmat.backends import default_backend @@ -825,6 +825,10 @@ class FIDO2Tests(Tester): def test_rk(self, pin_code=None): pin_auth = None + if pin_code: + pin_protocol = 1 + else: + pin_protocol = None if pin_code: with Test("Set pin code"): self.client.pin_protocol.set_pin(pin_code) @@ -837,7 +841,11 @@ class FIDO2Tests(Tester): rp, user, key_params, - other={"options": {"rk": True}, "pin_auth": pin_auth}, + other={ + "options": {"rk": True}, + "pin_auth": pin_auth, + "pin_protocol": pin_protocol, + }, expectedError=CtapError.ERR.SUCCESS, ) @@ -856,7 +864,11 @@ class FIDO2Tests(Tester): rp2, x, key_params, - other={"options": options, "pin_auth": pin_auth}, + other={ + "options": options, + "pin_auth": pin_auth, + "pin_protocol": pin_protocol, + }, expectedError=CtapError.ERR.SUCCESS, ) @@ -864,7 +876,7 @@ class FIDO2Tests(Tester): "Send GA request with no allow_list, expect SUCCESS", rp2["id"], cdh, - other={"options": options, "pin_auth": pin_auth}, + other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, expectedError=CtapError.ERR.SUCCESS, ) @@ -884,8 +896,8 @@ class FIDO2Tests(Tester): with Test("Check that all user info was returned"): for x in (auth1, auth2, auth3): for y in ("name", "icon", "displayName", "id"): - assert y in x.user.keys() - assert len(x.user.keys()) == 4 + if y not in x.user.keys(): + print("FAIL: %s was not in user: " % y, x.user) with Test("Send an extra getNextAssertion request, expect error"): try: @@ -910,7 +922,8 @@ class FIDO2Tests(Tester): key = res[1] assert "Is public key" and key[1] == 2 assert "Is P256" and key[-1] == 1 - assert "Is right alg" and key[3] == -7 + if key[3] != -7: + print("WARNING: algorithm returned is not for ES256 (-7): ", key[3]) assert "Right key" and len(key[-3]) == 32 and type(key[-3]) == type(bytes()) with Test("Test setting a new pin"): @@ -927,7 +940,7 @@ class FIDO2Tests(Tester): rp, user, key_params, - other={"pin_auth": pin_auth}, + other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, expectedError=CtapError.ERR.SUCCESS, ) @@ -935,7 +948,21 @@ class FIDO2Tests(Tester): assert res_mc.auth_data.flags & (1 << 2) res_ga = self.testGA( - "Send GA request with no allow_list, expect SUCCESS", + "Send GA request with pinAuth, expect SUCCESS", + rp["id"], + cdh, + [ + { + "type": "public-key", + "id": res_mc.auth_data.credential_data.credential_id, + } + ], + other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, + expectedError=CtapError.ERR.SUCCESS, + ) + + self.testGA( + "Send GA request with no pinAuth, expect SUCCESS", rp["id"], cdh, [ @@ -944,7 +971,6 @@ class FIDO2Tests(Tester): "id": res_mc.auth_data.credential_data.credential_id, } ], - other={"pin_auth": pin_auth}, expectedError=CtapError.ERR.SUCCESS, ) @@ -1004,10 +1030,10 @@ class FIDO2Tests(Tester): ) res_mc = self.testGA( - "Send GA request with no pin_auth, expect PIN_REQUIRED", + "Send GA request with no pin_auth, expect NO_CREDENTIALS", rp["id"], cdh, - expectedError=CtapError.ERR.PIN_REQUIRED, + expectedError=CtapError.ERR.NO_CREDENTIALS, ) res = self.testCP( @@ -1057,7 +1083,7 @@ class FIDO2Tests(Tester): rp, user, key_params, - other={"pin_auth": pin_auth}, + other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, expectedError=CtapError.ERR.SUCCESS, ) @@ -1069,8 +1095,8 @@ class FIDO2Tests(Tester): err = CtapError.ERR.PIN_INVALID if i in (3, 6): err = CtapError.ERR.PIN_AUTH_BLOCKED - elif i >= 9: - err = CtapError.ERR.PIN_BLOCKED + elif i >= 8: + err = [CtapError.ERR.PIN_BLOCKED, CtapError.ERR.PIN_AUTH_BLOCKED] self.testPP( "Lock out authentictor and check correct error codes %d/9" % i, pin_wrong, @@ -1089,13 +1115,12 @@ class FIDO2Tests(Tester): self.reboot() res_mc = self.testMC( - "Send MC request with correct pin_auth, expect PIN_BLOCKED", + "Send MC request with correct pin_auth, expect error", cdh, rp, user, key_params, - other={"pin_auth": pin_auth}, - expectedError=CtapError.ERR.PIN_BLOCKED, + other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, ) self.reboot() @@ -1108,8 +1133,7 @@ class FIDO2Tests(Tester): def test_fido2(self,): - creds = [] - exclude_list = [] + self.testReset() self.test_get_info() @@ -1123,77 +1147,6 @@ class FIDO2Tests(Tester): self.testReset() - print("Done") + self.test_extensions() - # def test_rk(self,): - # creds = [] - # rp = {"id": self.host, "name": "ExaRP"} - # - # users = [ - # {"id": b"user" + os.urandom(16), "name": "Username%d" % i} - # for i in range(0, self.user_count) - # ] - # challenge = "Y2hhbGxlbmdl" - # PIN = None - # self.ctap.reset() - # # if PIN: self.client.pin_protocol.set_pin(PIN) - # - # with Test("registering 1 user with RK"): - # t1 = time.time() * 1000 - # attest, data = self.client.make_credential( - # rp, users[-1], challenge, pin=PIN, exclude_list=[], rk=True - # ) - # t2 = time.time() * 1000 - # VerifyAttestation(attest, data) - # creds.append(attest.auth_data.credential_data) - # - # with Test("1 assertion"): - # t1 = time.time() * 1000 - # assertions, client_data = self.client.get_assertion( - # rp["id"], challenge, pin=PIN - # ) - # t2 = time.time() * 1000 - # assertions[0].verify(client_data.hash, creds[0].public_key) - # - # with Test("registering %d users with RK" % len(users)): - # for i in range(0, len(users) - 1): - # t1 = time.time() * 1000 - # attest, data = self.client.make_credential( - # rp, users[i], challenge, pin=PIN, exclude_list=[], rk=True - # ) - # t2 = time.time() * 1000 - # VerifyAttestation(attest, data) - # creds.append(attest.auth_data.credential_data) - # - # t1 = time.time() * 1000 - # assertions, client_data = self.client.get_assertion( - # rp["id"], challenge, pin=PIN - # ) - # t2 = time.time() * 1000 - # - # print("Got %d assertions for %d users" % (len(assertions), len(users))) - # assert len(assertions) == len(users) - # - # for x, y in zip(assertions, creds): - # x.verify(client_data.hash, y.public_key) - # - # print("Assertion(s) valid (%d ms)" % (t2 - t1)) - # - # with Test("register a duplicate user "): - # t1 = time.time() * 1000 - # attest, data = self.client.make_credential( - # rp, users[1], challenge, pin=PIN, exclude_list=[], rk=True - # ) - # t2 = time.time() * 1000 - # VerifyAttestation(attest, data) - # creds = creds[:2] + creds[3:] + [attest.auth_data.credential_data] - # - # t1 = time.time() * 1000 - # assertions, client_data = self.client.get_assertion( - # rp["id"], challenge, pin=PIN - # ) - # t2 = time.time() * 1000 - # with Test("check %d assertions, %d users" % (len(assertions), len(users))): - # assert len(assertions) == len(users) - # for x, y in zip(assertions, creds): - # x.verify(client_data.hash, y.public_key) + print("Done") diff --git a/tools/testing/tests/hid.py b/tools/testing/tests/hid.py index 89a9e70..4ad3f6c 100644 --- a/tools/testing/tests/hid.py +++ b/tools/testing/tests/hid.py @@ -1,3 +1,9 @@ +import sys, struct, os, time +from binascii import hexlify + +from fido2.hid import CtapHidDevice, CTAPHID +from fido2.ctap import CtapError + from .tester import Tester, Test @@ -10,6 +16,7 @@ class HIDTests(Tester): self.check_timeouts = en def run(self,): + self.test_long_ping() self.test_hid(self.check_timeouts) def test_long_ping(self): diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index 271f657..0cce014 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -1,8 +1,10 @@ -import time +import time, struct from fido2.hid import CtapHidDevice, CTAPHID from fido2.client import Fido2Client, ClientError from fido2.ctap1 import CTAP1, ApduError, APDU +from fido2.utils import Timeout + from fido2.ctap import CtapError @@ -153,7 +155,10 @@ class Tester: raise RuntimeError("Expected error to occur for test: %s" % test) except CtapError as e: if expectedError is not None: - if e.code != expectedError: + cond = e.code != expectedError + if type(expectedError) == type([]): + cond = e.code not in expectedError + if cond: raise RuntimeError( "Got error code 0x%x, expected %x" % (e.code, expectedError) ) From d97942032496646291d5fc213dae0ff0b03c8bff Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Fri, 22 Mar 2019 01:59:19 -0400 Subject: [PATCH 5/7] u2f work with yubikey5 --- tools/testing/tests/u2f.py | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/tools/testing/tests/u2f.py b/tools/testing/tests/u2f.py index 1d0b817..87d8799 100644 --- a/tools/testing/tests/u2f.py +++ b/tools/testing/tests/u2f.py @@ -1,5 +1,6 @@ from fido2.ctap1 import CTAP1, ApduError, APDU from fido2.utils import sha256 +from fido2.client import _call_polling from .tester import Tester, Test @@ -11,6 +12,23 @@ class U2FTests(Tester): def run(self,): self.test_u2f() + def register(self, chal, appid): + reg_data = _call_polling(0.25, None, None, self.ctap1.register, chal, appid) + return reg_data + + def authenticate(self, chal, appid, key_handle, check_only=False): + auth_data = _call_polling( + 0.25, + None, + None, + self.ctap1.authenticate, + chal, + appid, + key_handle, + check_only=check_only, + ) + return auth_data + def test_u2f(self,): chal = sha256(b"AAA") appid = sha256(b"BBB") @@ -37,9 +55,9 @@ class U2FTests(Tester): with Test( "U2F reg + auth %d/%d (count: %02x)" % (i + 1, self.user_count, lastc) ): - reg = self.ctap1.register(chal, appid) + reg = self.register(chal, appid) reg.verify(appid, chal) - auth = self.ctap1.authenticate(chal, appid, reg.key_handle) + auth = self.authenticate(chal, appid, reg.key_handle) auth.verify(appid, chal, reg.public_key) regs.append(reg) @@ -55,7 +73,7 @@ class U2FTests(Tester): with Test( "Checking previous registration %d/%d" % (i + 1, self.user_count) ): - auth = self.ctap1.authenticate(chal, appid, regs[i].key_handle) + auth = self.authenticate(chal, appid, regs[i].key_handle) auth.verify(appid, chal, regs[i].public_key) print("Check that all previous credentials are registered...") From d2091563abc0c77b15931a92ce7094e1aca8be04 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Tue, 26 Mar 2019 16:09:30 -0400 Subject: [PATCH 6/7] fix code quality issues --- tools/testing/tests/fido2.py | 105 ++++++++-------------------------- tools/testing/tests/hid.py | 12 ++-- tools/testing/tests/solo.py | 8 +-- tools/testing/tests/tester.py | 31 +++++----- tools/testing/tests/u2f.py | 2 +- tools/testing/tests/util.py | 6 +- 6 files changed, 55 insertions(+), 109 deletions(-) diff --git a/tools/testing/tests/fido2.py b/tools/testing/tests/fido2.py index ac50378..105e279 100644 --- a/tools/testing/tests/fido2.py +++ b/tools/testing/tests/fido2.py @@ -1,8 +1,8 @@ from __future__ import print_function, absolute_import, unicode_literals -import sys, os, time +import sys +import time from random import randint -from binascii import hexlify -import array, struct, socket +import array from fido2.ctap import CtapError @@ -73,62 +73,7 @@ class FIDO2Tests(Tester): print("Assertion time: %d ms" % (t2 - t1)) - def test_fido2_brute_force(self): - creds = [] - exclude_list = [] - PIN = None - abc = "abcdefghijklnmopqrstuvwxyz" - abc += abc.upper() - - self.ctap.reset() - - for i in range(0, 2048 ** 2): - creds = [] - - challenge = "".join([abc[randint(0, len(abc) - 1)] for x in range(0, 32)]) - - fake_id1 = array.array( - "B", [randint(0, 255) for i in range(0, 150)] - ).tobytes() - fake_id2 = array.array( - "B", [randint(0, 255) for i in range(0, 73)] - ).tobytes() - - exclude_list.append({"id": fake_id1, "type": "public-key"}) - exclude_list.append({"id": fake_id2, "type": "public-key"}) - - # for i in range(0,2048**2): - for i in range(0, 1): - t1 = time.time() * 1000 - attest, data = self.client.make_credential( - rp, user, challenge, pin=PIN, exclude_list=[] - ) - print(attest.auth_data.counter) - t2 = time.time() * 1000 - VerifyAttestation(attest, data) - print("Register valid (%d ms)" % (t2 - t1)) - sys.stdout.flush() - - cred = attest.auth_data.credential_data - creds.append(cred) - - # for i in range(0,2048**2): - for i in range(0, 1): - allow_list = [{"id": creds[0].credential_id, "type": "public-key"}] - t1 = time.time() * 1000 - assertions, client_data = self.client.get_assertion( - rp["id"], challenge, allow_list, pin=PIN - ) - t2 = time.time() * 1000 - assertions[0].verify(client_data.hash, creds[0].public_key) - print(assertions[0].auth_data.counter) - - print("Assertion valid (%d ms)" % (t2 - t1)) - sys.stdout.flush() - def test_extensions(self,): - creds = [] - exclude_list = [] salt1 = b"\x5a" * 32 salt2 = b"\x96" * 32 @@ -208,7 +153,7 @@ class FIDO2Tests(Tester): ext = auth.auth_data.extensions assert ext assert "hmac-secret" in ext - assert type(ext["hmac-secret"]) == type(b"") + assert isinstance(ext["hmac-secret"], bytes) assert len(ext["hmac-secret"]) == len(salt_list) * 32 with Test("Check that shannon_entropy of hmac-secret is good"): @@ -901,7 +846,7 @@ class FIDO2Tests(Tester): with Test("Send an extra getNextAssertion request, expect error"): try: - auth4 = self.ctap.get_next_assertion() + self.ctap.get_next_assertion() assert 0 except CtapError as e: print(e) @@ -924,7 +869,7 @@ class FIDO2Tests(Tester): assert "Is P256" and key[-1] == 1 if key[3] != -7: print("WARNING: algorithm returned is not for ES256 (-7): ", key[3]) - assert "Right key" and len(key[-3]) == 32 and type(key[-3]) == type(bytes()) + assert "Right key" and len(key[-3]) == 32 and isinstance(key[-3], bytes) with Test("Test setting a new pin"): pin2 = "qwertyuiop\x11\x22\x33\x00123" @@ -961,18 +906,18 @@ class FIDO2Tests(Tester): expectedError=CtapError.ERR.SUCCESS, ) - self.testGA( - "Send GA request with no pinAuth, expect SUCCESS", - rp["id"], - cdh, - [ - { - "type": "public-key", - "id": res_mc.auth_data.credential_data.credential_id, - } - ], - expectedError=CtapError.ERR.SUCCESS, - ) + # self.testGA( + # "Send GA request with no pinAuth, expect SUCCESS", + # rp["id"], + # cdh, + # [ + # { + # "type": "public-key", + # "id": res_mc.auth_data.credential_data.credential_id, + # } + # ], + # expectedError=CtapError.ERR.SUCCESS, + # ) with Test("Check UV flag is set"): assert res_ga.auth_data.flags & (1 << 2) @@ -1029,12 +974,12 @@ class FIDO2Tests(Tester): expectedError=CtapError.ERR.PIN_REQUIRED, ) - res_mc = self.testGA( - "Send GA request with no pin_auth, expect NO_CREDENTIALS", - rp["id"], - cdh, - expectedError=CtapError.ERR.NO_CREDENTIALS, - ) + # res_mc = self.testGA( + # "Send GA request with no pin_auth, expect NO_CREDENTIALS", + # rp["id"], + # cdh, + # expectedError=CtapError.ERR.NO_CREDENTIALS, + # ) res = self.testCP( "Test getRetries, expect SUCCESS", @@ -1096,7 +1041,7 @@ class FIDO2Tests(Tester): if i in (3, 6): err = CtapError.ERR.PIN_AUTH_BLOCKED elif i >= 8: - err = [CtapError.ERR.PIN_BLOCKED, CtapError.ERR.PIN_AUTH_BLOCKED] + err = [CtapError.ERR.PIN_BLOCKED, CtapError.ERR.PIN_INVALID] self.testPP( "Lock out authentictor and check correct error codes %d/9" % i, pin_wrong, diff --git a/tools/testing/tests/hid.py b/tools/testing/tests/hid.py index 4ad3f6c..74d9d92 100644 --- a/tools/testing/tests/hid.py +++ b/tools/testing/tests/hid.py @@ -1,7 +1,7 @@ -import sys, struct, os, time +import sys, os, time from binascii import hexlify -from fido2.hid import CtapHidDevice, CTAPHID +from fido2.hid import CTAPHID from fido2.ctap import CtapError from .tester import Tester, Test @@ -34,7 +34,7 @@ class HIDTests(Tester): raise RuntimeError("Fob is too slow (%d ms)" % delt) if r != pingdata: raise ValueError("Ping data not echo'd") - except CtapError as e: + except CtapError: raise RuntimeError("ping failed") sys.stdout.flush() @@ -95,7 +95,7 @@ class HIDTests(Tester): with Test("Sending packet with too large of a length."): self.send_raw("\x81\x1d\xba\x00") cmd, resp = self.recv_raw() - self.check_error(resp, CtapError.ERR.INVALID_LENGTH) + Tester.check_error(resp, CtapError.ERR.INVALID_LENGTH) r = self.send_data(CTAPHID.PING, "\x44" * 200) with Test("Sending packets that skip a sequence number."): @@ -105,7 +105,7 @@ class HIDTests(Tester): # skip 2 self.send_raw("\x03") cmd, resp = self.recv_raw() - self.check_error(resp, CtapError.ERR.INVALID_SEQ) + Tester.check_error(resp, CtapError.ERR.INVALID_SEQ) with Test("Resync and send ping"): try: @@ -207,7 +207,7 @@ class HIDTests(Tester): self.set_cid(cid2) # send ping on 2nd channel self.send_raw("\x81\x00\x63") - self.delay(0.1) + Tester.delay(0.1) self.send_raw("\x00") cmd, r = self.recv_raw() # busy response diff --git a/tools/testing/tests/solo.py b/tools/testing/tests/solo.py index 518389a..e59421e 100644 --- a/tools/testing/tests/solo.py +++ b/tools/testing/tests/solo.py @@ -1,6 +1,6 @@ from solo.client import SoloClient -from fido2.ctap1 import ApduError, APDU +from fido2.ctap1 import ApduError from .util import shannon_entropy from .tester import Tester, Test @@ -30,9 +30,9 @@ class SoloTests(Tester): entropy += sc.get_rng() with Test("Test entropy is close to perfect"): - sum = shannon_entropy(entropy) - assert sum > 7.98 - print("Entropy is %.5f bits per byte." % sum) + s = shannon_entropy(entropy) + assert s > 7.98 + print("Entropy is %.5f bits per byte." % s) with Test("Test Solo version command"): assert len(sc.solo_version()) == 3 diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index 0cce014..09d1e8d 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -1,8 +1,8 @@ import time, struct -from fido2.hid import CtapHidDevice, CTAPHID -from fido2.client import Fido2Client, ClientError -from fido2.ctap1 import CTAP1, ApduError, APDU +from fido2.hid import CtapHidDevice +from fido2.client import Fido2Client +from fido2.ctap1 import CTAP1, ApduError from fido2.utils import Timeout from fido2.ctap import CtapError @@ -17,7 +17,6 @@ def ForceU2F(client, device): class Packet(object): def __init__(self, data): - l = len(data) self.data = data def ToWireFormat(self,): @@ -79,14 +78,14 @@ class Tester: if self.is_sim: print("Sending restart command...") self.send_magic_reboot() - self.delay(0.25) + Tester.delay(0.25) else: print("Please reboot authentictor and hit enter") input() self.find_device() def send_data(self, cmd, data): - if type(data) != type(b""): + if not isinstance(data, bytes): data = struct.pack("%dB" % len(data), *[ord(x) for x in data]) with Timeout(1.0) as event: return self.dev.call(cmd, data, event) @@ -94,9 +93,9 @@ class Tester: def send_raw(self, data, cid=None): if cid is None: cid = self.dev._dev.cid - elif type(cid) != type(b""): + elif not isinstance(cid, bytes): cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid]) - if type(data) != type(b""): + if not isinstance(data, bytes): data = struct.pack("%dB" % len(data), *[ord(x) for x in data]) data = cid + data l = len(data) @@ -127,16 +126,16 @@ class Tester: return self.dev._dev.cid def set_cid(self, cid): - if type(cid) not in [type(b""), type(bytearray())]: + if not isinstance(cid, (bytes, bytearray)): cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid]) self.dev._dev.cid = cid def recv_raw(self,): - with Timeout(1.0) as t: + with Timeout(1.0): cmd, payload = self.dev._dev.InternalRecv() return cmd, payload - def check_error(self, data, err=None): + def check_error(data, err=None): assert len(data) == 1 if err is None: if data[0] != 0: @@ -156,11 +155,13 @@ class Tester: except CtapError as e: if expectedError is not None: cond = e.code != expectedError - if type(expectedError) == type([]): + if isinstance(expectedError, list): cond = e.code not in expectedError + else: + expectedError = [expectedError] if cond: raise RuntimeError( - "Got error code 0x%x, expected %x" % (e.code, expectedError) + f"Got error code {hex(e.code)}, expected {[hex(x) for x in expectedError]}" ) else: print(e) @@ -170,7 +171,7 @@ class Tester: print("Resetting Authenticator...") try: self.ctap.reset() - except CtapError as e: + except CtapError: # Some authenticators need a power cycle print("You must power cycle authentictor. Hit enter when done.") input() @@ -192,5 +193,5 @@ class Tester: self.client.pin_protocol.get_pin_token, test, *args, **kwargs ) - def delay(self, secs): + def delay(secs): time.sleep(secs) diff --git a/tools/testing/tests/u2f.py b/tools/testing/tests/u2f.py index 87d8799..847769b 100644 --- a/tools/testing/tests/u2f.py +++ b/tools/testing/tests/u2f.py @@ -41,7 +41,7 @@ class U2FTests(Tester): with Test("Check bad INS"): try: - res = self.ctap1.send_apdu(0, 0, 0, 0, b"") + self.ctap1.send_apdu(0, 0, 0, 0, b"") except ApduError as e: assert e.code == 0x6D00 diff --git a/tools/testing/tests/util.py b/tools/testing/tests/util.py index d6b07ae..94c3c45 100644 --- a/tools/testing/tests/util.py +++ b/tools/testing/tests/util.py @@ -2,11 +2,11 @@ import math def shannon_entropy(data): - sum = 0.0 + s = 0.0 total = len(data) for x in range(0, 256): freq = data.count(x) p = freq / total if p > 0: - sum -= p * math.log2(p) - return sum + s -= p * math.log2(p) + return s From 08e236df69b6c9308b519ba09c12194cf3cd62c2 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Tue, 26 Mar 2019 16:14:28 -0400 Subject: [PATCH 7/7] fix code quality issues x/2 --- tools/testing/tests/fido2.py | 1 - tools/testing/tests/tester.py | 2 +- tools/testing/tests/u2f.py | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/tools/testing/tests/fido2.py b/tools/testing/tests/fido2.py index 105e279..999f7d5 100644 --- a/tools/testing/tests/fido2.py +++ b/tools/testing/tests/fido2.py @@ -1,5 +1,4 @@ from __future__ import print_function, absolute_import, unicode_literals -import sys import time from random import randint import array diff --git a/tools/testing/tests/tester.py b/tools/testing/tests/tester.py index 09d1e8d..e0ae813 100644 --- a/tools/testing/tests/tester.py +++ b/tools/testing/tests/tester.py @@ -2,7 +2,7 @@ import time, struct from fido2.hid import CtapHidDevice from fido2.client import Fido2Client -from fido2.ctap1 import CTAP1, ApduError +from fido2.ctap1 import CTAP1 from fido2.utils import Timeout from fido2.ctap import CtapError diff --git a/tools/testing/tests/u2f.py b/tools/testing/tests/u2f.py index 847769b..d1e8033 100644 --- a/tools/testing/tests/u2f.py +++ b/tools/testing/tests/u2f.py @@ -47,7 +47,7 @@ class U2FTests(Tester): with Test("Check bad CLA"): try: - res = self.ctap1.send_apdu(1, CTAP1.INS.VERSION, 0, 0, b"abc") + self.ctap1.send_apdu(1, CTAP1.INS.VERSION, 0, 0, b"abc") except ApduError as e: assert e.code == 0x6E00