diff --git a/sys/fido2/ctap/ctap_mem.c b/sys/fido2/ctap/ctap_mem.c index 8b102f493d..cb4a00028a 100644 --- a/sys/fido2/ctap/ctap_mem.c +++ b/sys/fido2/ctap/ctap_mem.c @@ -30,6 +30,7 @@ #ifdef BOARD_NATIVE #include "mtd_default.h" +// native mtd is file backed => Start address of flash is 0. char *_backing_memory = NULL; static mtd_dev_t *_mtd_dev = NULL; #else diff --git a/tests/sys/fido2_ctap/Makefile b/tests/sys/fido2_ctap/Makefile index 31999264d5..66a265adb2 100644 --- a/tests/sys/fido2_ctap/Makefile +++ b/tests/sys/fido2_ctap/Makefile @@ -1,39 +1,17 @@ -BOARD ?= nrf52840dk -#BOARD ?= nrf52840dongle +include ../Makefile.tests_common -include ../Makefile.sys_common +# same as CTAP_STACKSIZE +CFLAGS += -DTHREAD_STACKSIZE_MAIN=15000 -USEMODULE += fido2_ctap_transport_hid -USEMODULE += usbus -USEMODULE += ztimer_sec -USEPKG += fido2_tests +# Add unittest framework +USEMODULE += embunit -USB_VID ?= $(USB_VID_TESTING) -USB_PID ?= $(USB_PID_TESTING) +USEMODULE += fido2_ctap # Disable user presence tests -# Should be used when running fido2-test to make them run quicker -#CFLAGS += -DCONFIG_FIDO2_CTAP_DISABLE_UP=1 +CFLAGS += -DCONFIG_FIDO2_CTAP_DISABLE_UP=1 # Disable user LED animation -#CFLAGS += -DCONFIG_FIDO2_CTAP_DISABLE_LED=1 - -# FIDO2 tests except for the ones requiring user presence -# -# Use env -i because fido2-test has a depedency (pyscard) that needs to be -# compiled natively (x86-64). Therefore we need to clear the flags set by e.g. -# BOARD = nrf52840dk -fido2-test: - env -i PATH=$(PATH) $(MAKE) -C $(PKGDIRBASE)/fido2_tests - -# FIDO2 user presence tests. -# -# Make sure to enable user presence tests by uncommenting CFLAGS += -DCONFIG_FIDO2_CTAP_DISABLE_UP=1 -# -# Use env -i because fido2-test has a depedency (pyscard) that needs to be -# compiled natively (x86-64). Therefore we need to clear the flags set by e.g. -# BOARD = nrf52840dk -fido2-test-up: - env -i PATH=$(PATH) $(MAKE) -C $(PKGDIRBASE)/fido2_tests up-tests +CFLAGS += -DCONFIG_FIDO2_CTAP_DISABLE_LED=1 include $(RIOTBASE)/Makefile.include diff --git a/tests/sys/fido2_ctap/README.md b/tests/sys/fido2_ctap/README.md index 726866ee7f..e73745fec9 100644 --- a/tests/sys/fido2_ctap/README.md +++ b/tests/sys/fido2_ctap/README.md @@ -1,59 +1,16 @@ -# Test Application for FIDO2 CTAP +# Overview -This test aims to test the FIDO2 CTAP implementation by creating a FIDO2 -authenticator which uses CTAPHID as communication protocol. +This test application tests FIDO2 CTAP2 functionality without a transport layer being used. -Note: -* This test application has only been tested on an nrf52840 DK. +To execute the test run e.g. -The test application requires at least 16536 bytes of stack memory which are -divided as follows: -* 512 bytes isr_stack -* 1024 usbus -* 15000 bytes FIDO2 CTAP +``` +BOARD = nrf52840dk make flash test +``` -## Usage -The FIDO2 authenticator can be tested in two ways: +To generate a new test case array run: +* Note: This requires the [python-fido lib](https://github.com/Yubico/python-fido2) -### Functional testing -1. Flash the device with `make flash`. -2. Test the authenticator on a website like [Webauthn.io](https://webauthn.io/). - -Note: -* Due to limited support of FIDO2 CTAP in browsers as of now, make sure to use the - Chromium or Google Chrome browser when testing on [Webauthn.io](https://webauthn.io/). -* When registering and authenticating on [Webauthn.io](https://webauthn.io/) you -will need to push button 1 on your device in order to show user presence. - -**Resetting the authenticator** -* To reset the authenticator, meaning that all credentials and state information -will be deleted, execute the `reset.py` file located in this directory. - * This requires you to install the python fido2 package. To install run: - `pip install fido2==0.8.1`. - -### Unit testing -Unit testing is based on the `fido2_tests` package. - -There are two test targets (fido2-test, fido2-test-up). The former requires no user -interaction the latter does. - -Note: -* The tests require python 3.6+. -* The tests require [swig](http://www.swig.org/) to be installed on your host computer. -* Running the tests for the first time will setup a virtual python environment (venv) and install python dependencies of the tests. To check the dependencies please refer to the `requirements.txt` of the [fido2-tests repository](https://github.com/solokeys/fido2-tests). -* The unit tests will require you to reboot the authenticator multiple times. Be patient before continuing as it takes a few seconds for the connection between OS and authenticator to be re-established. -* If you keep getting errors while trying to run the tests try changing to another git branch and back e.g. `git checkout branch1 && git checkout -` in order to remove build artifacts. Then re-flash the device with `make flash term` and try to run the tests again with `make fido2-test` or `make fido2-test-up`. - -fido2-test - -1. To make benchmarking faster disable user presence tests by enabling the CFLAG - `CONFIG_FIDO2_CTAP_DISABLE_UP` in the Makefile or through KConfig. -2. Flash the device with `make flash`. -3. Run the unit tests by running `make fido2-test`. - -fido2-test-up - -1. Make sure that the CFLAG `CONFIG_FIDO2_CTAP_DISABLE_UP` is disabled as this test target - requires user interaction. -2. Flash the device with `make flash`. -3. Run the unit tests by running `make fido2-test-up` and follow the instructions. E.g. when `.ACTIVATE UP ONCE` is displayed, press the configured UP button (default button 1) once. +``` +python3 gen_test_case.py +``` \ No newline at end of file diff --git a/tests/sys/fido2_ctap/gen_test_case.py b/tests/sys/fido2_ctap/gen_test_case.py new file mode 100644 index 0000000000..5b56b33174 --- /dev/null +++ b/tests/sys/fido2_ctap/gen_test_case.py @@ -0,0 +1,92 @@ +from fido2.ctap2 import CTAP2 +from fido2 import cbor +from fido2.client import Fido2Client +from fido2.server import Fido2Server +from unittest import mock + + +def mock_device(): + device = mock.MagicMock() + return CTAP2(device) + + +def get_cbor(data): + request = b"" + if data is not None: + request += cbor.encode(data) + + return request + + +def args(*params): + """Constructs a dict from a list of arguments for sending a CBOR command. + None elements will be omitted. + """ + return dict((i, v) for i, v in enumerate(params, 1) if v is not None) + + +def print_req(req, prefix): + print(f"static uint8_t {prefix}_data[] = {{", end='') + print("".join(f"{hex(x)}, " for x in req), end='') + print("};") + + +def gen_mc_req(): + dev = mock_device() + dev.capabilities = 0 + user = {"id": b"user_id", "name": "A. User"} + client = Fido2Client(dev, "https://example.com") + server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="direct") + + create_options, _ = server.register_begin(user) + create_options = create_options['publicKey'] + client_data = client._build_client_data("webauthn.create", create_options['challenge']) + + options = {} + options["rk"] = True + + return get_cbor( + args( + client_data.hash, + create_options["rp"], + create_options["user"], + create_options["pubKeyCredParams"], + None, # exclude list + None, # extensions + options, + None, # pin_auth + None, # pin protocol version + )) + + +def gen_ga_req(): + dev = mock_device() + dev.capabilities = 0 + # user = {"id": b"user_id", "name": "A. User"} + client = Fido2Client(dev, "https://example.com") + server = Fido2Server({"id": "example.com", "name": "Example RP"}, attestation="direct") + + request_options, _ = server.authenticate_begin() + request_options = request_options['publicKey'] + client_data = client._build_client_data("webauthn.get", request_options['challenge']) + + return get_cbor( + args( + request_options["rpId"], + client_data.hash, + None, # allow list + None, # extensions + None, # options + None, # pin_uv_param + None # pin_uv_protocol + )) + + +if __name__ == "__main__": + req = gen_mc_req() + print_req(req, "mc") + + print("") + + req = gen_ga_req() + print_req(req, "ga") diff --git a/tests/sys/fido2_ctap/main.c b/tests/sys/fido2_ctap/main.c index 6fbf5cd4e7..51fa8bc115 100644 --- a/tests/sys/fido2_ctap/main.c +++ b/tests/sys/fido2_ctap/main.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 Freie Universität Berlin + * Copyright (C) 2022 Freie Universität Berlin * * This file is subject to the terms and conditions of the GNU Lesser * General Public License v2.1. See the file LICENSE in the top level @@ -10,27 +10,91 @@ * @ingroup tests * @{ * @file - * @brief FIDO2 CTAP test application that creates an authenticator - * which uses CTAPHID as underlying communication protocol + * @brief FIDO2 CTAP test application that tests CTAP functionality + * without transport layer. * - * @author Nils Ollrogge + * @author Nils Ollrogge * @} */ - #include #include -#define ENABLE_DEBUG 0 -#include "debug.h" - -#include "ztimer.h" +#include "embUnit.h" #include "fido2/ctap.h" -#include "fido2/ctap/transport/ctap_transport.h" + +/** + * To generate a new arrays simply run the gen_test_case.py script + */ +static uint8_t mc_data[] = +{ 0xa5, 0x1, 0x58, 0x20, 0xe0, 0xa1, 0xec, 0x5a, 0xa, 0x12, 0xa1, 0x4, 0xc8, 0xcb, 0x93, 0x54, 0x31, + 0xbf, 0x5c, 0x39, 0x7a, 0xee, 0x1b, 0x9f, 0xd0, 0x97, 0x97, 0x7d, 0x7b, 0xfb, 0x1, 0xa1, 0x20, + 0x2a, 0xad, 0x5c, 0x2, 0xa2, 0x62, 0x69, 0x64, 0x6b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, + 0x2e, 0x63, 0x6f, 0x6d, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x6a, 0x45, 0x78, 0x61, 0x6d, 0x70, 0x6c, + 0x65, 0x20, 0x52, 0x50, 0x3, 0xa2, 0x62, 0x69, 0x64, 0x47, 0x75, 0x73, 0x65, 0x72, 0x5f, 0x69, + 0x64, 0x64, 0x6e, 0x61, 0x6d, 0x65, 0x67, 0x41, 0x2e, 0x20, 0x55, 0x73, 0x65, 0x72, 0x4, 0x84, + 0xa2, 0x63, 0x61, 0x6c, 0x67, 0x26, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, + 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0xa2, 0x63, 0x61, 0x6c, 0x67, 0x27, 0x64, 0x74, 0x79, 0x70, + 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0xa2, 0x63, 0x61, 0x6c, + 0x67, 0x38, 0x24, 0x64, 0x74, 0x79, 0x70, 0x65, 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, + 0x6b, 0x65, 0x79, 0xa2, 0x63, 0x61, 0x6c, 0x67, 0x39, 0x1, 0x0, 0x64, 0x74, 0x79, 0x70, 0x65, + 0x6a, 0x70, 0x75, 0x62, 0x6c, 0x69, 0x63, 0x2d, 0x6b, 0x65, 0x79, 0x7, 0xa1, 0x62, 0x72, 0x6b, + 0xf5, }; + +static uint8_t ga_data[] = +{ 0xa2, 0x1, 0x6b, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2, 0x58, + 0x20, 0x7d, 0xf3, 0x9a, 0x49, 0xa9, 0x8c, 0xa2, 0xd8, 0x3e, 0x50, 0x36, 0x42, 0xd9, 0xc6, 0xfa, + 0x39, 0xf5, 0xa7, 0xe9, 0x35, 0x41, 0xb0, 0x1c, 0x59, 0xfa, 0xc2, 0x35, 0x3a, 0xb0, 0xbc, 0xcc, + 0x70, }; + +static void test_ctap(void) +{ + fido2_ctap_init(); + ctap_req_t req = { 0 }; + ctap_resp_t resp = { 0 }; + + /* reset authenticator */ + req.method = CTAP_RESET; + req.buf = NULL; + req.len = 0x0; + fido2_ctap_handle_request(&req, &resp); + + TEST_ASSERT(resp.status == CTAP2_OK); + + /* create new credential */ + req.method = CTAP_MAKE_CREDENTIAL; + req.buf = mc_data; + req.len = sizeof(mc_data); + fido2_ctap_handle_request(&req, &resp); + + TEST_ASSERT(resp.status == CTAP2_OK); + + /* create assertion using credential */ + req.method = CTAP_GET_ASSERTION; + req.buf = ga_data; + req.len = sizeof(ga_data); + fido2_ctap_handle_request(&req, &resp); + + TEST_ASSERT(resp.status == CTAP2_OK); +} + +Test *ctap_tests(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_ctap), + }; + + EMB_UNIT_TESTCALLER(ctap_tests, NULL, NULL, fixtures); + + return (Test *)&ctap_tests; +} int main(void) { - /* sleep in order to see early DEBUG outputs */ - ztimer_sleep(ZTIMER_SEC, 3); - fido2_ctap_transport_init(); + TESTS_START(); + TESTS_RUN(ctap_tests()); + TESTS_END(); + + return 0; } +/** @} */ diff --git a/tests/sys/fido2_ctap/tests/01-run.py b/tests/sys/fido2_ctap/tests/01-run.py new file mode 100755 index 0000000000..06365aea55 --- /dev/null +++ b/tests/sys/fido2_ctap/tests/01-run.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2022 Freie Universität Berlin +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +import sys +from testrunner import run_check_unittests + + +if __name__ == "__main__": + sys.exit(run_check_unittests()) diff --git a/tests/sys/fido2_ctap_hid/Makefile b/tests/sys/fido2_ctap_hid/Makefile new file mode 100644 index 0000000000..51b1153a93 --- /dev/null +++ b/tests/sys/fido2_ctap_hid/Makefile @@ -0,0 +1,37 @@ +BOARD ?= nrf52840dk +#BOARD ?= nrf52840dongle + +include ../Makefile.tests_common + +USEMODULE += fido2_ctap_transport_hid +USEPKG += fido2_tests + +USB_VID ?= $(USB_VID_TESTING) +USB_PID ?= $(USB_PID_TESTING) + +# Disable user presence tests +# Should be used when running fido2-test to make them run quicker +CFLAGS += -DCONFIG_FIDO2_CTAP_DISABLE_UP=1 + +# Disable user LED animation +CFLAGS += -DCONFIG_FIDO2_CTAP_DISABLE_LED=1 + +# FIDO2 tests except for the ones requiring user presence +# +# Use env -i because fido2-test has a depedency (pyscard) that needs to be +# compiled natively (x86-64). Therefore we need to clear the flags set by e.g. +# BOARD = nrf52840dk +fido2-test: + env -i PATH=$(PATH) $(MAKE) -C $(RIOTBASE)/build/pkg/fido2_tests + +# FIDO2 user presence tests. +# +# Make sure to enable user presence tests by uncommenting CFLAGS += -DCONFIG_FIDO2_CTAP_DISABLE_UP=1 +# +# Use env -i because fido2-test has a depedency (pyscard) that needs to be +# compiled natively (x86-64). Therefore we need to clear the flags set by e.g. +# BOARD = nrf52840dk +fido2-test-up: + env -i PATH=$(PATH) $(MAKE) -C $(RIOTBASE)/build/pkg/fido2_tests up-tests + +include $(RIOTBASE)/Makefile.include diff --git a/tests/sys/fido2_ctap_hid/README.md b/tests/sys/fido2_ctap_hid/README.md new file mode 100644 index 0000000000..5846b7da8f --- /dev/null +++ b/tests/sys/fido2_ctap_hid/README.md @@ -0,0 +1,59 @@ +# Test Application for FIDO2 CTAP using USB HID transport binding + +This test aims to test the FIDO2 CTAP implementation by creating a FIDO2 +authenticator which uses CTAPHID as communication protocol. + +Note: +* This test application has only been tested on an nrf52840 DK. + +The test application requires at least 16536 bytes of stack memory which are +divided as follows: +* 512 bytes isr_stack +* 1024 usbus +* 15000 bytes FIDO2 CTAP + +## Usage +The FIDO2 authenticator can be tested in two ways: + +### Functional testing +1. Flash the device with `make flash`. +2. Test the authenticator on a website like [Webauthn.io](https://webauthn.io/). + +Note: +* Due to limited support of FIDO2 CTAP in browsers as of now, make sure to use the + Chromium or Google Chrome browser when testing on [Webauthn.io](https://webauthn.io/). +* When registering and authenticating on [Webauthn.io](https://webauthn.io/) you +will need to push button 1 on your device in order to show user presence. + +**Resetting the authenticator** +* To reset the authenticator, meaning that all credentials and state information +will be deleted, execute the `reset.py` file located in this directory. + * This requires you to install the python fido2 package. To install run: + `pip install fido2==0.8.1`. + +### Unit testing +Unit testing is based on the `fido2_tests` package. + +There are two test targets (fido2-test, fido2-test-up). The former requires no user +interaction the latter does. + +Note: +* The tests require python 3.6+. +* The tests require [swig](http://www.swig.org/) to be installed on your host computer. +* Running the tests for the first time will setup a virtual python environment (venv) and install python dependencies of the tests. To check the dependencies please refer to the `requirements.txt` of the [fido2-tests repository](https://github.com/solokeys/fido2-tests). +* The unit tests will require you to reboot the authenticator multiple times. Be patient before continuing as it takes a few seconds for the connection between OS and authenticator to be re-established. +* If you keep getting errors while trying to run the tests try changing to another git branch and back e.g. `git checkout branch1 && git checkout -` in order to remove build artifacts. Then re-flash the device with `make flash term` and try to run the tests again with `make fido2-test` or `make fido2-test-up`. + +fido2-test + +1. To make benchmarking faster disable user presence tests by enabling the CFLAG + `CONFIG_FIDO2_CTAP_DISABLE_UP` in the Makefile or through KConfig. +2. Flash the device with `make flash`. +3. Run the unit tests by running `make fido2-test`. + +fido2-test-up + +1. Make sure that the CFLAG `CONFIG_FIDO2_CTAP_DISABLE_UP` is disabled as this test target + requires user interaction. +2. Flash the device with `make flash`. +3. Run the unit tests by running `make fido2-test-up` and follow the instructions. E.g. when `.ACTIVATE UP ONCE` is displayed, press the configured UP button (default button 1) once. diff --git a/tests/sys/fido2_ctap_hid/app.config.test b/tests/sys/fido2_ctap_hid/app.config.test new file mode 100644 index 0000000000..df5e7e4f8d --- /dev/null +++ b/tests/sys/fido2_ctap_hid/app.config.test @@ -0,0 +1,8 @@ +CONFIG_MODULE_AUTO_INIT_USBUS=n +CONFIG_MODULE_USBUS=y +CONFIG_MODULE_FIDO2=y +CONFIG_MODULE_FIDO2_CTAP=y +CONFIG_MODULE_FIDO2_CTAP_TRANSPORT=y +CONFIG_MODULE_FIDO2_CTAP_TRANSPORT_HID=y + +CONFIG_PACKAGE_FIDO2_TESTS=y diff --git a/tests/sys/fido2_ctap_hid/main.c b/tests/sys/fido2_ctap_hid/main.c new file mode 100644 index 0000000000..4e09323388 --- /dev/null +++ b/tests/sys/fido2_ctap_hid/main.c @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2022 Freie Universität Berlin + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup tests + * @{ + * @file + * @brief FIDO2 CTAP test application that creates an authenticator + * which uses CTAPHID as underlying communication protocol + * + * @author Nils Ollrogge + * @} + */ +#define ENABLE_DEBUG (0) +#include "debug.h" + +#include "xtimer.h" + +#include "fido2/ctap/transport/ctap_transport.h" + +int main(void) +{ + /* sleep in order to see early DEBUG outputs */ + xtimer_sleep(3); + fido2_ctap_transport_init(); +} diff --git a/tests/sys/fido2_ctap_hid/reset.py b/tests/sys/fido2_ctap_hid/reset.py new file mode 100644 index 0000000000..f36dbb9750 --- /dev/null +++ b/tests/sys/fido2_ctap_hid/reset.py @@ -0,0 +1,23 @@ +from fido2.hid import CtapHidDevice +from fido2.ctap2 import CTAP2 + + +def get_device(): + devs = list(CtapHidDevice.list_devices()) + assert len(devs) == 1 + return devs[0] + + +if __name__ == '__main__': + try: + dev = get_device() + except Exception: + print("Unable to find authenticator") + exit(-1) + + ctap = CTAP2(dev) + try: + ctap.reset() + print("Device successfully reset") + except Exception as e: + print(e)