diff --git a/README.md b/README.md index b243ea1..f3b61e3 100644 --- a/README.md +++ b/README.md @@ -94,10 +94,7 @@ Run the Solo application: ./main ``` -In another shell, you can run client software, for example our tests: -```bash -python tools/ctap_test.py sim fido2 -``` +In another shell, you can run our [test suite](https://github.com/solokeys/fido2-tests). You can find more details in our [documentation](https://docs.solokeys.io/solo/), including how to build on the the NUCLEO-L432KC development board. diff --git a/mkdocs.yml b/mkdocs.yml index b140e92..2cfc0c9 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -11,6 +11,7 @@ nav: - FIDO2 Implementation: solo/fido2-impl.md - Metadata Statements: solo/metadata-statements.md - Build instructions: solo/building.md + - Running on Nucleo32 board: solo/nucleo32-board.md - Signed update process: solo/signed-updates.md - Code documentation: solo/code-overview.md - Contributing Code: solo/contributing.md diff --git a/tools/testing/main.py b/tools/testing/main.py deleted file mode 100644 index 437e938..0000000 --- a/tools/testing/main.py +++ /dev/null @@ -1,64 +0,0 @@ -#!/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] [nfc] <[u2f]|[fido2]|[rk]|[hid]|[ping]>") - print(" sim - test via UDP simulation backend only") - print(" nfc - test via NFC interface only") - 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) - - nfcOnly = False - if "nfc" in sys.argv: - nfcOnly = True - - t.find_device(nfcOnly) - - 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() - - # 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 deleted file mode 100644 index 23cf5a8..0000000 --- a/tools/testing/tests/__init__.py +++ /dev/null @@ -1,11 +0,0 @@ -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/testing/tests/fido2.py b/tools/testing/tests/fido2.py deleted file mode 100644 index dc76df3..0000000 --- a/tools/testing/tests/fido2.py +++ /dev/null @@ -1,1334 +0,0 @@ -from __future__ import print_function, absolute_import, unicode_literals -import time -from random import randint -import array -from functools import cmp_to_key - -from fido2 import cbor -from fido2.ctap import CtapError - -from fido2.ctap2 import ES256, PinProtocolV1, AttestedCredentialData -from fido2.utils import sha256, hmac_sha256 -from fido2.attestation import Attestation - -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes - -from .u2f import U2FTests -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) - verifier().verify(attest.att_statement, attest.auth_data, data.hash) - - -def cbor_key_to_representative(key): - if isinstance(key, int): - if key >= 0: - return (0, key) - return (1, -key) - elif isinstance(key, bytes): - return (2, key) - elif isinstance(key, str): - return (3, key) - else: - raise ValueError(key) - - -def cbor_str_cmp(a, b): - if isinstance(a, str) or isinstance(b, str): - a = a.encode("utf8") - b = b.encode("utf8") - - if len(a) == len(b): - for x, y in zip(a, b): - if x != y: - return x - y - return 0 - else: - return len(a) - len(b) - - -def cmp_cbor_keys(a, b): - a = cbor_key_to_representative(a) - b = cbor_key_to_representative(b) - if a[0] != b[0]: - return a[0] - b[0] - if a[0] in (2, 3): - return cbor_str_cmp(a[1], b[1]) - else: - return (a[1] > b[1]) - (a[1] < b[1]) - - -def TestCborKeysSorted(cbor_obj): - # Cbor canonical ordering of keys. - # https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#ctap2-canonical-cbor-encoding-form - - if isinstance(cbor_obj, bytes): - cbor_obj = cbor.decode_from(cbor_obj)[0] - - if isinstance(cbor_obj, dict): - l = [x for x in cbor_obj] - else: - l = cbor_obj - - l_sorted = sorted(l[:], key=cmp_to_key(cmp_cbor_keys)) - - for i in range(len(l)): - - if not isinstance(l[i], (str, int)): - raise ValueError(f"Cbor map key {l[i]} must be int or str for CTAP2") - - if l[i] != l_sorted[i]: - raise ValueError(f"Cbor map item {i}: {l[i]} is out of order") - - return l - - -# hot patch cbor map parsing to test the order of keys in map -_load_map_old = cbor.load_map - - -def _load_map_new(ai, data): - values, data = _load_map_old(ai, data) - TestCborKeysSorted(values) - return values, data - - -cbor.load_map = _load_map_new -cbor._DESERIALIZERS[5] = _load_map_new - - -class FIDO2Tests(Tester): - def __init__(self, tester=None): - super().__init__(tester) - self.self_test() - - def self_test(self,): - cbor_key_list_sorted = [ - 0, - 1, - 1, - 2, - 3, - -1, - -2, - "b", - "c", - "aa", - "aaa", - "aab", - "baa", - "bbb", - ] - with Test("Self test CBOR sorting"): - TestCborKeysSorted(cbor_key_list_sorted) - - with Test("Self test CBOR sorting integers", catch=ValueError): - TestCborKeysSorted([1, 0]) - - with Test("Self test CBOR sorting major type", catch=ValueError): - TestCborKeysSorted([-1, 0]) - - with Test("Self test CBOR sorting strings", catch=ValueError): - TestCborKeysSorted(["bb", "a"]) - - with Test("Self test CBOR sorting same length strings", catch=ValueError): - TestCborKeysSorted(["ab", "aa"]) - - def run(self,): - self.test_fido2() - - def test_fido2_simple(self, pin_token=None): - creds = [] - exclude_list = [] - PIN = pin_token - - 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"}) - - t1 = time.time() * 1000 - attest, data = self.client.make_credential( - rp, user, challenge, pin=PIN, exclude_list=[] - ) - t2 = time.time() * 1000 - VerifyAttestation(attest, data) - print("Register time: %d ms" % (t2 - t1)) - - cred = attest.auth_data.credential_data - creds.append(cred) - - 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("Assertion time: %d ms" % (t2 - t1)) - - def test_extensions(self,): - - salt1 = b"\x5a" * 32 - salt2 = b"\x96" * 32 - salt3 = b"\x03" * 32 - - # self.testReset() - - with Test("Get info has hmac-secret"): - info = self.ctap.get_info() - assert "hmac-secret" in info.extensions - - reg = self.testMC( - "Send MC with hmac-secret ext set to true, expect SUCCESS", - cdh, - rp, - user, - key_params, - expectedError=CtapError.ERR.SUCCESS, - other={"extensions": {"hmac-secret": True}, "options": {"rk": True}}, - ) - - with Test("Check 'hmac-secret' is set to true in auth_data extensions"): - assert reg.auth_data.extensions - assert "hmac-secret" in reg.auth_data.extensions - assert reg.auth_data.extensions["hmac-secret"] == True - - self.testMC( - "Send MC with fake extension set to true, expect SUCCESS", - cdh, - rp, - user, - key_params, - expectedError=CtapError.ERR.SUCCESS, - other={"extensions": {"tetris": True}}, - ) - - with Test("Get shared secret"): - key_agreement, shared_secret = self.client.pin_protocol.get_shared_secret() - cipher = Cipher( - algorithms.AES(shared_secret), - modes.CBC(b"\x00" * 16), - default_backend(), - ) - - def get_salt_params(salts): - enc = cipher.encryptor() - salt_enc = b"" - for salt in salts: - salt_enc += enc.update(salt) - salt_enc += enc.finalize() - - salt_auth = hmac_sha256(shared_secret, salt_enc)[:16] - return salt_enc, salt_auth - - for salt_list in ((salt1,), (salt1, salt2)): - salt_enc, salt_auth = get_salt_params(salt_list) - - auth = self.testGA( - "Send GA request with %d salts hmac-secret, expect success" - % len(salt_list), - rp["id"], - cdh, - other={ - "extensions": { - "hmac-secret": {1: key_agreement, 2: salt_enc, 3: salt_auth} - } - }, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test( - "Check that hmac-secret is in auth_data extensions and has %d bytes" - % (len(salt_list) * 32) - ): - ext = auth.auth_data.extensions - assert ext - assert "hmac-secret" in ext - 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"): - ext = auth.auth_data.extensions - dec = cipher.decryptor() - key = dec.update(ext["hmac-secret"]) + dec.finalize() - - print(shannon_entropy(ext["hmac-secret"])) - if len(salt_list) == 1: - assert shannon_entropy(ext["hmac-secret"]) > 4.6 - assert shannon_entropy(key) > 4.6 - if len(salt_list) == 2: - assert shannon_entropy(ext["hmac-secret"]) > 5.4 - assert shannon_entropy(key) > 5.4 - - with Test("Check that the assertion is valid"): - credential_data = AttestedCredentialData(reg.auth_data.credential_data) - auth.verify(cdh, credential_data.public_key) - - salt_enc, salt_auth = get_salt_params((salt3,)) - - auth = self.testGA( - "Send GA request with hmac-secret missing keyAgreement, expect error", - rp["id"], - cdh, - other={"extensions": {"hmac-secret": {2: salt_enc, 3: salt_auth}}}, - ) - auth = self.testGA( - "Send GA request with hmac-secret missing saltAuth, expect MISSING_PARAMETER", - rp["id"], - cdh, - other={"extensions": {"hmac-secret": {1: key_agreement, 2: salt_enc}}}, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - auth = self.testGA( - "Send GA request with hmac-secret missing saltEnc, expect MISSING_PARAMETER", - rp["id"], - cdh, - other={"extensions": {"hmac-secret": {1: key_agreement, 3: salt_auth}}}, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - bad_auth = list(salt_auth[:]) - bad_auth[len(bad_auth) // 2] = bad_auth[len(bad_auth) // 2] ^ 1 - bad_auth = bytes(bad_auth) - - auth = self.testGA( - "Send GA request with hmac-secret containing bad saltAuth, expect EXTENSION_FIRST", - rp["id"], - cdh, - other={ - "extensions": { - "hmac-secret": {1: key_agreement, 2: salt_enc, 3: bad_auth} - } - }, - expectedError=CtapError.ERR.EXTENSION_FIRST, - ) - - salt4 = b"\x5a" * 16 - salt5 = b"\x96" * 64 - for salt_list in ((salt4,), (salt4, salt5)): - salt_enc, salt_auth = get_salt_params(salt_list) - - salt_auth = hmac_sha256(shared_secret, salt_enc)[:16] - auth = self.testGA( - "Send GA request with incorrect salt length %d, expect INVALID_LENGTH" - % len(salt_enc), - rp["id"], - cdh, - other={ - "extensions": { - "hmac-secret": {1: key_agreement, 2: salt_enc, 3: salt_auth} - } - }, - expectedError=CtapError.ERR.INVALID_LENGTH, - ) - - def test_get_info(self,): - with Test("Get info"): - info = self.ctap.get_info() - print("data:", bytes(info)) - print("decoded:", cbor.decode_from(bytes(info))) - - with Test("Check FIDO2 string is in VERSIONS field"): - assert "FIDO_2_0" in info.versions - - with Test("Check pin protocols field"): - if len(info.pin_protocols): - assert sum(info.pin_protocols) > 0 - - with Test("Check options field"): - 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, - rp, - user, - key_params, - expectedError=CtapError.ERR.SUCCESS, - ) - - 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"] - - 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", - None, - rp, - user, - key_params, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testMC( - "Send MC request with integer for clientDataHash, expect error", - 5, - rp, - user, - key_params, - ) - - self.testMC( - "Send MC request with missing user, expect error", - cdh, - rp, - None, - key_params, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testMC( - "Send MC request with bytearray user, expect error", - cdh, - rp, - b"1234abcd", - key_params, - ) - - self.testMC( - "Send MC request with missing RP, expect error", - cdh, - None, - user, - key_params, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testMC( - "Send MC request with bytearray RP, expect error", - cdh, - b"1234abcd", - user, - key_params, - ) - - self.testMC( - "Send MC request with missing pubKeyCredParams, expect error", - cdh, - rp, - user, - None, - ) - - self.testMC( - "Send MC request with incorrect pubKeyCredParams, expect error", - cdh, - rp, - user, - b"2356", - ) - - self.testMC( - "Send MC request with incorrect excludeList, expect error", - cdh, - rp, - user, - key_params, - other={"exclude_list": 8}, - ) - - self.testMC( - "Send MC request with incorrect extensions, expect error", - cdh, - rp, - user, - key_params, - other={"extensions": 8}, - ) - - self.testMC( - "Send MC request with incorrect options, expect error", - cdh, - rp, - user, - key_params, - other={"options": 8}, - ) - - self.testMC( - "Send MC request with bad RP.name", - cdh, - {"id": self.host, "name": 8, "icon": "icon"}, - user, - key_params, - ) - - self.testMC( - "Send MC request with bad RP.id", - cdh, - {"id": 8, "name": "name", "icon": "icon"}, - user, - key_params, - ) - - self.testMC( - "Send MC request with bad RP.icon", - cdh, - {"id": self.host, "name": "name", "icon": 8}, - user, - key_params, - ) - - self.testMC( - "Send MC request with bad user.name", - cdh, - rp, - {"id": b"usee_od", "name": 8}, - key_params, - ) - - self.testMC( - "Send MC request with bad user.id", - cdh, - rp, - {"id": "usee_od", "name": "name"}, - key_params, - ) - - self.testMC( - "Send MC request with bad user.displayName", - cdh, - rp, - {"id": "usee_od", "name": "name", "displayName": 8}, - key_params, - ) - - self.testMC( - "Send MC request with bad user.icon", - cdh, - rp, - {"id": "usee_od", "name": "name", "icon": 8}, - key_params, - ) - - self.testMC( - "Send MC request with non-map pubKeyCredParams item", - cdh, - rp, - user, - ["wrong"], - ) - - self.testMC( - "Send MC request with pubKeyCredParams item missing type field", - cdh, - rp, - user, - [{"alg": ES256.ALGORITHM}], - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testMC( - "Send MC request with pubKeyCredParams item with bad type field", - cdh, - rp, - user, - [{"alg": ES256.ALGORITHM, "type": b"public-key"}], - ) - - self.testMC( - "Send MC request with pubKeyCredParams item missing alg", - cdh, - rp, - user, - [{"type": "public-key"}], - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testMC( - "Send MC request with pubKeyCredParams item with bad alg", - cdh, - rp, - user, - [{"alg": "7", "type": "public-key"}], - ) - - self.testMC( - "Send MC request with pubKeyCredParams item with bogus alg, expect UNSUPPORTED_ALGORITHM", - cdh, - rp, - user, - [{"alg": 1234, "type": "public-key"}], - expectedError=CtapError.ERR.UNSUPPORTED_ALGORITHM, - ) - - self.testMC( - "Send MC request with pubKeyCredParams item with bogus type, expect UNSUPPORTED_ALGORITHM", - cdh, - rp, - user, - [{"alg": ES256.ALGORITHM, "type": "rot13"}], - expectedError=CtapError.ERR.UNSUPPORTED_ALGORITHM, - ) - - self.testMC( - "Send MC request with excludeList item with bogus type, expect SUCCESS", - cdh, - rp, - user, - key_params, - expectedError=CtapError.ERR.SUCCESS, - other={"exclude_list": [{"id": b"1234", "type": "rot13"}]}, - ) - - self.testMC( - "Send MC request with excludeList item with bogus type, expect SUCCESS", - cdh, - rp, - user, - key_params, - expectedError=CtapError.ERR.SUCCESS, - other={ - "exclude_list": [ - {"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"} - ] - }, - ) - - self.testMC( - "Send MC request with excludeList with bad item, expect error", - cdh, - rp, - user, - key_params, - other={"exclude_list": ["1234"]}, - ) - - self.testMC( - "Send MC request with excludeList with item missing type field, expect error", - cdh, - rp, - user, - key_params, - other={"exclude_list": [{"id": b"1234"}]}, - ) - - self.testMC( - "Send MC request with excludeList with item missing id field, expect error", - cdh, - rp, - user, - key_params, - other={"exclude_list": [{"type": "public-key"}]}, - ) - - self.testMC( - "Send MC request with excludeList with item containing bad id field, expect error", - cdh, - rp, - user, - key_params, - other={"exclude_list": [{"type": "public-key", "id": "1234"}]}, - ) - - self.testMC( - "Send MC request with excludeList with item containing bad type field, expect error", - cdh, - rp, - user, - key_params, - other={"exclude_list": [{"type": b"public-key", "id": b"1234"}]}, - ) - - self.testMC( - "Send MC request with excludeList containing previous registration, expect CREDENTIAL_EXCLUDED", - cdh, - rp, - user, - key_params, - other={ - "exclude_list": [ - { - "type": "public-key", - "id": prev_reg.auth_data.credential_data.credential_id, - } - ] - }, - expectedError=CtapError.ERR.CREDENTIAL_EXCLUDED, - ) - - self.testMC( - "Send MC request with unknown option, expect SUCCESS", - cdh, - rp, - user, - key_params, - other={"options": {"unknown": False}}, - expectedError=CtapError.ERR.SUCCESS, - ) - - 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("Check assertion is correct"): - credential_data = AttestedCredentialData(prev_reg.auth_data.credential_data) - prev_auth.verify(cdh, credential_data.public_key) - assert ( - prev_auth.credential["id"] - == prev_reg.auth_data.credential_data.credential_id - ) - - self.reboot() - - prev_auth = self.testGA( - "Send GA request after reboot, expect success", - rp["id"], - cdh, - allow_list, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Check assertion is correct"): - credential_data = AttestedCredentialData(prev_reg.auth_data.credential_data) - prev_auth.verify(cdh, credential_data.public_key) - assert ( - prev_auth.credential["id"] - == prev_reg.auth_data.credential_data.credential_id - ) - - 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", - None, - cdh, - allow_list, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testGA( - "Send GA request with bad RPID, expect error", - {"type": "wrong"}, - cdh, - allow_list, - ) - - self.testGA( - "Send GA request with missing clientDataHash, expect MISSING_PARAMETER", - rp["id"], - None, - allow_list, - expectedError=CtapError.ERR.MISSING_PARAMETER, - ) - - self.testGA( - "Send GA request with bad clientDataHash, expect error", - rp["id"], - {"type": "wrong"}, - allow_list, - ) - - self.testGA( - "Send GA request with bad allow_list, expect error", - rp["id"], - cdh, - {"type": "wrong"}, - ) - - self.testGA( - "Send GA request with bad item in allow_list, expect error", - rp["id"], - cdh, - allow_list + ["wrong"], - ) - - self.testGA( - "Send GA request with unknown option, expect SUCCESS", - rp["id"], - cdh, - allow_list, - 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"]: - res = self.testGA( - "Send GA request with uv set to true, expect SUCCESS", - rp["id"], - cdh, - allow_list, - other={"options": {"uv": True}}, - expectedError=CtapError.ERR.SUCCESS, - ) - with Test("Check that UV flag is set in response"): - assert res.auth_data.flags & (1 << 2) - if "up" in info.options: - if info.options["up"]: - res = self.testGA( - "Send GA request with up set to true, expect SUCCESS", - rp["id"], - cdh, - allow_list, - other={"options": {"up": True}}, - expectedError=CtapError.ERR.SUCCESS, - ) - with Test("Check that UP flag is set in response"): - assert res.auth_data.flags & 1 - - self.testGA( - "Send GA request with bogus type item in allow_list, expect SUCCESS", - rp["id"], - cdh, - allow_list + [{"type": "rot13", "id": b"1234"}], - expectedError=CtapError.ERR.SUCCESS, - ) - - self.testGA( - "Send GA request with item missing type field in allow_list, expect error", - rp["id"], - cdh, - allow_list + [{"id": b"1234"}], - ) - - self.testGA( - "Send GA request with item containing bad type field in allow_list, expect error", - rp["id"], - cdh, - allow_list + [{"type": b"public-key", "id": b"1234"}], - ) - - self.testGA( - "Send GA request with item containing bad id in allow_list, expect error", - rp["id"], - cdh, - allow_list + [{"type": b"public-key", "id": 42}], - ) - - self.testGA( - "Send GA request with item missing id in allow_list, expect error", - rp["id"], - cdh, - allow_list + [{"type": b"public-key"}], - ) - - self.testReset() - - appid = sha256(rp["id"].encode("utf8")) - chal = sha256(challenge.encode("utf8")) - with Test("Send CTAP1 register request"): - u2f = U2FTests(self) - reg = u2f.register(chal, appid) - reg.verify(appid, chal) - - with Test("Authenticate CTAP1"): - auth = u2f.authenticate(chal, appid, reg.key_handle) - auth.verify(appid, chal, reg.public_key) - - auth = self.testGA( - "Authenticate CTAP1 registration with CTAP2", - rp["id"], - cdh, - [{"id": reg.key_handle, "type": "public-key"}], - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Check assertion is correct"): - credential_data = AttestedCredentialData.from_ctap1( - reg.key_handle, reg.public_key - ) - auth.verify(cdh, credential_data.public_key) - assert auth.credential["id"] == reg.key_handle - - 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) - 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, - "pin_protocol": pin_protocol, - }, - 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 %d/3" - % (i + 1), - cdh, - rp2, - x, - key_params, - other={ - "options": options, - "pin_auth": pin_auth, - "pin_protocol": pin_protocol, - }, - expectedError=CtapError.ERR.SUCCESS, - ) - - auth1 = self.testGA( - "Send GA request with no allow_list, expect SUCCESS", - rp2["id"], - cdh, - other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Check that there are 3 credentials returned"): - assert auth1.number_of_credentials == 3 - - with Test("Get the next 2 assertions"): - auth2 = self.ctap.get_next_assertion() - auth3 = self.ctap.get_next_assertion() - - 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"): - 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: - self.ctap.get_next_assertion() - assert 0 - except CtapError as e: - print(e) - - def test_client_pin(self,): - pin1 = "1234567890" - self.test_rk(pin1) - - # PinProtocolV1 - res = self.testCP( - "Test getKeyAgreement, expect SUCCESS", - pin_protocol, - PinProtocolV1.CMD.GET_KEY_AGREEMENT, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Test getKeyAgreement has appropriate fields"): - key = res[1] - assert "Is public key" and key[1] == 2 - assert "Is P256" and key[-1] == 1 - assert "Is ALG_ECDH_ES_HKDF_256" and key[3] == -25 - - 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" - self.client.pin_protocol.change_pin(pin1, pin2) - - with Test("Test getting new pin_auth"): - pin_token = self.client.pin_protocol.get_pin_token(pin2) - pin_auth = hmac_sha256(pin_token, cdh)[:16] - - res_mc = self.testMC( - "Send MC request with new pin auth", - cdh, - rp, - user, - key_params, - other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Check UV flag is set"): - assert res_mc.auth_data.flags & (1 << 2) - - res_ga = self.testGA( - "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, - ) - - with Test("Check UV flag is set"): - assert res_ga.auth_data.flags & (1 << 2) - - res_ga = 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 NOT set"): - assert not (res_ga.auth_data.flags & (1 << 2)) - - self.testReset() - - with Test("Test sending zero-length pin_auth, expect PIN_NOT_SET"): - self.testMC( - "Send MC request with new pin auth", - cdh, - rp, - user, - key_params, - other={"pin_auth": b"", "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.PIN_NOT_SET, - ) - self.testGA( - "Send MC request with new pin auth", - rp["id"], - cdh, - other={"pin_auth": b"", "pin_protocol": pin_protocol}, - expectedError=[ - CtapError.ERR.PIN_AUTH_INVALID, - CtapError.ERR.NO_CREDENTIALS, - ], - ) - - with Test("Setting pin code, expect SUCCESS"): - self.client.pin_protocol.set_pin(pin1) - - with Test("Test sending zero-length pin_auth, expect PIN_INVALID"): - self.testMC( - "Send MC request with new pin auth", - cdh, - rp, - user, - key_params, - other={"pin_auth": b"", "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.PIN_AUTH_INVALID, - ) - self.testGA( - "Send MC request with new pin auth", - rp["id"], - cdh, - other={"pin_auth": b"", "pin_protocol": pin_protocol}, - expectedError=[ - CtapError.ERR.PIN_AUTH_INVALID, - CtapError.ERR.NO_CREDENTIALS, - ], - ) - - self.testReset() - with Test("Setting pin code >63 bytes, expect POLICY_VIOLATION "): - try: - self.client.pin_protocol.set_pin("A" * 64) - assert 0 - except CtapError as e: - assert e.code == CtapError.ERR.PIN_POLICY_VIOLATION - - with Test("Get pin token when no pin is set, expect PIN_NOT_SET"): - try: - self.client.pin_protocol.get_pin_token(pin1) - assert 0 - except CtapError as e: - assert e.code == CtapError.ERR.PIN_NOT_SET - - with Test("Get change pin when no pin is set, expect PIN_NOT_SET"): - try: - self.client.pin_protocol.change_pin(pin1, "1234") - assert 0 - except CtapError as e: - assert e.code == CtapError.ERR.PIN_NOT_SET - - with Test("Setting pin code and get pin_token, expect SUCCESS"): - self.client.pin_protocol.set_pin(pin1) - pin_token = self.client.pin_protocol.get_pin_token(pin1) - pin_auth = hmac_sha256(pin_token, cdh)[:16] - - with Test("Get info and assert that clientPin is set to true"): - info = self.ctap.get_info() - assert info.options["clientPin"] - - with Test("Test setting pin again fails"): - try: - self.client.pin_protocol.set_pin(pin1) - assert 0 - except CtapError as e: - print(e) - - res_mc = self.testMC( - "Send MC request with no pin_auth, expect PIN_REQUIRED", - cdh, - rp, - user, - key_params, - 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 = self.testCP( - "Test getRetries, expect SUCCESS", - pin_protocol, - PinProtocolV1.CMD.GET_RETRIES, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Check there is 8 pin attempts left"): - assert res[3] == 8 - - # Flip 1 bit - pin_wrong = list(pin1) - c = pin1[len(pin1) // 2] - - pin_wrong[len(pin1) // 2] = chr(ord(c) ^ 1) - pin_wrong = "".join(pin_wrong) - - for i in range(1, 3): - self.testPP( - "Get pin_token with wrong pin code, expect PIN_INVALID (%d/2)" % i, - pin_wrong, - expectedError=CtapError.ERR.PIN_INVALID, - ) - print("Check there is %d pin attempts left" % (8 - i)) - res = self.ctap.client_pin(pin_protocol, PinProtocolV1.CMD.GET_RETRIES) - assert res[3] == (8 - i) - print("Pass") - - for i in range(1, 3): - self.testPP( - "Get pin_token with wrong pin code, expect PIN_AUTH_BLOCKED %d/2" % i, - pin_wrong, - expectedError=CtapError.ERR.PIN_AUTH_BLOCKED, - ) - - self.reboot() - - with Test("Get pin_token, expect SUCCESS"): - pin_token = self.client.pin_protocol.get_pin_token(pin1) - pin_auth = hmac_sha256(pin_token, cdh)[:16] - - res_mc = self.testMC( - "Send MC request with correct pin_auth", - cdh, - rp, - user, - key_params, - other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, - expectedError=CtapError.ERR.SUCCESS, - ) - - with Test("Test getRetries resets to 8"): - res = self.ctap.client_pin(pin_protocol, PinProtocolV1.CMD.GET_RETRIES) - assert res[3] == (8) - - for i in range(1, 10): - err = CtapError.ERR.PIN_INVALID - if i in (3, 6): - err = CtapError.ERR.PIN_AUTH_BLOCKED - elif i >= 8: - err = [CtapError.ERR.PIN_BLOCKED, CtapError.ERR.PIN_INVALID] - self.testPP( - "Lock out authentictor and check correct error codes %d/9" % i, - pin_wrong, - expectedError=err, - ) - - attempts = 8 - i - if i > 8: - attempts = 0 - - with Test("Check there is %d pin attempts left" % attempts): - res = self.ctap.client_pin(pin_protocol, PinProtocolV1.CMD.GET_RETRIES) - assert res[3] == attempts - - if err == CtapError.ERR.PIN_AUTH_BLOCKED: - self.reboot() - - res_mc = self.testMC( - "Send MC request with correct pin_auth, expect error", - cdh, - rp, - user, - key_params, - other={"pin_auth": pin_auth, "pin_protocol": pin_protocol}, - ) - - self.reboot() - - self.testPP( - "Get pin_token with correct pin code, expect PIN_BLOCKED", - pin1, - expectedError=CtapError.ERR.PIN_BLOCKED, - ) - - def test_fido2(self,): - - self.testReset() - - # self.test_get_info() - # - # self.test_get_assertion() - # - # self.test_make_credential() - # - # self.test_rk(None) - - self.test_client_pin() - - self.testReset() - - self.test_extensions() - - print("Done") diff --git a/tools/testing/tests/hid.py b/tools/testing/tests/hid.py deleted file mode 100644 index 74d9d92..0000000 --- a/tools/testing/tests/hid.py +++ /dev/null @@ -1,252 +0,0 @@ -import sys, os, time -from binascii import hexlify - -from fido2.hid import CTAPHID -from fido2.ctap import CtapError - -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_long_ping() - 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: - 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() - 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."): - 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() - Tester.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") - Tester.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 deleted file mode 100644 index 8853f0d..0000000 --- a/tools/testing/tests/solo.py +++ /dev/null @@ -1,83 +0,0 @@ -from solo.client import SoloClient -from solo.commands import SoloExtension - -from fido2.ctap1 import ApduError -from fido2.utils import sha256 - -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"): - 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 - - with Test("Test bootloader is not active"): - try: - sc.write_flash(memmap[0], b"1234") - except ApduError: - pass - - sc.exchange = sc.exchange_fido2 - - req = SoloClient.format_request(SoloExtension.version, 0, b"A" * 16) - a = sc.ctap2.get_assertion( - sc.host, b"B" * 32, [{"id": req, "type": "public-key"}] - ) - - with Test("Test custom command returned valid assertion"): - assert a.auth_data.rp_id_hash == sha256(sc.host.encode("utf8")) - assert a.credential["id"] == req - assert (a.auth_data.flags & 0x5) == 0x5 - - 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 deleted file mode 100644 index 764bcca..0000000 --- a/tools/testing/tests/tester.py +++ /dev/null @@ -1,230 +0,0 @@ -import time, struct - -from fido2.hid import CtapHidDevice -from fido2.client import Fido2Client -from fido2.attestation import Attestation -from fido2.ctap1 import CTAP1 -from fido2.utils import Timeout - -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): - self.data = data - - def ToWireFormat(self,): - return self.data - - @staticmethod - def FromWireFormat(pkt_size, data): - return Packet(data) - - -class Test: - def __init__(self, msg, catch=None): - self.msg = msg - self.catch = catch - - def __enter__(self,): - print(self.msg) - - def __exit__(self, a, b, c): - if self.catch is None: - print("Pass") - elif isinstance(b, self.catch): - print("Pass") - return b - else: - raise RuntimeError(f"Expected exception {self.catch} did not occur.") - - -class Tester: - def __init__(self, tester=None): - self.origin = "https://examplo.org" - self.host = "examplo.org" - self.user_count = 10 - self.is_sim = False - self.nfc_interface_only = 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 - self.nfc_interface_only = tester.nfc_interface_only - - def find_device(self, nfcInterfaceOnly=False): - dev = None - self.nfc_interface_only = nfcInterfaceOnly - if not nfcInterfaceOnly: - print("--- HID ---") - print(list(CtapHidDevice.list_devices())) - dev = next(CtapHidDevice.list_devices(), None) - - if not dev: - from fido2.pcsc import CtapPcscDevice - - print("--- NFC ---") - print(list(CtapPcscDevice.list_devices())) - dev = next(CtapPcscDevice.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 reboot(self,): - if self.is_sim: - print("Sending restart command...") - self.send_magic_reboot() - Tester.delay(0.25) - else: - print("Please reboot authentictor and hit enter") - input() - self.find_device(self.nfc_interface_only) - - def send_data(self, cmd, data): - 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) - - def send_raw(self, data, cid=None): - if cid is None: - cid = self.dev._dev.cid - elif not isinstance(cid, bytes): - cid = struct.pack("%dB" % len(cid), *[ord(x) for x in cid]) - if not isinstance(data, bytes): - 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 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): - cmd, payload = self.dev._dev.InternalRecv() - return cmd, payload - - def check_error(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: - cond = e.code != expectedError - if isinstance(expectedError, list): - cond = e.code not in expectedError - else: - expectedError = [expectedError] - if cond: - raise RuntimeError( - f"Got error code {hex(e.code)}, expected {[hex(x) for x in expectedError]}" - ) - else: - print(e) - return res - - def testReset(self,): - print("Resetting Authenticator...") - try: - self.ctap.reset() - except CtapError: - # 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.nfc_interface_only) - self.ctap.reset() - - def testMC(self, test, *args, **kwargs): - attestation_object = self.testFunc( - self.ctap.make_credential, test, *args, **kwargs - ) - if attestation_object: - verifier = Attestation.for_type(attestation_object.fmt) - client_data = args[0] - verifier().verify( - attestation_object.att_statement, - attestation_object.auth_data, - client_data, - ) - return attestation_object - - 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(secs): - time.sleep(secs) diff --git a/tools/testing/tests/u2f.py b/tools/testing/tests/u2f.py deleted file mode 100644 index 96d7fd8..0000000 --- a/tools/testing/tests/u2f.py +++ /dev/null @@ -1,133 +0,0 @@ -from fido2.ctap1 import CTAP1, ApduError, APDU -from fido2.utils import sha256 -from fido2.client import _call_polling - -from .tester import Tester, Test - - -class U2FTests(Tester): - def __init__(self, tester=None): - super().__init__(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") - lastc = 0 - - regs = [] - - with Test("Check version"): - assert self.ctap1.get_version() == "U2F_V2" - - with Test("Check bad INS"): - try: - self.ctap1.send_apdu(0, 0, 0, 0, b"") - assert False - except ApduError as e: - assert e.code == 0x6D00 - - with Test("Check bad CLA"): - try: - self.ctap1.send_apdu(1, CTAP1.INS.VERSION, 0, 0, b"abc") - assert False - 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.register(chal, appid) - reg.verify(appid, chal) - auth = self.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.authenticate(chal, appid, regs[i].key_handle) - auth.verify(appid, chal, regs[i].public_key) - - self.reboot() - - for i in range(0, self.user_count): - with Test( - "Post reboot, Checking previous registration %d/%d" - % (i + 1, self.user_count) - ): - 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...") - 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 deleted file mode 100644 index 94c3c45..0000000 --- a/tools/testing/tests/util.py +++ /dev/null @@ -1,12 +0,0 @@ -import math - - -def shannon_entropy(data): - s = 0.0 - total = len(data) - for x in range(0, 256): - freq = data.count(x) - p = freq / total - if p > 0: - s -= p * math.log2(p) - return s