1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-13 08:40:26 +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
#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

View File

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

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

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
* 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 <nils-ollrogge@outlook.de>
* @author Nils Ollrogge <nils.ollrogge@mailbox.tu-dresden.de>
* @}
*/
#include <stdio.h>
#include <stdlib.h>
#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;
}
/** @} */

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)