1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 00:09:46 +01:00

fido2/ctap: add test for ctap without transport layer

This commit is contained in:
Ollrogge 2024-10-01 22:00:52 +02:00
parent eddb00ae42
commit 7e0a86ba32
11 changed files with 361 additions and 97 deletions

View File

@ -30,6 +30,7 @@
#ifdef BOARD_NATIVE #ifdef BOARD_NATIVE
#include "mtd_default.h" #include "mtd_default.h"
// native mtd is file backed => Start address of flash is 0.
char *_backing_memory = NULL; char *_backing_memory = NULL;
static mtd_dev_t *_mtd_dev = NULL; static mtd_dev_t *_mtd_dev = NULL;
#else #else

View File

@ -1,39 +1,17 @@
BOARD ?= nrf52840dk include ../Makefile.tests_common
#BOARD ?= nrf52840dongle
include ../Makefile.sys_common # same as CTAP_STACKSIZE
CFLAGS += -DTHREAD_STACKSIZE_MAIN=15000
USEMODULE += fido2_ctap_transport_hid # Add unittest framework
USEMODULE += usbus USEMODULE += embunit
USEMODULE += ztimer_sec
USEPKG += fido2_tests
USB_VID ?= $(USB_VID_TESTING) USEMODULE += fido2_ctap
USB_PID ?= $(USB_PID_TESTING)
# Disable user presence tests # 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 # Disable user LED animation
#CFLAGS += -DCONFIG_FIDO2_CTAP_DISABLE_LED=1 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
include $(RIOTBASE)/Makefile.include include $(RIOTBASE)/Makefile.include

View File

@ -1,59 +1,16 @@
# Test Application for FIDO2 CTAP # Overview
This test aims to test the FIDO2 CTAP implementation by creating a FIDO2 This test application tests FIDO2 CTAP2 functionality without a transport layer being used.
authenticator which uses CTAPHID as communication protocol.
Note: To execute the test run e.g.
* 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: BOARD = nrf52840dk make flash test
* 512 bytes isr_stack ```
* 1024 usbus
* 15000 bytes FIDO2 CTAP
## Usage To generate a new test case array run:
The FIDO2 authenticator can be tested in two ways: * Note: This requires the [python-fido lib](https://github.com/Yubico/python-fido2)
### Functional testing ```
1. Flash the device with `make flash`. python3 gen_test_case.py
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.

View File

@ -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")

View File

@ -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 * 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 * General Public License v2.1. See the file LICENSE in the top level
@ -10,27 +10,91 @@
* @ingroup tests * @ingroup tests
* @{ * @{
* @file * @file
* @brief FIDO2 CTAP test application that creates an authenticator * @brief FIDO2 CTAP test application that tests CTAP functionality
* which uses CTAPHID as underlying communication protocol * without transport layer.
* *
* @author Nils Ollrogge <nils-ollrogge@outlook.de> * @author Nils Ollrogge <nils.ollrogge@mailbox.tu-dresden.de>
* @} * @}
*/ */
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#define ENABLE_DEBUG 0 #include "embUnit.h"
#include "debug.h"
#include "ztimer.h"
#include "fido2/ctap.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) int main(void)
{ {
/* sleep in order to see early DEBUG outputs */ TESTS_START();
ztimer_sleep(ZTIMER_SEC, 3); TESTS_RUN(ctap_tests());
fido2_ctap_transport_init(); TESTS_END();
return 0;
} }
/** @} */

View File

@ -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())

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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 <nils.ollrogge@mailbox.tu-dresden.de>
* @}
*/
#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();
}

View File

@ -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)