diff --git a/fido2/ctap.c b/fido2/ctap.c index ecdff80..2a74632 100644 --- a/fido2/ctap.c +++ b/fido2/ctap.c @@ -820,7 +820,7 @@ int ctap_filter_invalid_credentials(CTAP_getAssertion * GA) printf1(TAG_GA, "RK %d is a rpId match!\r\n", i); if (count == ALLOW_LIST_MAX_SIZE-1) { - printf2(TAG_ERR, "not enough ram allocated for matching RK's (%d)\r\n", count); + printf2(TAG_ERR, "not enough ram allocated for matching RK's (%d). Skipping.\r\n", count); break; } GA->creds[count].type = PUB_KEY_CRED_PUB_KEY; diff --git a/pc/device.c b/pc/device.c index 704726d..0a7376b 100644 --- a/pc/device.c +++ b/pc/device.c @@ -22,6 +22,11 @@ #include "log.h" #include "ctaphid.h" +#define RK_NUM 50 + +struct ResidentKeyStore { + CTAP_residentKey rks[RK_NUM]; +} RK_STORE; void authenticator_initialize(); @@ -251,6 +256,7 @@ int ctap_generate_rng(uint8_t * dst, size_t num) const char * state_file = "authenticator_state.bin"; const char * backup_file = "authenticator_state2.bin"; +const char * rk_file = "resident_keys.bin"; void authenticator_read_state(AuthenticatorState * state) { @@ -370,6 +376,24 @@ int authenticator_is_backup_initialized() /*}*/ +static void sync_rk() +{ + FILE * f = fopen(rk_file, "wb+"); + if (f== NULL) + { + perror("fopen"); + exit(1); + } + + int ret = fwrite(&RK_STORE, 1, sizeof(RK_STORE), f); + fclose(f); + if (ret != sizeof(RK_STORE)) + { + perror("fwrite"); + exit(1); + } +} + void authenticator_initialize() { uint8_t header[16]; @@ -393,6 +417,22 @@ void authenticator_initialize() perror("fwrite"); exit(1); } + + // resident_keys + f = fopen(rk_file, "rb"); + if (f== NULL) + { + perror("fopen"); + exit(1); + } + ret = fread(&RK_STORE, 1, sizeof(RK_STORE), f); + fclose(f); + if(ret != sizeof(RK_STORE)) + { + perror("fwrite"); + exit(1); + } + } else { @@ -431,6 +471,12 @@ void authenticator_initialize() exit(1); } + // resident_keys + memset(&RK_STORE,0xff,sizeof(RK_STORE)); + sync_rk(); + + + } } @@ -439,26 +485,52 @@ void device_manage() } + + void ctap_reset_rk() { + memset(&RK_STORE,0xff,sizeof(RK_STORE)); + sync_rk(); + } uint32_t ctap_rk_size() { - printf("Warning: rk not implemented\n"); - return 0; + return RK_NUM; } -void ctap_store_rk(int index,CTAP_residentKey * rk) + + +void ctap_store_rk(int index, CTAP_residentKey * rk) { - printf("Warning: rk not implemented\n"); + if (index < RK_NUM) + { + memmove(RK_STORE.rks + index, rk, sizeof(CTAP_residentKey)); + sync_rk(); + } + else + { + printf1(TAG_ERR,"Out of bounds for store_rk\r\n"); + } + } -void ctap_load_rk(int index,CTAP_residentKey * rk) + + +void ctap_load_rk(int index, CTAP_residentKey * rk) { - printf("Warning: rk not implemented\n"); + memmove(rk, RK_STORE.rks + index, sizeof(CTAP_residentKey)); } -void ctap_overwrite_rk(int index,CTAP_residentKey * rk) + +void ctap_overwrite_rk(int index, CTAP_residentKey * rk) { - printf("Warning: rk not implemented\n"); + if (index < RK_NUM) + { + memmove(RK_STORE.rks + index, rk, sizeof(CTAP_residentKey)); + sync_rk(); + } + else + { + printf1(TAG_ERR,"Out of bounds for store_rk\r\n"); + } } void device_wink() diff --git a/targets/stm32l432/src/device.c b/targets/stm32l432/src/device.c index d65b7f8..da82d8b 100644 --- a/targets/stm32l432/src/device.c +++ b/targets/stm32l432/src/device.c @@ -596,7 +596,7 @@ void ctap_overwrite_rk(int index,CTAP_residentKey * rk) memmove(tmppage + (sizeof(CTAP_residentKey) * index) % PAGE_SIZE, rk, sizeof(CTAP_residentKey)); flash_erase_page(page); - flash_write(flash_addr(page), tmppage, ((sizeof(CTAP_residentKey) * (index + 1)) % PAGE_SIZE) ); + flash_write(flash_addr(page), tmppage, PAGE_SIZE); } else { diff --git a/tools/ctap_test.py b/tools/ctap_test.py index f8d21be..932b77d 100755 --- a/tools/ctap_test.py +++ b/tools/ctap_test.py @@ -20,7 +20,7 @@ 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 +from fido2.ctap1 import CTAP1, ApduError, APDU from fido2.ctap2 import * from fido2.cose import * from fido2.utils import Timeout, sha256 @@ -61,6 +61,7 @@ class Tester: def __init__(self,): self.origin = "https://examplo.org" self.host = "examplo.org" + self.user_count = 10 def find_device(self,): print(list(CtapHidDevice.list_devices())) @@ -75,6 +76,9 @@ class Tester: # consume timeout error # cmd,resp = self.recv_raw() + def set_user_count(self, count): + self.user_count = count + def send_data(self, cmd, data): if type(data) != type(b""): data = struct.pack("%dB" % len(data), *[ord(x) for x in data]) @@ -393,16 +397,101 @@ class Tester: chal = sha256(b"AAA") appid = sha256(b"BBB") lastc = 0 - for i in range(0, 5): + + regs = [] + + print("Check version") + assert self.ctap1.get_version() == "U2F_V2" + print("Pass") + + print("Check bad INS") + try: + res = self.ctap1.send_apdu(0, 0, 0, 0, b"") + except ApduError as e: + assert e.code == 0x6D00 + print("Pass") + + print("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 + print("Pass") + + for i in range(0, self.user_count): 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 - print(hex(lastc)) - print("U2F reg + auth pass %d/5" % (i + 1)) + if lastc > 0x100000: + print("WARNING: counter is unusually high: %04x" % lastc) + assert 0 + + print( + "U2F reg + auth pass %d/%d (count: %02x)" + % (i + 1, self.user_count, lastc) + ) + + print("Checking previous registrations...") + for i in range(0, self.user_count): + auth = self.ctap1.authenticate(chal, appid, regs[i].key_handle) + auth.verify(appid, chal, regs[i].public_key) + print("Auth pass %d/%d" % (i + 1, self.user_count)) + + print("Check that all previous credentials are registered...") + for i in range(0, self.user_count): + 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 + + print("Check pass %d/%d" % (i + 1, self.user_count)) + + print("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 + print("Pass") + + print("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 + print("Pass") + + print("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 + print("Pass") + + print("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 + print("Pass") def test_fido2_simple(self, pin_token=None): creds = [] @@ -515,8 +604,8 @@ class Tester: exclude_list.append({"id": fake_id2, "type": "public-key"}) # test make credential - print("make 3 credentials") - for i in range(0, 3): + print("make %d credentials" % self.user_count) + for i in range(0, self.user_count): attest, data = self.client.make_credential( rp, user, challenge, pin=PIN, exclude_list=[] ) @@ -657,10 +746,10 @@ class Tester: def test_rk(self,): creds = [] rp = {"id": self.host, "name": "ExaRP"} - user0 = {"id": b"first one", "name": "single User"} users = [ - {"id": b"user" + os.urandom(16), "name": "AB User"} for i in range(0, 2) + {"id": b"user" + os.urandom(16), "name": "Username%d" % i} + for i in range(0, self.user_count) ] challenge = "Y2hhbGxlbmdl" PIN = None @@ -671,7 +760,7 @@ class Tester: print("registering 1 user with RK") t1 = time.time() * 1000 attest, data = self.client.make_credential( - rp, user0, challenge, pin=PIN, exclude_list=[], rk=True + rp, users[-1], challenge, pin=PIN, exclude_list=[], rk=True ) t2 = time.time() * 1000 VerifyAttestation(attest, data) @@ -690,7 +779,7 @@ class Tester: print(assertions[0], client_data) print("registering %d users with RK" % len(users)) - for i in range(0, 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 @@ -707,6 +796,9 @@ class Tester: ) 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) @@ -728,7 +820,8 @@ class Tester: rp["id"], challenge, pin=PIN ) t2 = time.time() * 1000 - assert len(assertions) == len(users) + 1 + print("Assertions: %d, users: %d" % (len(assertions), len(users))) + assert len(assertions) == len(users) for x, y in zip(assertions, creds): x.verify(client_data.hash, y.public_key) @@ -834,18 +927,35 @@ def test_find_brute_force(): if __name__ == "__main__": - if len(sys.argv) > 1 and sys.argv[1] == "sim": + if len(sys.argv) < 2: + print("Usage: %s [sim] <[u2f]|[fido2]|[rk]|[hid]|[ping]>") + sys.exit(0) + + if "sim" in sys.argv: print("Using UDP backend.") force_udp_backend() t = Tester() t.find_device() - # t.test_hid() - # t.test_long_ping() - # t.test_fido2() - t.test_u2f() - # t.test_rk() + t.set_user_count(15) + + if "u2f" in sys.argv: + t.test_u2f() + + if "fido2" in sys.argv: + t.test_fido2() + t.test_fido2_simple() + + 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() + # t.test_responses() # test_find_brute_force() - # t.test_fido2_simple() # t.test_fido2_brute_force()