FIDO2 support in RIOT

This commit is contained in:
Ollrogge 2021-02-19 00:17:40 +01:00
parent 8ca1520342
commit e127a4d865
33 changed files with 7893 additions and 1 deletions

View File

@ -37,6 +37,7 @@ PSEUDOMODULES += evtimer_mbox
PSEUDOMODULES += evtimer_on_ztimer
PSEUDOMODULES += fmt_%
PSEUDOMODULES += gcoap_dtls
PSEUDOMODULES += fido2_tests
PSEUDOMODULES += gnrc_dhcpv6_%
PSEUDOMODULES += gnrc_ipv6_default
PSEUDOMODULES += gnrc_ipv6_ext_frag_stats

9
pkg/fido2_tests/Makefile Normal file
View File

@ -0,0 +1,9 @@
PKG_NAME=fido2_tests
PKG_URL=https://github.com/solokeys/fido2-tests
PKG_VERSION=3f7893d8d1a39b009cddad7913d3808ca664d3b7
PKG_LICENSE=Apache-2.0 OR MIT
include $(RIOTBASE)/pkg/pkg.mk
all:
@

View File

@ -0,0 +1,226 @@
From 445c1fe93f6d0edbd1c59f318703b070c8ee445f Mon Sep 17 00:00:00 2001
From: Ollrogge <nils-ollrogge@outlook.de>
Date: Tue, 7 Sep 2021 19:12:31 +0200
Subject: [PATCH] Adaptions for RIOT FIDO2 CTAP
---
Makefile | 15 ++++++------
tests/conftest.py | 2 +-
tests/standard/fido2/pin/test_pin.py | 24 ++++++++++++++++---
tests/standard/fido2/test_reset.py | 5 ++++
tests/standard/fido2/test_resident_key.py | 4 ++--
.../fido2/user_presence/test_user_presence.py | 10 +++++++-
tests/standard/transport/test_hid.py | 11 +++++++++
7 files changed, 57 insertions(+), 14 deletions(-)
diff --git a/Makefile b/Makefile
index 85aa451..c101826 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-.PHONY: standard-tests vendor-tests
+.PHONY: standard-tests
PY_VERSION=$(shell python -c "import sys; print('%d.%d'% sys.version_info[0:2])")
VALID=$(shell python -c "print($(PY_VERSION) >= 3.6)")
@@ -16,24 +16,21 @@ else
endif
standard-tests: venv
- $(BIN)/pytest tests/standard
+ $(BIN)/pytest --ignore=tests/standard/fido2v1 --ignore=tests/standard/fido2/extensions --ignore=tests/standard/u2f --ignore tests/standard/fido2/user_presence -k "not test_ctap1_interop and not test_rk_maximum_list_capacity_per_rp_nodisplay and not test_keep_alive" tests/standard/ -s
-vendor-tests: venv
- $(BIN)/pytest tests/vendor
+up-tests: venv
+ $(BIN)/pytest tests/standard/fido2/user_presence -s
# setup development environment
venv:
$(PYTHON) -m venv venv
$(BIN)/python -m pip install -U pip
$(BIN)/pip install -U -r requirements.txt
- $(BIN)/pip install -U -r dev-requirements.txt
- $(BIN)/pre-commit install
# re-run if dependencies change
update:
$(BIN)/python -m pip install -U pip
$(BIN)/pip install -U -r requirements.txt
- $(BIN)/pip install -U -r dev-requirements.txt
# ensure this passes before commiting
check:
@@ -48,3 +45,7 @@ black:
isort:
$(BIN)/isort -y --recursive tests/
+
+clean:
+ rm -r venv
+
diff --git a/tests/conftest.py b/tests/conftest.py
index 761a684..d13d6dc 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -175,7 +175,7 @@ class MoreRobustPcscDevice(CtapPcscDevice):
except CtapError:
if self._capabilities == 0:
raise ValueError("Unsupported device")
-
+
def apdu_exchange(self, apdu, protocol = None):
try:
return super().apdu_exchange(apdu,protocol)
diff --git a/tests/standard/fido2/pin/test_pin.py b/tests/standard/fido2/pin/test_pin.py
index 78b09e3..f5ee4e4 100644
--- a/tests/standard/fido2/pin/test_pin.py
+++ b/tests/standard/fido2/pin/test_pin.py
@@ -60,7 +60,11 @@ class TestPin(object):
with pytest.raises(CtapError) as e:
device.client.pin_protocol.set_pin('1234')
- assert e.value.code == CtapError.ERR.NOT_ALLOWED
+ '''
+ CTAP spec states: "If a PIN has already been set, authenticator
+ returns CTAP2_ERR_PIN_AUTH_INVALID error."
+ '''
+ assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
def test_get_key_agreement_fields(self, CPRes):
@@ -99,11 +103,25 @@ class TestPin(object):
def test_zero_length_pin_auth(self, device, SetPinRes):
with pytest.raises(CtapError) as e:
reg = device.sendMC(*FidoRequest(SetPinRes, pin_auth=b"").toMC())
- assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
+
+ '''
+ CTAP spec states: If platform sends zero length pinAuth, authenticator
+ needs to wait for user touch and then returns either
+ CTAP2_ERR_PIN_NOT_SET if pin is not set or CTAP2_ERR_PIN_INVALID
+ if pin has been set. [...]"
+ '''
+ assert e.value.code == CtapError.ERR.PIN_INVALID
with pytest.raises(CtapError) as e:
reg = device.sendGA(*FidoRequest(SetPinRes, pin_auth=b"").toGA())
- assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
+
+ '''
+ CTAP spec states: If platform sends zero length pinAuth, authenticator
+ needs to wait for user touch and then returns either
+ CTAP2_ERR_PIN_NOT_SET if pin is not set or CTAP2_ERR_PIN_INVALID
+ if pin has been set.
+ '''
+ assert e.value.code == CtapError.ERR.PIN_INVALID
def test_make_credential_no_pin(self, device, SetPinRes):
with pytest.raises(CtapError) as e:
diff --git a/tests/standard/fido2/test_reset.py b/tests/standard/fido2/test_reset.py
index 508d755..adb2818 100644
--- a/tests/standard/fido2/test_reset.py
+++ b/tests/standard/fido2/test_reset.py
@@ -9,9 +9,14 @@ import tests
def test_reset(device):
device.reset()
+'''
+Not mentioned in any spec.
+'''
+'''
def test_reset_window(device):
print("Waiting 11s before sending reset...")
time.sleep(11)
with pytest.raises(CtapError) as e:
device.ctap2.reset(on_keepalive=DeviceSelectCredential(1))
assert e.value.code == CtapError.ERR.NOT_ALLOWED
+'''
\ No newline at end of file
diff --git a/tests/standard/fido2/test_resident_key.py b/tests/standard/fido2/test_resident_key.py
index 2c5bece..32fe534 100644
--- a/tests/standard/fido2/test_resident_key.py
+++ b/tests/standard/fido2/test_resident_key.py
@@ -45,7 +45,7 @@ class TestResidentKeyPersistance(object):
@pytest.mark.parametrize("do_reboot", [False, True])
def test_user_info_returned_when_using_allowlist(self, device, MC_RK_Res, GA_RK_Res, do_reboot):
assert "id" in GA_RK_Res.user.keys()
-
+
allow_list = [
{
"id": MC_RK_Res.auth_data.credential_data.credential_id[:],
@@ -66,7 +66,7 @@ class TestResidentKeyPersistance(object):
class TestResidentKeyAfterReset(object):
def test_with_allow_list_after_reset(self, device, MC_RK_Res, GA_RK_Res):
assert "id" in GA_RK_Res.user.keys()
-
+
allow_list = [
{
"id": MC_RK_Res.auth_data.credential_data.credential_id[:],
diff --git a/tests/standard/fido2/user_presence/test_user_presence.py b/tests/standard/fido2/user_presence/test_user_presence.py
index c9904b2..0b74d24 100644
--- a/tests/standard/fido2/user_presence/test_user_presence.py
+++ b/tests/standard/fido2/user_presence/test_user_presence.py
@@ -34,7 +34,10 @@ class TestUserPresence(object):
device.sendGA(
*FidoRequest(GARes, timeout=event, on_keepalive=None).toGA()
)
- assert e.value.code == CtapError.ERR.KEEPALIVE_CANCEL
+ '''
+ The CTAP states that if no UP has been activated, CTAP2_ERR_OPERATION_DENIED should be returned.
+ '''
+ assert e.value.code == CtapError.ERR.OPERATION_DENIED
@pytest.mark.skipif(
not "trezor" in sys.argv, reason="Only Trezor supports decline."
@@ -71,6 +74,10 @@ class TestUserPresence(object):
)
assert e.value.code == CtapError.ERR.INVALID_OPTION
+ '''
+ This test makes no sense since device.sendGA is blocking
+ '''
+ '''
def test_user_presence_permits_only_one_request(self, device, MCRes, GARes):
print("ACTIVATE UP ONCE")
device.sendGA(*FidoRequest(GARes).toGA())
@@ -81,3 +88,4 @@ class TestUserPresence(object):
*FidoRequest(GARes, timeout=event, on_keepalive=None).toGA()
)
assert e.value.code == CtapError.ERR.KEEPALIVE_CANCEL
+ '''
\ No newline at end of file
diff --git a/tests/standard/transport/test_hid.py b/tests/standard/transport/test_hid.py
index c79c933..6203a00 100644
--- a/tests/standard/transport/test_hid.py
+++ b/tests/standard/transport/test_hid.py
@@ -105,6 +105,16 @@ class TestHID(object):
device.send_raw("\x01")
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
+ '''
+ CTAP spec states: If an application tries to access the device from a
+ different channel while the device is busy with a transaction, that request
+ will immediately fail with a busy-error message sent to the requesting channel.
+
+ This test tries to send an init from a different cid while the authenticator
+ is busy with the ping. In my understanding, based on the sentence above,
+ this should throw an error. Therefore the test does not make sense.
+ '''
+ '''
def test_ping_abort_from_different_cid(self, device, check_timeouts=False):
oldcid = device.cid()
newcid = "\x11\x22\x33\x44"
@@ -123,6 +133,7 @@ class TestHID(object):
# print('wait for timeout')
cmd, r = device.recv_raw() # timeout response
assert cmd == 0xBF
+ '''
def test_timeout(self, device):
device.send_data(CTAPHID.INIT, "\x11\x22\x33\x44\x55\x66\x77\x88")
--
2.33.0

View File

@ -24,6 +24,7 @@ rsource "embunit/Kconfig"
rsource "entropy_source/Kconfig"
rsource "eepreg/Kconfig"
rsource "event/Kconfig"
rsource "fido2/Kconfig"
rsource "fmt/Kconfig"
rsource "frac/Kconfig"
rsource "hashes/Kconfig"

View File

@ -779,4 +779,35 @@ ifneq (,$(filter dbgpin,$(USEMODULE)))
FEATURES_REQUIRED += dbgpin
endif
ifneq (,$(filter fido2_ctap_%,$(USEMODULE)))
USEMODULE += fido2_ctap_transport
USEMODULE += fido2_ctap
ifneq (,$(filter fido2_ctap_transport_hid,$(USEMODULE)))
USEMODULE += usbus_hid
DISABLE_MODULE += auto_init_usbus
endif
endif
ifneq (,$(filter fido2_ctap,$(USEMODULE)))
FEATURES_REQUIRED += periph_flashpage
FEATURES_REQUIRED += periph_gpio_irq
USEPKG += tiny-asn1
USEPKG += tinycbor
USEPKG += micro-ecc
INCLUDE += $(RIOTPKG)/tinycbor
USEMODULE += mtd_flashpage
USEMODULE += mtd_write_page
USEMODULE += ztimer_msec
USEMODULE += event
USEMODULE += event_timeout
USEMODULE += prng_sha256prng
USEMODULE += cipher_modes
USEMODULE += crypto_aes_256
USEMODULE += hashes
USEMODULE += fido2
endif
include $(RIOTBASE)/sys/test_utils/Makefile.dep

7
sys/fido2/Kconfig Normal file
View File

@ -0,0 +1,7 @@
# Copyright (C) 2021 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.
rsource "ctap/Kconfig"

5
sys/fido2/Makefile Normal file
View File

@ -0,0 +1,5 @@
ifneq (,$(filter fido2_ctap%,$(USEMODULE)))
DIRS += ctap
endif
include $(RIOTBASE)/Makefile.base

96
sys/fido2/ctap/Kconfig Normal file
View File

@ -0,0 +1,96 @@
# Copyright (C) 2021 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.
menuconfig KCONFIG_USEMODULE_FIDO2_CTAP
bool "FIDO2 CTAP"
depends on USEMODULE_FIDO2_CTAP
help
Configure a FIDO2 CTAP authenticator via KConfig.
if KCONFIG_USEMODULE_FIDO2_CTAP
config FIDO2_CTAP_STACK_SIZE
int "CTAP thread stack size"
default 15000
config FIDO2_CTAP_DEVICE_AAGUID
string "AAGUID of the CTAP authenticator"
default "9c295865fa2c36b705a42320af9c8f16"
help
The AAGUID is identifying the type of the authenticator (e.g manufacturer
and model). The AAGUID needs to be 128 bits long. The default value here
is a fallback value that was randomly generated.
config FIDO2_CTAP_DISABLE_UP
bool "Disable user presence tests"
help
When set, the authenticator will not ask for permission before creating
a new credential pair or authenticating.
config FIDO2_CTAP_DISABLE_LED
bool "Disable LED animations"
help
When set, the authenticator will not use LED's.
config FIDO2_CTAP_UP_TIMEOUT
int "Seconds until user presence test times out"
default 15
config FIDO2_CTAP_UP_BUTTON_PORT
int "Port of user presence button"
depends on !FIDO2_CTAP_DISABLE_UP
default -1
config FIDO2_CTAP_UP_BUTTON_PIN
int "Pin of user presence button"
depends on !FIDO2_CTAP_DISABLE_UP
default -1
choice
bool "User presence button mode"
depends on !FIDO2_CTAP_DISABLE_UP
default FIDO2_CTAP_UP_BUTTON_MODE_IN_PU
config FIDO2_CTAP_UP_BUTTON_MODE_IN_PU
bool "GPIO_IN_PU"
help
Configure as input with pull-up resistor
config FIDO2_CTAP_UP_BUTTON_MODE_IN_PD
bool "GPIO_IN_PD"
help
Configure as input with pull-down resistor
config FIDO2_CTAP_UP_BUTTON_MODE_IN
bool "GPIO_IN"
help
Configure as input without pull resistor
endchoice
choice
bool "User presence button pin flank"
depends on !FIDO2_CTAP_DISABLE_UP
default FIDO2_CTAP_UP_BUTTON_FLANK_FALLING
config FIDO2_CTAP_UP_BUTTON_FLANK_FALLING
bool "GPIO_FALLING"
config FIDO2_CTAP_UP_BUTTON_FLANK_RISING
bool "GPIO_RISING"
endchoice
config FIDO2_CTAP_FLASH_START_PAGE
int "First flash page to store data in"
default -1
help
Configuring this incorrectly can lead to firmware corruption so make sure
the flash page is located after the firmware.
rsource "transport/Kconfig"
endif # KCONFIG_USEMODULE_FIDO2_CTAP

7
sys/fido2/ctap/Makefile Normal file
View File

@ -0,0 +1,7 @@
MODULE := fido2_ctap
ifneq (,$(filter fido2_ctap_%,$(USEMODULE)))
DIRS += transport
endif
include $(RIOTBASE)/Makefile.base

1850
sys/fido2/ctap/ctap.c Normal file

File diff suppressed because it is too large Load Diff

1740
sys/fido2/ctap/ctap_cbor.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,343 @@
/*
* Copyright (C) 2021 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 fido2_ctap_crypto
* @{
* @file
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
* @}
*/
#include <string.h>
#include "assert.h"
#include "random.h"
#include "crypto/ciphers.h"
#include "crypto/modes/ccm.h"
#include "crypto/modes/cbc.h"
#include "uECC.h"
#include "tiny-asn1.h"
#include "fido2/ctap/ctap_crypto.h"
#include "fido2/ctap.h"
#include "fido2/ctap/ctap_utils.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
/**
* @brief Parse signature into ASN.1 DER format
*/
static int _sig_to_der_format(uint8_t *r, uint8_t *s, uint8_t *sig,
size_t *sig_len);
/**
* @brief Random number generator
*
* wrapper for @ref fido2_ctap_crypto_prng
*/
static int _RNG(uint8_t *dest, unsigned size);
int fido2_ctap_crypto_init(void)
{
uECC_set_rng(&_RNG);
return CTAP2_OK;
}
static int _RNG(uint8_t *dest, unsigned size)
{
fido2_ctap_crypto_prng(dest, (size_t)size);
return 1;
}
int fido2_ctap_crypto_prng(uint8_t *buf, size_t len)
{
random_bytes(buf, len);
return CTAP2_OK;
}
int fido2_ctap_crypto_sha256_init(sha256_context_t *ctx)
{
sha256_init(ctx);
return CTAP2_OK;
}
int fido2_ctap_crypto_sha256_update(sha256_context_t *ctx, const void *data, size_t len)
{
sha256_update(ctx, data, len);
return CTAP2_OK;
}
int fido2_ctap_crypto_sha256_final(sha256_context_t *ctx, void *digest)
{
sha256_final(ctx, digest);
return CTAP2_OK;
}
int fido2_ctap_crypto_sha256(const void *data, size_t len,
void *digest)
{
sha256(data, len, digest);
return CTAP2_OK;
}
int fido2_ctap_crypto_hmac_sha256_init(hmac_context_t *ctx, const void *key,
size_t key_length)
{
hmac_sha256_init(ctx, key, key_length);
return CTAP2_OK;
}
int fido2_ctap_crypto_hmac_sha256_update(hmac_context_t *ctx, const void *data, size_t len)
{
hmac_sha256_update(ctx, data, len);
return CTAP2_OK;
}
int fido2_ctap_crypto_hmac_sha256_final(hmac_context_t *ctx, void *digest)
{
hmac_sha256_final(ctx, digest);
return CTAP2_OK;
}
int fido2_ctap_crypto_hmac_sha256(const void *key,
size_t key_length, const void *data, size_t len,
void *digest)
{
hmac_sha256(key, key_length, data, len, digest);
return CTAP2_OK;
}
int fido2_ctap_crypto_ecdh(uint8_t *out, size_t len,
ctap_crypto_pub_key_t *pub_key, uint8_t *priv_key, size_t key_len)
{
assert(len == CTAP_CRYPTO_KEY_SIZE);
assert(key_len == CTAP_CRYPTO_KEY_SIZE);
int ret;
const struct uECC_Curve_t *curve = uECC_secp256r1();
ret = uECC_shared_secret((uint8_t *)pub_key, priv_key, out, curve);
if (ret == 0) {
return CTAP1_ERR_OTHER;
}
return CTAP2_OK;
}
int fido2_ctap_crypto_aes_enc(uint8_t *out, size_t *out_len, uint8_t *in,
size_t in_len, const uint8_t *key,
size_t key_len)
{
assert(*out_len >= in_len);
int ret;
cipher_t cipher;
uint8_t iv[16] = { 0 };
ret = cipher_init(&cipher, CIPHER_AES, key, key_len);
if (ret < 0) {
return CTAP1_ERR_OTHER;
}
ret = cipher_encrypt_cbc(&cipher, iv, in, in_len, out);
if (ret < 0) {
return CTAP1_ERR_OTHER;
}
return CTAP2_OK;
}
int fido2_ctap_crypto_aes_dec(uint8_t *out, size_t *out_len, uint8_t *in,
size_t in_len, const uint8_t *key,
size_t key_len)
{
assert(*out_len >= in_len);
int ret;
cipher_t cipher;
uint8_t iv[16] = { 0 };
ret = cipher_init(&cipher, CIPHER_AES, key, key_len);
if (ret < 0) {
return CTAP1_ERR_OTHER;
}
ret = cipher_decrypt_cbc(&cipher, iv, in, in_len, out);
if (ret < 0) {
return CTAP1_ERR_OTHER;
}
return CTAP2_OK;
}
int fido2_ctap_crypto_aes_ccm_enc(uint8_t *out, size_t out_len,
const uint8_t *in, size_t in_len,
uint8_t *auth_data, size_t auth_data_len,
uint8_t mac_len, uint8_t length_encoding,
const uint8_t *nonce, size_t nonce_len,
const uint8_t *key, size_t key_len)
{
assert(key_len == CTAP_CRED_KEY_LEN);
cipher_t cipher;
int ret;
ret = cipher_init(&cipher, CIPHER_AES_128, key, key_len);
if (ret != 1) {
return CTAP1_ERR_OTHER;
}
ret = cipher_encrypt_ccm(&cipher, auth_data, auth_data_len, mac_len,
length_encoding, nonce, nonce_len,
in, in_len, out);
if (ret < 0) {
return CTAP1_ERR_OTHER;
}
return CTAP2_OK;
}
int fido2_ctap_crypto_aes_ccm_dec(uint8_t *out, size_t out_len,
const uint8_t *in, size_t in_len,
uint8_t *auth_data, size_t auth_data_len,
uint8_t mac_len, uint8_t length_encoding,
const uint8_t *nonce, size_t nonce_len,
const uint8_t *key, size_t key_len)
{
assert(key_len == CTAP_CRED_KEY_LEN);
cipher_t cipher;
int ret, len;
ret = cipher_init(&cipher, CIPHER_AES, key, key_len);
if (ret != 1) {
return CTAP1_ERR_OTHER;
}
len = cipher_decrypt_ccm(&cipher, auth_data, auth_data_len,
mac_len, length_encoding, nonce, nonce_len,
in, in_len, out);
if (len < 0) {
return CTAP1_ERR_OTHER;
}
return CTAP2_OK;
}
int fido2_ctap_crypto_gen_keypair(ctap_crypto_pub_key_t *pub_key,
uint8_t *priv_key, size_t len)
{
assert(len == CTAP_CRYPTO_KEY_SIZE);
int ret;
const struct uECC_Curve_t *curve = uECC_secp256r1();
ret = uECC_make_key((uint8_t *)pub_key, priv_key, curve);
if (ret == 0) {
return CTAP1_ERR_OTHER;
}
return CTAP2_OK;
}
int fido2_ctap_crypto_get_sig(uint8_t *hash, size_t hash_len, uint8_t *sig,
size_t *sig_len, const uint8_t *key,
size_t key_len)
{
assert(*sig_len >= CTAP_CRYPTO_ES256_DER_MAX_SIZE);
assert(key_len == CTAP_CRYPTO_KEY_SIZE);
/**
* +1 to pad with leading zero to prevent integer from being interpreted as
* negative (e.g. MSB of r >= 0x80)
*/
uint8_t r[CTAP_CRYPTO_KEY_SIZE + 1] = { 0 };
uint8_t s[CTAP_CRYPTO_KEY_SIZE + 1] = { 0 };
int ret;
const struct uECC_Curve_t *curve = uECC_secp256r1();
ret = uECC_sign(key, hash, hash_len, sig, curve);
if (ret == 0) {
return CTAP1_ERR_OTHER;
}
memcpy(r + 1, sig, CTAP_CRYPTO_KEY_SIZE);
memcpy(s + 1, sig + CTAP_CRYPTO_KEY_SIZE, CTAP_CRYPTO_KEY_SIZE);
ret = _sig_to_der_format(r, s, sig, sig_len);
if (ret != CTAP2_OK) {
return ret;
}
return CTAP2_OK;
}
static int _sig_to_der_format(uint8_t *r, uint8_t *s, uint8_t *sig,
size_t *sig_len)
{
asn1_tree t;
asn1_tree c1;
asn1_tree c2;
uint8_t pad_s, pad_r;
int ret;
/**
* if MSB >= 0x80, pad with leading zero byte in order to have number
* interpreted as positive.
*/
pad_r = ((r[1] & 0x80) == 0x80);
pad_s = ((s[1] & 0x80) == 0x80);
memset(sig, 0, *sig_len);
list_init(&t);
list_init(&c1);
list_init(&c2);
t.type = ASN1_TYPE_SEQUENCE;
c1.type = ASN1_TYPE_INTEGER;
c1.length = 0x20 + pad_r;
c1.data = pad_r ? r : r + 1;
ret = add_child(&t, &c1);
if (ret < 0) {
return CTAP1_ERR_OTHER;
}
c2.type = ASN1_TYPE_INTEGER;
c2.length = 0x20 + pad_s;
c2.data = pad_s ? s : s + 1;
ret = add_child(&t, &c2);
if (ret < 0) {
return CTAP1_ERR_OTHER;
}
*sig_len = der_encode(&t, sig, *sig_len);
return CTAP2_OK;
}

156
sys/fido2/ctap/ctap_mem.c Normal file
View File

@ -0,0 +1,156 @@
/*
* Copyright (C) 2021 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 fido2_ctap_mem
* @{
* @file
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
* @}
*/
#include <string.h>
#include "mtd.h"
#include "mtd_flashpage.h"
#include "fido2/ctap/ctap_mem.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
/**
* @brief MTD device descriptor initialized with flash-page driver
*/
static mtd_dev_t _mtd_dev = MTD_FLASHPAGE_INIT_VAL(CTAP_FLASH_PAGES_PER_SECTOR);
/**
* @brief Max amount of resident keys that can be stored
*/
static uint16_t _max_rk_amnt;
/**
* @brief Check if flash region is erased
*/
static bool _flash_is_erased(int page, int offset, size_t len);
/**
* @brief Get amount of flashpages
*/
static unsigned _amount_of_flashpages(void);
int fido2_ctap_mem_init(void)
{
int ret;
ret = mtd_init(&_mtd_dev);
if (ret < 0) {
return ret;
}
for (unsigned i = CTAP_FLASH_RK_START_PAGE; i < _amount_of_flashpages(); i++) {
_max_rk_amnt += flashpage_size(i) / CTAP_FLASH_RK_SZ;
}
return CTAP2_OK;
}
static unsigned _amount_of_flashpages(void)
{
return _mtd_dev.sector_count * _mtd_dev.pages_per_sector;
}
int fido2_ctap_mem_read(void *buf, uint32_t page, uint32_t offset, uint32_t len)
{
assert(buf);
int ret;
ret = mtd_read_page(&_mtd_dev, buf, page, offset, len);
if (ret < 0) {
return CTAP1_ERR_OTHER;
}
return CTAP2_OK;
}
int fido2_ctap_mem_write(const void *buf, uint32_t page, uint32_t offset, uint32_t len)
{
assert(buf);
int ret;
if (!_flash_is_erased(page, offset, len)) {
ret = mtd_write_page(&_mtd_dev, buf, page, offset, len);
if (ret < 0) {
return CTAP1_ERR_OTHER;
}
}
else {
ret = mtd_write_page_raw(&_mtd_dev, buf, page, offset, len);
if (ret < 0) {
return CTAP1_ERR_OTHER;
}
}
return CTAP2_OK;
}
static bool _flash_is_erased(int page, int offset, size_t len)
{
uint8_t *addr = ((uint8_t *)flashpage_addr(page) + offset);
for (size_t i = 0; i < len; i++) {
if (addr[i] != FLASHPAGE_ERASE_STATE) {
return false;
}
}
return true;
}
uint16_t fido2_ctap_mem_get_max_rk_amount(void)
{
return _max_rk_amnt;
}
int fido2_ctap_mem_get_flashpage_number_of_rk(uint16_t rk_idx)
{
uint16_t idx = 0;
for (unsigned i = CTAP_FLASH_RK_START_PAGE; i < _amount_of_flashpages(); i++) {
idx += flashpage_size(i) / CTAP_FLASH_RK_SZ;
if (idx >= rk_idx) {
return i;
}
}
return -1;
}
int fido2_ctap_mem_get_offset_of_rk_into_flashpage(uint16_t rk_idx)
{
uint16_t idx = 0;
for (unsigned i = CTAP_FLASH_RK_START_PAGE; i < _amount_of_flashpages(); i++) {
uint16_t old_idx = idx;
idx += flashpage_size(i) / CTAP_FLASH_RK_SZ;
if (idx >= rk_idx) {
return CTAP_FLASH_RK_SZ * (rk_idx - old_idx);
}
}
return -1;
}

122
sys/fido2/ctap/ctap_utils.c Normal file
View File

@ -0,0 +1,122 @@
/*
* Copyright (C) 2021 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 fido2_ctap_utils
* @{
* @file
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
* @}
*/
#include <string.h>
#include "ztimer.h"
#include "fido2/ctap.h"
#include "fido2/ctap/ctap_utils.h"
#if !IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_UP)
#include "periph/gpio.h"
#endif
#define ENABLE_DEBUG (0)
#include "debug.h"
#if !IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_UP)
/**
* @brief Flag holding information if user is present or not
*/
static bool _user_present = false;
/**
* @brief GPIO pin to use for user presence test
*/
static gpio_t _pin;
/**
* @brief Button callback function
*/
static void _gpio_cb(void *arg);
int fido2_ctap_utils_init_gpio_pin(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank)
{
if (gpio_init_int(pin, mode, flank, _gpio_cb, NULL) < 0) {
return CTAP1_ERR_OTHER;
}
_pin = pin;
return CTAP2_OK;
}
int fido2_ctap_utils_user_presence_test(void)
{
int ret;
gpio_irq_enable(_pin);
#if !IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_LED)
fido2_ctap_utils_led_animation();
#endif
ret = _user_present ? CTAP2_OK : CTAP2_ERR_ACTION_TIMEOUT;
gpio_irq_disable(_pin);
_user_present = false;
return ret;
}
static void _gpio_cb(void *arg)
{
(void)arg;
_user_present = true;
}
#if !IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_LED)
void fido2_ctap_utils_led_animation(void)
{
uint32_t start = ztimer_now(ZTIMER_MSEC);
uint32_t diff = 0;
uint32_t delay = 500;
while (!_user_present && diff < CTAP_UP_TIMEOUT) {
#ifdef LED0_TOGGLE
LED0_TOGGLE;
#endif
#ifdef LED1_TOGGLE
LED1_TOGGLE;
#endif
#ifdef LED3_TOGGLE
LED3_TOGGLE;
#endif
#ifdef LED2_TOGGLE
LED2_TOGGLE;
#endif
ztimer_sleep(ZTIMER_MSEC, delay);
diff = ztimer_now(ZTIMER_MSEC) - start;
}
#ifdef LED0_TOGGLE
LED0_OFF;
#endif
#ifdef LED1_TOGGLE
LED1_OFF;
#endif
#ifdef LED3_TOGGLE
LED3_OFF;
#endif
#ifdef LED2_TOGGLE
LED2_OFF;
#endif
}
#endif /* !IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_LED) */
#endif /* CONFIG_FIDO2_CTAP_DISABLE_UP */

View File

@ -0,0 +1,7 @@
# Copyright (C) 2021 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.
rsource "hid/Kconfig"

View File

@ -0,0 +1,7 @@
MODULE := fido2_ctap_transport
ifneq (,$(filter fido2_ctap_transport_hid,$(USEMODULE)))
DIRS += hid
endif
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,102 @@
/*
* Copyright (C) 2021 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 fido2_ctap_transport
* @{
* @file
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
* @}
*/
#include "event/timeout.h"
#include "fido2/ctap/transport/ctap_transport.h"
#include "fido2/ctap/ctap.h"
#if IS_USED(MODULE_FIDO2_CTAP_TRANSPORT_HID)
#include "usb/usbus.h"
#include "usb/usbus/hid_io.h"
#include "fido2/ctap/transport/hid/ctap_hid.h"
#endif
#define ENABLE_DEBUG (0)
#include "debug.h"
#if IS_USED(MODULE_FIDO2_CTAP_TRANSPORT_HID)
/**
* @brief CTAPHID timeout handler
*/
static void _ctap_hid_timeout_cb(event_t *arg);
/**
* @brief CTAPHID timeout event
*/
static event_t _ctap_hid_timeout_event = { .handler = _ctap_hid_timeout_cb };
/**
* @brief CTAPHID event_timeout object
*/
static event_timeout_t _ctap_hid_event_timeout;
#endif
/**
* @brief CTAP stack
*/
static char _ctap_stack[CTAP_STACKSIZE];
/**
* @brief CTAP transport event queue
*/
static event_queue_t _queue;
static void *_event_loop(void *arg)
{
(void)arg;
int ret;
ret = fido2_ctap_init();
if (ret < 0) {
return NULL;
}
event_queue_init(&_queue);
#if IS_USED(MODULE_FIDO2_CTAP_TRANSPORT_HID)
event_timeout_init(&_ctap_hid_event_timeout, &_queue, &_ctap_hid_timeout_event);
event_timeout_set(&_ctap_hid_event_timeout, CTAP_HID_TRANSACTION_TIMEOUT);
#endif
event_loop(&_queue);
return NULL;
}
#if IS_USED(MODULE_FIDO2_CTAP_TRANSPORT_HID)
static void _ctap_hid_timeout_cb(event_t *arg)
{
(void)arg;
fido2_ctap_transport_hid_check_timeouts();
event_timeout_set(&_ctap_hid_event_timeout, CTAP_HID_TRANSACTION_TIMEOUT);
}
#endif
void fido2_ctap_transport_init(void)
{
#if IS_USED(MODULE_FIDO2_CTAP_TRANSPORT_HID)
fido2_ctap_transport_hid_init(&_queue);
#endif
int ret = thread_create(_ctap_stack, sizeof(_ctap_stack), CTAP_TRANSPORT_PRIO,
THREAD_CREATE_STACKTEST, _event_loop, NULL,
"fido2_ctap_transport_loop");
(void)ret;
assert(ret > 0);
}

View File

@ -0,0 +1,23 @@
# Copyright (C) 2021 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.
menuconfig KCONFIG_USEMODULE_FIDO2_CTAP_TRANSPORT_HID
bool "FIDO2 CTAP TRANSPORT HID"
depends on USEMODULE_FIDO2_CTAP_TRANSPORT_HID
help
Configure a FIDO2 CTAP authenticator via KConfig.
if KCONFIG_USEMODULE_FIDO2_CTAP_TRANSPORT_HID
config FIDO2_CTAP_TRANSPORT_HID_TRANSACTION_TIMEOUT
int "CTAPHID Transaction timeout in milliseconds"
default 500
help
A CTAPHID transaction has to be completed within a specified period
of time to prevent the authenticator from being locked by a
stalling application.
endif # KCONFIG_USEMODULE_FIDO2_CTAP_TRANSPORT_HID

View File

@ -0,0 +1,3 @@
MODULE := fido2_ctap_transport_hid
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,723 @@
/*
* Copyright (C) 2021 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 fido2_ctap_transport_hid
* @{
* @file
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
* @}
*/
#include <string.h>
#include "xtimer.h"
#include "usb/usbus.h"
#include "usb/usbus/hid_io.h"
#include "fido2/ctap.h"
#include "fido2/ctap/transport/hid/ctap_hid.h"
#include "fido2/ctap/ctap_utils.h"
#define ENABLE_DEBUG (0)
#include "debug.h"
/**
* @brief CTAP HID report descriptor
*
* CTAP specification (version 20190130) section 8.1.8.2
*/
const uint8_t _hid_report_desc[] = {
0x06, 0xD0, 0xF1, /**< HID_UsagePage ( FIDO_USAGE_PAGE ) */
0x09, 0x01, /**< HID_Usage ( FIDO_USAGE_CTAPHID ) */
0xA1, 0x01, /**< HID_Collection ( HID_Application ) */
0x09, 0x20, /**< HID_Usage ( FIDO_USAGE_DATA_IN ) */
0x15, 0x00, /**< HID_LogicalMin ( 0 ) */
0x26, 0xFF, 0x00, /**< HID_LogicalMaxS ( 0xff ) */
0x75, 0x08, /**< HID_ReportSize ( 8 ) */
0x95, 0x40, /**< HID_ReportCount ( HID_INPUT_REPORT_BYTES ) */
0x81, 0x02, /**< HID_Input ( HID_Data | HID_Absolute | HID_Variable ) */
0x09, 0x21, /**< HID_Usage ( FIDO_USAGE_DATA_OUT ) */
0x15, 0x00, /**< HID_LogicalMin ( 0 ) */
0x26, 0xFF, 0x00, /**< HID_LogicalMaxS ( 0xff ) */
0x75, 0x08, /**< HID_ReportSize ( 8 ) */
0x95, 0x40, /**< HID_ReportCount ( HID_OUTPUT_REPORT_BYTES ) */
0x91, 0x02, /**< HID_Output ( HID_Data | HID_Absolute | HID_Variable ) */
0xC0, /**< HID_EndCollection */
};
/**
* @brief CTAP_HID buffer struct
*
*/
typedef struct {
uint32_t cid; /**< channel identifier */
uint8_t cmd; /**< CTAP_HID command */
uint8_t buffer[CTAP_HID_BUFFER_SIZE]; /**< data buffer */
uint16_t offset; /**< current offset into data buffer */
int16_t seq; /**< current sequence number */
uint16_t bcnt; /**< expected amount of bytes to be received */
uint8_t err; /**< error type if error */
bool is_locked; /**< buffer is locked by transaction */
bool should_cancel; /**< flag if current transaction should be cancelled */
} ctap_hid_state_t;
/**
* @brief Serialize data and transmit it via USB HID layer
*/
static void _ctap_hid_write(uint8_t cmd, uint32_t cid, const void *_data, size_t size);
/**
* @brief CTAPHID_CBOR command
*
* CTAP specification (version 20190130) section 8.1.9.1.2
*/
static void _handle_cbor_packet(uint8_t cmd, uint32_t cid, uint8_t *buf, uint16_t bcnt);
/**
* @brief CTAPHID_INIT command
*
* CTAP specification (version 20190130) section 8.1.9.1.3
*/
static uint32_t _handle_init_packet(uint32_t cid, uint16_t bcnt,
const uint8_t *nonce);
/**
* @brief CTAPHID_WINK command
*
* CTAP specification (version 20190130) section 8.1.9.2.1
*/
static void _wink(uint32_t cid, uint8_t cmd);
/**
* @brief Encode response to CTAPHID_INIT command
*/
static void _send_init_response(uint32_t cid_old, uint32_t cid_new,
const uint8_t *nonce);
/**
* @brief Clear the CTAP packet buffer
*/
static void _clear_ctap_buffer(void);
/**
* @brief Buffer packet belonging to currently processed transaction
*/
static uint8_t _buffer_pkt(const ctap_hid_pkt_t *pkt);
/**
* @brief Send error code to cid
*/
static void _send_error_response(uint32_t cid, uint8_t err);
/**
* @brief Refresh the last_used timestamp for this cid
*/
static int8_t _refresh_cid(uint32_t cid);
/**
* @brief Allocate a new logical channel
*/
static int8_t _add_cid(uint32_t cid);
/**
* @brief Delete logical channel
*/
static int8_t _delete_cid(uint32_t cid);
/**
* @brief Check if a logical channel with cid exists
*/
static bool _cid_exists(uint32_t cid);
/**
* @brief Parse packet length from pkt
*/
static inline uint16_t _get_packet_len(const ctap_hid_pkt_t *pkt);
/**
* @brief Process CTAPHID transaction
*/
static void _process_transaction(event_t *arg);
/**
* @brief Check if packet is an initialization packet
*/
static inline bool _is_init_type_pkt(const ctap_hid_pkt_t *pkt);
/* usbus functionality */
/**
* @brief USB stack
*/
static char _usb_stack[USBUS_STACKSIZE];
/**
* @brief USBUS context
*/
static usbus_t _usbus;
/**
* @brief Indicate if authenticator is busy processing a transactions
*
* Transactions are atomic, therefore only 1 transaction can be processed at
* once
*/
static bool _is_busy = false;
/**
* @brief State for handling transactions
*/
static ctap_hid_state_t _state;
/**
* @brief Logical CTAPHID channels
*/
static ctap_hid_cid_t g_cids[CTAP_HID_CIDS_MAX];
/**
* @brief Incremental channel ids
*
* channel id 0 is reserved
*/
static uint32_t _cid = 1;
/**
* @brief CTAP transport layer event queue
*/
static event_queue_t *_queue;
/**
* @brief CTAPHID event
*/
static event_t _ctap_hid_event = { .handler = _process_transaction };
/**
* @brief USBUS context
*/
static usbus_t _usbus;
static void _usb_cb(void *arg)
{
(void)arg;
uint8_t buffer[CONFIG_USBUS_HID_INTERRUPT_EP_SIZE];
int read;
read = usb_hid_io_read(buffer, CONFIG_USBUS_HID_INTERRUPT_EP_SIZE);
if (read == CONFIG_USBUS_HID_INTERRUPT_EP_SIZE) {
fido2_ctap_transport_hid_handle_packet(buffer);
}
}
void fido2_ctap_transport_hid_init(event_queue_t *queue)
{
_queue = queue;
usbdev_t *usbdev = usbdev_get_ctx(0);
assert(usbdev);
usbus_init(&_usbus, usbdev);
usb_hid_io_init(&_usbus, _hid_report_desc, sizeof(_hid_report_desc));
usb_hid_io_set_rx_cb(_usb_cb, NULL);
usbus_create(_usb_stack, sizeof(_usb_stack), USBUS_PRIO, USBUS_TNAME, &_usbus);
}
void fido2_ctap_transport_hid_handle_packet(void *pkt_raw)
{
ctap_hid_pkt_t *pkt = (ctap_hid_pkt_t *)pkt_raw;
uint32_t cid = pkt->cid;
uint8_t status = CTAP_HID_BUFFER_STATUS_BUFFERING;
if (cid == 0x00) {
/* cid = 0x00 always invalid */
_send_error_response(cid, CTAP_HID_ERR_INVALID_CHANNEL);
return;
}
else if (_is_busy) {
if (_state.cid == cid) {
/* CTAP specification (version 20190130) section 8.1.5.3 */
if (_is_init_type_pkt(pkt)) {
if (pkt->init.cmd == CTAP_HID_COMMAND_INIT) {
/* abort */
_clear_ctap_buffer();
status = _buffer_pkt(pkt);
}
else if (_state.is_locked && pkt->init.cmd ==
CTAP_HID_COMMAND_CANCEL) {
_state.should_cancel = true;
}
/* random init type pkt. invalid sequence of pkts */
else {
_send_error_response(cid, CTAP_HID_ERR_INVALID_SEQ);
return;
}
}
/* packet for this cid is currently being worked */
else if (_state.is_locked) {
_send_error_response(cid, CTAP_HID_ERR_CHANNEL_BUSY);
return;
}
else {
/* buffer cont packets */
status = _buffer_pkt(pkt);
}
}
/* transactions are atomic. Deny all other cids if busy with one cid */
else {
_send_error_response(cid, CTAP_HID_ERR_CHANNEL_BUSY);
return;
}
}
else {
/* first init packet received starts a transaction */
if (_is_init_type_pkt(pkt)) {
_is_busy = true;
status = _buffer_pkt(pkt);
}
/* ignore rest */
}
if (status == CTAP_HID_BUFFER_STATUS_ERROR) {
_send_error_response(cid, _state.err);
_delete_cid(cid);
_clear_ctap_buffer();
_is_busy = false;
return;
}
/* pkt->init.bcnt bytes have been received. Transaction can now be processed */
if (status == CTAP_HID_BUFFER_STATUS_DONE) {
_state.is_locked = 1;
event_post(_queue, &_ctap_hid_event);
_is_busy = false;
}
else {
/* refresh timestamp of cid that is being buffered */
_refresh_cid(_state.cid);
}
}
static uint8_t _buffer_pkt(const ctap_hid_pkt_t *pkt)
{
if (_is_init_type_pkt(pkt)) {
/**
* broadcast cid only allowed for CTAP_HID_COMMAND_INIT
*/
if (pkt->cid == CTAP_HID_BROADCAST_CID &&
pkt->init.cmd != CTAP_HID_COMMAND_INIT) {
_send_error_response(pkt->cid, CTAP_HID_ERR_INVALID_CHANNEL);
}
/**
* received CTAP_HID_COMMAND_CANCEL while buffering packet.
* Cancel request.
*/
if (pkt->init.cmd == CTAP_HID_COMMAND_CANCEL && !_state.is_locked &&
pkt->cid == _state.cid) {
_state.err = CTAP2_ERR_KEEPALIVE_CANCEL;
return CTAP_HID_BUFFER_STATUS_ERROR;
}
_state.bcnt = _get_packet_len(pkt);
/* check for init transaction size described in CTAP specification
(version 20190130) section 8.1.9.1.3 */
if (pkt->init.cmd == CTAP_HID_COMMAND_INIT && _state.bcnt != 8) {
_state.err = CTAP_HID_ERR_INVALID_LEN;
return CTAP_HID_BUFFER_STATUS_ERROR;
}
/* don't allow transactions bigger than max buffer size */
if (_state.bcnt > CTAP_HID_BUFFER_SIZE) {
_state.err = CTAP_HID_ERR_INVALID_LEN;
return CTAP_HID_BUFFER_STATUS_ERROR;
}
uint16_t size = (_state.bcnt < CTAP_HID_INIT_PAYLOAD_SIZE) ?
_state.bcnt : CTAP_HID_INIT_PAYLOAD_SIZE;
_state.cmd = pkt->init.cmd;
_state.cid = pkt->cid;
_state.seq = -1;
memcpy(_state.buffer, pkt->init.payload, size);
_state.offset = size;
}
else {
int left = _state.bcnt - _state.offset;
int diff = left - CTAP_HID_CONT_PAYLOAD_SIZE;
_state.seq++;
/* seqs have to increase sequentially */
if (pkt->cont.seq != _state.seq) {
_state.err = CTAP_HID_ERR_INVALID_SEQ;
return CTAP_HID_BUFFER_STATUS_ERROR;
}
/* check for potential buffer overflow */
if (_state.offset + CTAP_HID_CONT_PAYLOAD_SIZE > CTAP_HID_BUFFER_SIZE) {
_state.err = CTAP_HID_ERR_INVALID_LEN;
return CTAP_HID_BUFFER_STATUS_ERROR;
}
if (diff <= 0) {
memcpy(_state.buffer + _state.offset, pkt->cont.payload, left);
_state.offset += left;
}
else {
memcpy(_state.buffer + _state.offset, pkt->cont.payload,
CTAP_HID_CONT_PAYLOAD_SIZE);
_state.offset += CTAP_HID_CONT_PAYLOAD_SIZE;
}
}
return _state.offset == _state.bcnt ?
CTAP_HID_BUFFER_STATUS_DONE : CTAP_HID_BUFFER_STATUS_BUFFERING;
}
static void _process_transaction(event_t *arg)
{
(void)arg;
uint8_t *buf = (uint8_t *)&_state.buffer;
uint32_t cid = _state.cid;
uint16_t bcnt = _state.bcnt;
uint8_t cmd = _state.cmd;
if (cmd == CTAP_HID_COMMAND_INIT) {
_handle_init_packet(cid, bcnt, buf);
}
else {
/* readding deleted cid */
if (!_cid_exists(cid) && _add_cid(cid) == -1) {
_send_error_response(cid, CTAP_HID_ERR_CHANNEL_BUSY);
}
else {
switch (cmd) {
case CTAP_HID_COMMAND_MSG:
/* not implemented as of now */
DEBUG("CTAP_HID: MSG COMMAND \n");
_send_error_response(cid, CTAP_HID_ERR_INVALID_CMD);
break;
case CTAP_HID_COMMAND_CBOR:
DEBUG("CTAP_HID: CBOR COMMAND \n");
_handle_cbor_packet(cmd, cid, buf, bcnt);
break;
case CTAP_HID_COMMAND_WINK:
DEBUG("CTAP_HID: wink \n");
_wink(cid, cmd);
break;
case CTAP_HID_COMMAND_PING:
DEBUG("CTAP_HID: PING \n");
_ctap_hid_write(cmd, cid, buf, bcnt);
break;
case CTAP_HID_COMMAND_CANCEL:
/*
* no transaction is currently being processed,
* no reason to send cancel
*/
break;
default:
_send_error_response(cid, CTAP_HID_ERR_INVALID_CMD);
DEBUG("Ctaphid: unknown command %u \n", cmd);
}
}
}
/* transaction done, cleanup */
_clear_ctap_buffer();
}
static uint32_t _handle_init_packet(uint32_t cid, uint16_t bcnt,
const uint8_t *nonce)
{
uint32_t cid_new = 0;
/* cid 0 is reserved */
if (cid == 0) {
_send_error_response(cid, CTAP_HID_ERR_INVALID_CHANNEL);
return 0;
}
/* check for len described in standard */
if (bcnt != 8) {
_send_error_response(cid, CTAP_HID_ERR_INVALID_LEN);
return 0;
}
/* create new channel */
if (cid == CTAP_HID_BROADCAST_CID) {
cid_new = _cid++;
if (_add_cid(cid_new) == -1) {
_send_error_response(cid, CTAP_HID_ERR_CHANNEL_BUSY);
return 0;
}
_send_init_response(cid, cid_new, nonce);
}
/* synchronize channel */
else {
cid_new = cid;
if (!_cid_exists(cid)) {
if (_add_cid(cid) == -1) {
/* reached cid limit */
_send_error_response(cid, CTAP_HID_ERR_CHANNEL_BUSY);
return 0;
}
}
_send_init_response(cid, cid, nonce);
}
return cid_new;
}
static void _handle_cbor_packet(uint8_t cmd, uint32_t cid, uint8_t *buf, uint16_t bcnt)
{
ctap_resp_t resp;
uint8_t err;
size_t size;
if (bcnt == 0) {
err = CTAP_HID_ERR_INVALID_LEN;
cmd = CTAP_HID_COMMAND_ERROR;
_ctap_hid_write(cmd, cid, &err, sizeof(err));
return;
}
memset(&resp, 0, sizeof(ctap_resp_t));
ctap_req_t req;
req.method = *buf;
req.buf = buf + 1;
req.len = bcnt - 1;
size = fido2_ctap_handle_request(&req, &resp);
/* transaction done, clear should_cancel flag */
_state.should_cancel = false;
if (resp.status == CTAP2_OK && size > 0) {
/* status + data */
_ctap_hid_write(cmd, cid, &resp, size + sizeof(resp.status));
}
else {
/* status only */
_ctap_hid_write(cmd, cid, &resp.status, sizeof(resp.status));
}
}
static inline bool _is_init_type_pkt(const ctap_hid_pkt_t *pkt)
{
return ((pkt->init.cmd & CTAP_HID_INIT_PACKET) == CTAP_HID_INIT_PACKET);
}
static void _clear_ctap_buffer(void)
{
memset(&_state, 0, sizeof(_state));
}
bool fido2_ctap_transport_hid_should_cancel(void)
{
return _state.should_cancel;
}
void fido2_ctap_transport_hid_check_timeouts(void)
{
uint64_t now = xtimer_now_usec64();
for (uint8_t i = 0; i < CTAP_HID_CIDS_MAX; i++) {
/* transaction timed out because cont packets didn't arrive in time */
if (_is_busy && g_cids[i].taken &&
(now - g_cids[i].last_used) >= CTAP_HID_TRANSACTION_TIMEOUT &&
_state.cid == g_cids[i].cid && !_state.is_locked) {
_send_error_response(g_cids[i].cid, CTAP_HID_ERR_MSG_TIMEOUT);
_delete_cid(g_cids[i].cid);
_clear_ctap_buffer();
_is_busy = false;
}
}
}
static int8_t _add_cid(uint32_t cid)
{
uint64_t oldest = xtimer_now_usec64();
int8_t index_oldest = -1;
for (int i = 0; i < CTAP_HID_CIDS_MAX; i++) {
if (!g_cids[i].taken) {
g_cids[i].taken = true;
g_cids[i].cid = cid;
g_cids[i].last_used = xtimer_now_usec64();
return CTAP_HID_OK;
}
if (g_cids[i].last_used < oldest) {
oldest = g_cids[i].last_used;
index_oldest = i;
}
}
/* remove oldest cid to make place for a new one */
if (index_oldest > -1) {
g_cids[index_oldest].taken = true;
g_cids[index_oldest].cid = cid;
g_cids[index_oldest].last_used = xtimer_now_usec64();
return CTAP_HID_OK;
}
return CTAP_HID_ERR_OTHER;
}
static int8_t _refresh_cid(uint32_t cid)
{
for (int i = 0; i < CTAP_HID_CIDS_MAX; i++) {
if (g_cids[i].cid == cid) {
g_cids[i].last_used = xtimer_now_usec64();
return CTAP_HID_OK;
}
}
return CTAP_HID_ERR_OTHER;
}
static int8_t _delete_cid(uint32_t cid)
{
for (int i = 0; i < CTAP_HID_CIDS_MAX; i++) {
if (g_cids[i].cid == cid) {
g_cids[i].taken = false;
g_cids[i].cid = 0;
return CTAP_HID_OK;
}
}
return CTAP_HID_ERR_OTHER;
}
static bool _cid_exists(uint32_t cid)
{
for (int i = 0; i < CTAP_HID_CIDS_MAX; i++) {
if (g_cids[i].cid == cid) {
return true;
}
}
return false;
}
static inline uint16_t _get_packet_len(const ctap_hid_pkt_t *pkt)
{
return (uint16_t)((pkt->init.bcnth << 8) | pkt->init.bcntl);
}
static void _wink(uint32_t cid, uint8_t cmd)
{
#if !IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_LED)
uint32_t delay = CTAP_HID_WINK_DELAY;
for (int i = 1; i <= 8; i++) {
#ifdef LED0_TOGGLE
LED0_TOGGLE;
xtimer_msleep(delay);
#endif
#ifdef LED1_TOGGLE
LED1_TOGGLE;
xtimer_msleep(delay);
#endif
#ifdef LED2_TOGGLE
LED2_TOGGLE;
xtimer_msleep(delay);
#endif
#ifdef LED3_TOGGLE
LED3_TOGGLE;
xtimer_msleep(delay);
#endif
delay /= 2;
}
#endif /* CONFIG_FIDO2_CTAP_DISABLE_LED */
_ctap_hid_write(cmd, cid, NULL, 0);
}
static void _send_error_response(uint32_t cid, uint8_t err)
{
DEBUG("ctap_trans_hid err resp: %02x \n", err);
_ctap_hid_write(CTAP_HID_COMMAND_ERROR, cid, &err, sizeof(err));
}
static void _send_init_response(uint32_t cid_old, uint32_t cid_new,
const uint8_t *nonce)
{
ctap_hid_init_resp_t resp;
memset(&resp, 0, sizeof(ctap_hid_init_resp_t));
resp.cid = cid_new;
resp.protocol_version = CTAP_HID_PROTOCOL_VERSION;
resp.version_major = 0;
resp.version_minor = 0;
resp.build_version = 0;
memcpy(resp.nonce, nonce, sizeof(resp.nonce));
uint8_t cmd = (CTAP_HID_INIT_PACKET | CTAP_HID_COMMAND_INIT);
resp.capabilities = CTAP_HID_CAPABILITY_CBOR | CTAP_HID_CAPABILITY_WINK
| CTAP_HID_CAPABILITY_NMSG;
_ctap_hid_write(cmd, cid_old, &resp, sizeof(ctap_hid_init_resp_t));
}
void _ctap_hid_write(uint8_t cmd, uint32_t cid, const void *_data, size_t len)
{
const uint8_t *data = (uint8_t *)_data;
uint8_t buf[CONFIG_USBUS_HID_INTERRUPT_EP_SIZE] = { 0 };
uint16_t bytes_written = 0;
uint8_t seq = 0;
uint8_t offset = 0;
memcpy(buf, &cid, sizeof(cid));
offset += sizeof(cid);
buf[offset++] = cmd;
/* high part of payload length first */
buf[offset++] = (len & 0xff00) >> 8;
buf[offset++] = (len & 0xff) >> 0;
if (data == NULL) {
usb_hid_io_write(buf, CONFIG_USBUS_HID_INTERRUPT_EP_SIZE);
return;
}
for (size_t i = 0; i < len; i++) {
if (offset == 0) {
memcpy(buf, &cid, sizeof(cid));
offset += sizeof(cid);
/* initialization packet */
if (bytes_written == 0) {
buf[offset++] = cmd;
buf[offset++] = (len & 0xff00) >> 8;
buf[offset++] = (len & 0xff) >> 0;
}
/* continuation packet */
else {
buf[offset++] = seq++;
}
}
buf[offset++] = data[i];
bytes_written++;
if (offset == CONFIG_USBUS_HID_INTERRUPT_EP_SIZE) {
usb_hid_io_write(buf, CONFIG_USBUS_HID_INTERRUPT_EP_SIZE);
offset = 0;
}
}
if (offset > 0) {
memset(buf + offset, 0, CONFIG_USBUS_HID_INTERRUPT_EP_SIZE - offset);
usb_hid_io_write(buf, CONFIG_USBUS_HID_INTERRUPT_EP_SIZE);
}
}

218
sys/fido2/doc.txt Normal file
View File

@ -0,0 +1,218 @@
/*
* Copyright (C) 2021 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.
*/
/**
* @defgroup fido2 FIDO2 - Fast Identity Online 2
* @ingroup sys
* @brief Description of the FIDO2 CTAP implementation in RIOT
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
*
* @warning This feature is experimental!
* This API is experimental and in an early state - expect changes.
*
* @warning The FIDO2 implementation currently stores private keys in plain text inside flash memory.
*
* FIDO2 is an authentication standard that seeks to solve the password problem
* by enabling passwordless authentication. Instead of using passwords to
* authenticate to web services, FIDO2 enables users to use common devices
* (authenticators) to create cryptographic credentials which are then used
* for authentication. FIDO2 consists of the [W3C Web Authentication
* specification (WebAuthn)](https://www.w3.org/TR/2019/REC-webauthn-1-20190304/)
* and the [Client to Authenticator Protocol (CTAP)](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html).
*
* **This code implements the FIDO2 CTAP protocol.**
*
* ### General
*
* Following is an overview of the entities of this implementation and their relationships:
*
*
* +-----------+ +-----------+ +-----------+ +-----------+
* | | | | | | | |
* | ctap_cbor | |ctap_crypto| | ctap_mem | |ctap_utils |
* | | | | | | | |
* +-----------+ +-----------+ +-----------+ +-----|-----+
* | | | |
* | | | |
* | +---------------------------+ |
* | | | |
* |---------| ctap |--------|
* | |
* +---------------------------+
* |
* +-------------|-------------+
* | |
* | ctap_transport |
* | |
* +---------------------------+
* |
* |
* +-----------+
* | |
* | ctap_hid |
* | |
* +-----------+
*
*
*
* **ctap_hid**
*
* USB Human Interface Device (USB HID) transport binding for CTAP (CTAPHID).
*
* Initializes the USBUS HID interface.
*
* Communicates with the USBUS USB HID interface through the USBUS HID IO interface.
*
* **ctap_transport**
*
* Initializes CTAP layer.
*
* Initializes CTAP event queue.
*
* Manages CTAP transport bindings (currently only CTAPHID).
*
* **ctap**
*
* Contains the main CTAP logic.
*
* Makes use of helpers for flash access, cryptographic operations and CBOR operations.
*
* **ctap_cbor**
*
* Helper containing functionality to parse and encode CBOR messages. Uses the [tinyCBOR](https://doc.riot-os.org/group__pkg__tinycbor.html) pkg.
*
* **ctap_crypto**
*
* Helper containing functionality for cryptographic operations.
*
* Abstraction for cryptographic operations (SHA256, HMAC-SHA-256, AES CCM).
*
* @note This abstraction exposes error return values which are currently not implemented in all cases by the RIOT crypto API.
*
* Abstraction for Elliptic curve cryptography (ECC) operations. Uses the [micro-ecc](https://api.riot-os.org/group__pkg__micro__ecc.html) pkg.
*
* Parsing of cryptographic signatures into ASN.1 DER format. Uses the [tiny-asn1](http://doc.riot-os.org/group__pkg__tiny-asn1.html) pkg.
*
* **ctap_mem**
*
* Abstraction for flash operations. Uses the RIOT [Flashpage MTD driver](http://api.riot-os.org/group__drivers__mtd__flashpage.html).
*
* Adds additional functionality to speedup flash accesses (e.g. by checking if a flash page is erased to avoid unnecessary erasures of flash pages).
*
* **ctap_utils**
*
* Abstraction for GPIO functionality and LED animations.
*
* ### Implemented features
* **FIDO2 CTAP methods**
*
* All methods defined in the FIDO2 CTAP specification are implemented. Specifically
* these are:
* * MakeCredential
* * GetAssertion
* * GetNextAssertion
* * GetInfo
* * ClientPIN
* * Reset
*
* For information about the FIDO2 CTAP methods refer to the [CTAP specification](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticator-api).
*
* **Transport bindings**
*
* The USB Human Interface Device (USB HID) transport binding is fully implemented.
*
* For more information about the available transport bindings refer to the [CTAP specification](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#transport-specific-bindings).
*
* **Credentials**
*
* Both types of credentials are supported. Resident and non resident.
*
* * Resident Credentials
* * Resident credentials are credentials stored on the authenticator.
* * This implementation stores resident keys in flash memory.
* @warning As of now the credentials (containing a private key) are stored
* in plain text inside flash memory
*
* * Non-resident credentials
* * Non-resident credentials are credentials that are stored by the relying
* party in encrypted form.
* * To encrypt the credentials, this implementation uses the RIOT [AES-CCM 128
* CCM implementation](https://api.riot-os.org/ccm_8h.html).
*
* For more information about the two types of credential refer to the [WebAuthn specification](https://www.w3.org/TR/2019/REC-webauthn-1-20190304/#sctn-credential-storage-modality)
*
* **Attestation types**
*
* Currently only self attestation is supported.
*
* For more information about available attestation types refer to the [WebAuthn specification](https://www.w3.org/TR/2019/REC-webauthn-1-20190304/#sctn-attestation-types).
*
* ### Unimplemented features
*
* **Backward compatibility with FIDO1**
*
* For more information about the backward compatibility of FIDO2 to FIDO1
* refer to the [CTAP specification](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#u2f-interoperability).
*
* **Support of further attestation types**
*
* Specifically these are:
* * Basic Attestation
* * Attestation Certificate Authority
* * Elliptic Curve based Direct Anonymous Attestation
*
* For more information about available attestation types refer to the [WebAuthn specification](https://www.w3.org/TR/2019/REC-webauthn-1-20190304/#sctn-attestation-types).
*
* **Support of further transport bindings**
*
* Specifically these are:
* * Near Field Communication (NFC)
* * Bluetooth Low Energy (BLE)
*
* For information about the available transport bindings refer to the
* [CTAP specification](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#transport-specific-bindings).
*
* **Extensions**
*
* For information about CTAP extensions refer to the [CTAP specification](https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#sctn-defined-extensions)
*
* **CTAP 2.1 support**
*
* None of the additions from the [CTAP 2.1 specification](https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html) are implemented.
*
* ### Testing
*
* Testing is done with the help of the fido2-tests package based on the [solokeys fido2-tests](https://github.com/solokeys/fido2-tests).
*
* For for more information about testing the FIDO2 CTAP implementation refer to the README of the test application (`/tests/sys_fido2_ctap`).
*
* **Todo**
*
* * The expected return codes of some tests were changed due to different opinions of how to interpret the CTAP2 specification. Refer to the [issue](https://github.com/solokeys/fido2-tests/issues/55) for more information. As of writing this the issue is still open.
*
* ### Configuration
*
* There are two CFLAGS which can be used to change the behavior of the FIDO2 CTAP implementation:
*
* * **FIDO2_CTAP_DISABLE_UP**: Disables the user presence test. User presence will always be set to true.
* This is helpful when running the fido2-tests as one doesn't have to click the button many times, as well as other use cases
* where no user presence test is wanted.
* * **FIDO2_CTAP_DISABLE_LED**: Disables LED animations which are used to indicate that user action is needed.
*
* The CFLAGS can either be set in the Makefile or configured via KConfig.
*
* ### Future work
*
* Future improvements / extensions to the FIDO2 CTAP implementation that should be implemented are:
*
* * Usage of secure elements if available to safely store private keys of FIDO2 credentials.
* * Usage of an extra cryptographic processor (e.g. [ARM CryptoCell](https://infocenter.nordicsemi.com/index.jsp?topic=%2Fps_nrf9160%2Fcryptocell.html)) to improve efficiency and drop dependency for ECC cryptography.
* * Support of further attestation types.
* * Support of further CTAP transport bindings.
* * Support of CTAP 2.1.
*/

219
sys/include/fido2/ctap.h Normal file
View File

@ -0,0 +1,219 @@
/*
* Copyright (C) 2021 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.
*/
/**
* @defgroup fido2_ctap CTAP
* @ingroup fido2
* @brief FIDO2 CTAP
*
* The Client-to-Authenticator Protocol (CTAP) is an application layer protocol
* for the communication between an authenticator and a host.
*
* @{
*
* @file
* @brief Public FIDO2 CTAP defines, structures and function declarations
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
*/
#ifndef FIDO2_CTAP_H
#define FIDO2_CTAP_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief CTAP max message size
*
* CTAP specification (version 20190130) section 6
*/
#define CTAP_MAX_MSG_SIZE 0x400
/**
* @brief CTAP status codes
*
* CTAP specification (version 20190130) section 6.3
* @{
*/
typedef enum {
CTAP2_OK = 0x00,
CTAP1_ERR_INVALID_COMMAND = 0x01,
CTAP1_ERR_INVALID_PARAMETER = 0x02,
CTAP1_ERR_INVALID_LENGTH = 0x03,
CTAP1_ERR_INVALID_SEQ = 0x04,
CTAP1_ERR_TIMEOUT = 0x05,
CTAP1_ERR_CHANNEL_BUSY = 0x06,
CTAP1_ERR_LOCK_REQUIRED = 0x0A,
CTAP1_ERR_INVALID_CHANNEL = 0x0B,
CTAP2_ERR_CBOR_PARSING = 0x10,
CTAP2_ERR_CBOR_UNEXPECTED_TYPE = 0x11,
CTAP2_ERR_INVALID_CBOR = 0x12,
CTAP2_ERR_INVALID_CBOR_TYPE = 0x13,
CTAP2_ERR_MISSING_PARAMETER = 0x14,
CTAP2_ERR_LIMIT_EXCEEDED = 0x15,
CTAP2_ERR_UNSUPPORTED_EXTENSION = 0x16,
CTAP2_ERR_TOO_MANY_ELEMENTS = 0x17,
CTAP2_ERR_EXTENSION_NOT_SUPPORTED = 0x18,
CTAP2_ERR_CREDENTIAL_EXCLUDED = 0x19,
CTAP2_ERR_CREDENTIAL_NOT_VALID = 0x20,
CTAP2_ERR_PROCESSING = 0x21,
CTAP2_ERR_INVALID_CREDENTIAL = 0x22,
CTAP2_ERR_USER_ACTION_PENDING = 0x23,
CTAP2_ERR_OPERATION_PENDING = 0x24,
CTAP2_ERR_NO_OPERATIONS = 0x25,
CTAP2_ERR_UNSUPPORTED_ALGORITHM = 0x26,
CTAP2_ERR_OPERATION_DENIED = 0x27,
CTAP2_ERR_KEY_STORE_FULL = 0x28,
CTAP2_ERR_NOT_BUSY = 0x29,
CTAP2_ERR_NO_OPERATION_PENDING = 0x2A,
CTAP2_ERR_UNSUPPORTED_OPTION = 0x2B,
CTAP2_ERR_INVALID_OPTION = 0x2C,
CTAP2_ERR_KEEPALIVE_CANCEL = 0x2D,
CTAP2_ERR_NO_CREDENTIALS = 0x2E,
CTAP2_ERR_USER_ACTION_TIMEOUT = 0x2F,
CTAP2_ERR_NOT_ALLOWED = 0x30,
CTAP2_ERR_PIN_INVALID = 0x31,
CTAP2_ERR_PIN_BLOCKED = 0x32,
CTAP2_ERR_PIN_AUTH_INVALID = 0x33,
CTAP2_ERR_PIN_AUTH_BLOCKED = 0x34,
CTAP2_ERR_PIN_NOT_SET = 0x35,
CTAP2_ERR_PIN_REQUIRED = 0x36,
CTAP2_ERR_PIN_POLICY_VIOLATION = 0x37,
CTAP2_ERR_PIN_TOKEN_EXPIRED = 0x38,
CTAP2_ERR_REQUEST_TOO_LARGE = 0x39,
CTAP2_ERR_ACTION_TIMEOUT = 0x3A,
CTAP2_ERR_UP_REQUIRED = 0x3B,
CTAP1_ERR_OTHER = 0x7F,
CTAP2_ERR_SPEC_LAST = 0xDF,
CTAP2_ERR_EXTENSION_FIRST = 0xE0,
CTAP2_ERR_EXTENSION_LAST = 0xEF,
CTAP2_ERR_VENDOR_FIRST = 0xF0,
CTAP2_ERR_VENDOR_LAST = 0xFF
} ctap_status_codes_t;
/** @} */
/**
* @brief CTAP request struct
*
* CTAP specification (version 20190130) section 6.1
*/
typedef struct {
uint8_t *buf; /**< Buffer holding CBOR encoded data */
size_t len; /**< Length of buf */
uint8_t method; /**< CTAP method identitifer */
} ctap_req_t;
/**
* @brief CTAP response struct
*
* CTAP specification (version 20190130) section 6.2
*/
typedef struct {
uint8_t status; /**< response status */
uint8_t data[CTAP_MAX_MSG_SIZE]; /**< response data */
} ctap_resp_t;
/**
* @brief Initialize ctap
*
* @return 0 for success
* @return negative error code otherwise
*/
int fido2_ctap_init(void);
/**
* @brief Handle CBOR encoded ctap request.
*
* This is a convenience function that checks @p req->method and calls the
* appropriate CTAP method handler function
*
* @param[in] req request struct
* @param[in] resp response struct
*
* @return Length of @p resp->data
*/
size_t fido2_ctap_handle_request(ctap_req_t *req, ctap_resp_t *resp);
/**
* @brief MakeCredential method
*
* CTAP specification (version 20190130) section 5.1
*
* @param[in] req CTAP request
* @param[in, out] resp CTAP response
*
* @return Length of @p resp->data
*/
size_t fido2_ctap_make_credential(ctap_req_t *req, ctap_resp_t *resp);
/**
* @brief GetAssertion method
*
* CTAP specification (version 20190130) section 5.2
*
* @param[in] req CTAP request
* @param[in, out] resp CTAP response
*
* @return Length of @p resp->data
*/
size_t fido2_ctap_get_assertion(ctap_req_t *req, ctap_resp_t *resp);
/**
* @brief GetNextAssertion method
*
* CTAP specification (version 20190130) section 5.3
*
* @param[in, out] resp CTAP response
*
* @return Length of @p resp->data
*/
size_t fido2_ctap_get_next_assertion(ctap_resp_t *resp);
/**
* @brief GetInfo method
*
* CTAP specification (version 20190130) section 5.4
*
* @param[in, out] resp CTAP response
*
* @return Length of @p resp->data
*/
size_t fido2_ctap_get_info(ctap_resp_t *resp);
/**
* @brief ClientPIN method
*
* CTAP specification (version 20190130) section 5.5
*
* @param[in] req CTAP request
* @param[in, out] resp CTAP response
*
* @return Length of @p resp->data
*/
size_t fido2_ctap_client_pin(ctap_req_t *req, ctap_resp_t *resp);
/**
* @brief Reset method
*
* CTAP specification (version 20190130) section 5.6
*
* @param[in, out] resp CTAP response
*
* @return Length of @p resp->data
*/
size_t fido2_ctap_reset(ctap_resp_t *resp);
#ifdef __cplusplus
}
#endif
#endif /* FIDO2_CTAP_H */
/** @} */

View File

@ -0,0 +1,674 @@
/*
* Copyright (C) 2021 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.
*/
/**
* @defgroup fido2_ctap_ctap FIDO2 CTAP
* @ingroup fido2_ctap
* @brief FIDO2 CTAP
*
* The Client-to-Authenticator Protocol (CTAP) is an application layer protocol
* for the communication between an authenticator and a host.
*
* @{
*
* @file
* @brief Internal FIDO2 CTAP defines, structures and function declarations
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
*/
#ifndef FIDO2_CTAP_CTAP_H
#define FIDO2_CTAP_CTAP_H
#include <stdint.h>
#include "mutex.h"
#include "cbor.h"
#include "assert.h"
#include "crypto/modes/ccm.h"
#include "timex.h"
#include "board.h"
#include "fido2/ctap.h"
#include "fido2/ctap/ctap_crypto.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Size of pin auth
*
* First 16 bytes of a HMAC-256.
*
* CTAP specification (version 20190130) section 5.5.8.2.
*/
#define CTAP_PIN_AUTH_SZ 16
/**
* @name CTAP methods
*
* @{
*/
#define CTAP_MAKE_CREDENTIAL 0x01 /**< authenticatorMakeCredential method */
#define CTAP_GET_ASSERTION 0x02 /**< authenticatorGetAssertion method */
#define CTAP_GET_INFO 0x04 /**< authenticatorGetInfo method */
#define CTAP_CLIENT_PIN 0x06 /**< authenticatorClientPIN method */
#define CTAP_RESET 0x07 /**< authenticatorReset method */
#define CTAP_GET_NEXT_ASSERTION 0x08 /**< authenticatorGetNextAssertion method */
/** @} */
/**
* @name CTAP authenticator data option flags
*
* @{
*/
#define CTAP_AUTH_DATA_FLAG_UP (1 << 0) /**< user present */
#define CTAP_AUTH_DATA_FLAG_UV (1 << 2) /**< user verified */
#define CTAP_AUTH_DATA_FLAG_AT (1 << 6) /**< attested credential data included */
#define CTAP_AUTH_DATA_FLAG_ED (1 << 7) /**< extension data included */
/** @} */
/**
* @name CTAP version flags
*
* @{
*/
#define CTAP_VERSION_FLAG_FIDO_PRE 0x01 /**< FIDO 2.1 flag */
#define CTAP_VERSION_FLAG_FIDO 0x02 /**< FIDO 2 flag */
#define CTAP_VERSION_FLAG_U2F_V2 0x04 /**< U2F V2 flag */
/** @} */
/**
* @name CTAP get info response options map CBOR key values
*
* All options are in the form key-value pairs with string IDs and
* boolean values
* @{
*/
#define CTAP_GET_INFO_RESP_OPTIONS_ID_PLAT "plat" /**< platform device string */
#define CTAP_GET_INFO_RESP_OPTIONS_ID_RK "rk" /**< resident key string */
#define CTAP_GET_INFO_RESP_OPTIONS_ID_CLIENT_PIN "clientPin" /**< client PIN string */
#define CTAP_GET_INFO_RESP_OPTIONS_ID_UP "up" /**< user presence string */
#define CTAP_GET_INFO_RESP_OPTIONS_ID_UV "uv" /**< user verification string */
/** @} */
/**
* @name CTAP get info options flags
*
* @{
*/
#define CTAP_INFO_OPTIONS_FLAG_PLAT (1 << 0) /**< platform device flag */
#define CTAP_INFO_OPTIONS_FLAG_RK (1 << 1) /**< resident key flag */
#define CTAP_INFO_OPTIONS_FLAG_CLIENT_PIN (1 << 2) /**< clientPIN flag */
#define CTAP_INFO_OPTIONS_FLAG_UP (1 << 3) /**< user presence flag */
#define CTAP_INFO_OPTIONS_FLAG_UV (1 << 4) /**< user verification flag */
/** @} */
/**
* @name CTAP Client PIN request subCommand CBOR key values
*
* @{
*/
#define CTAP_CP_REQ_SUB_COMMAND_GET_RETRIES 0x01 /**< getRetries subCommand */
#define CTAP_CP_REQ_SUB_COMMAND_GET_KEY_AGREEMENT 0x02 /**< getKeyAgreement subCommand */
#define CTAP_CP_REQ_SUB_COMMAND_SET_PIN 0x03 /**< setPIN subCommand */
#define CTAP_CP_REQ_SUB_COMMAND_CHANGE_PIN 0x04 /**< changePIN subCommand */
#define CTAP_CP_REQ_SUB_COMMAND_GET_PIN_TOKEN 0x05 /**< getPinToken subCommand */
/** @} */
/**
* @brief CTAP thread stack size
*/
#ifdef CONFIG_FIDO2_CTAP_STACK_SIZE
#define CTAP_STACKSIZE CONFIG_FIDO2_CTAP_STACK_SIZE
#else
#define CTAP_STACKSIZE 15000
#endif
#if !IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_UP)
/**
* @brief CTAP user presence button
*/
#if defined(CONFIG_FIDO2_CTAP_UP_BUTTON_PORT) && defined(CONFIG_FIDO2_CTAP_UP_BUTTON_PIN) && \
(CONFIG_FIDO2_CTAP_UP_BUTTON_PORT >= 0) && (CONFIG_FIDO2_CTAP_UP_BUTTON_PIN >= 0)
#define CTAP_UP_BUTTON GPIO_PIN(CONFIG_FIDO2_CTAP_UP_BUTTON_PORT, CONFIG_FIDO2_CTAP_UP_BUTTON_PIN)
#else
/* set default button if no button is configured */
#ifdef BTN0_PIN
#define CTAP_UP_BUTTON BTN0_PIN
/* if no button available disable UP test */
#else
#define CONFIG_FIDO2_CTAP_DISABLE_UP 1
#endif
#endif
/**
* @brief CTAP user presence button mode
*/
#if IS_ACTIVE(CONFIG_FIDO2_CTAP_UP_BUTTON_MODE_IN_PU)
#define CTAP_UP_BUTTON_MODE GPIO_IN_PU
#elif IS_ACTIVE(CONFIG_FIDO2_CTAP_UP_BUTTON_MODE_IN_PD)
#define CTAP_UP_BUTTON_MODE GPIO_IN_PD
#elif IS_ACTIVE(CONFIG_FIDO2_CTAP_UP_BUTTON_MODE_IN)
#define CTAP_UP_BUTTON_MODE GPIO_IN
#else
#define CTAP_UP_BUTTON_MODE GPIO_IN_PU
#endif
/**
* @brief CTAP user presence button flank
*/
#if IS_ACTIVE(CONFIG_FIDO2_CTAP_UP_BUTTON_FLANK_FALLING)
#define CTAP_UP_BUTTON_FLANK GPIO_FALLING
#elif IS_ACTIVE(CONFIG_FIDO2_CTAP_UP_BUTTON_FLANK_RISING)
#define CTAP_UP_BUTTON_FLANK GPIO_RISING
#elif IS_ACTIVE(CONFIG_FIDO2_CTAP_UP_BUTTON_FLANK_BOTH)
#define CTAP_UP_BUTTON_FLANK GPIO_BOTH
#else
#define CTAP_UP_BUTTON_FLANK GPIO_FALLING
#endif
#endif /* !IS_ACTIVE(CONFIG_FIDO2_CTAP_DISABLE_UP) */
/**
* @brief Max size of relying party name
*/
#define CTAP_RP_MAX_NAME_SIZE 32
/**
* @brief Max size of username including null character
*/
#define CTAP_USER_MAX_NAME_SIZE 64 + 1
/**
* @brief Max size of user id
*/
#define CTAP_USER_ID_MAX_SIZE 64
/**
* @brief Max size of a domain name including null character
*/
#define CTAP_DOMAIN_NAME_MAX_SIZE 253 + 1
/**
* @brief Max size of icon including null character
*/
#define CTAP_ICON_MAX_SIZE 128 + 1
/**
* @brief PIN min size
*/
#define CTAP_PIN_MIN_SIZE 4
/**
* @brief Encrypted newPin min size
*
* Encrypted PIN is padded with trailing 0x00 bytes to a minimum length
* of 64 in order to prevent leak of PIN length.
*/
#define CTAP_PIN_ENC_MIN_SIZE 64
/**
* @brief Encrypted newPin max size
*
*/
#define CTAP_PIN_ENC_MAX_SIZE 256
/**
* @brief PIN max size
*/
#define CTAP_PIN_MAX_SIZE 64
/**
* @brief Max total consecutive incorrect PIN attempts
*/
#define CTAP_PIN_MAX_ATTS 8
/**
* @brief Max consecutive incorrect PIN attempts for 1 boot cycle
*/
#define CTAP_PIN_MAX_ATTS_BOOT 3
/**
* @brief PIN protocol version
*/
#define CTAP_PIN_PROT_VER 1
/**
* @brief Total number of supported PIN protocol versions
*/
#define CTAP_AMT_SUP_PIN_VER 1
/**
* @brief Size of pin token
*
* Needs to be a multiple of 16 bytes (AES block length).
*/
#define CTAP_PIN_TOKEN_SZ 16
/**
* @brief Size of key used to encrypt credential
*
* Needed if authenticator is unable to store resident keys.
* See webauthn specification (version 20190304) section 4 (Credential ID)
* for details.
*/
#define CTAP_CRED_KEY_LEN 16
/**
* @brief AES_CCM_L parameter
*
* L has to be between 2 and 8. Value of 2 means that message has to be
* in the range 0 <= l(m) < 2^(16) = 65536.
* This should always be sufficient to send an encrypted resident key.
*/
#define CTAP_AES_CCM_L 2
/**
* @brief AES CCM nonce size
*/
#define CTAP_AES_CCM_NONCE_SIZE (15 - CTAP_AES_CCM_L)
/**
* @brief Total size of AES CCM credential id
*
* Size of encrypted resident key = resident key - cred id - has_nonce
*/
#define CTAP_CREDENTIAL_ID_ENC_SIZE (sizeof(struct ctap_resident_key) - \
sizeof(((struct ctap_resident_key *)0)-> \
cred_desc.cred_id) - \
sizeof(((struct ctap_resident_key *)0)-> \
cred_desc.has_nonce))
/**
* @brief Timeout for user presence test
*/
#ifdef CONFIG_FIDO2_CTAP_UP_TIMEOUT
#define CTAP_UP_TIMEOUT (CONFIG_FIDO2_CTAP_UP_TIMEOUT * MS_PER_SEC)
#else
#define CTAP_UP_TIMEOUT (15 * MS_PER_SEC)
#endif
/**
* @brief Max time between call to get_assertion or get_next_assertion until
* error is returned
*/
#define CTAP_GET_NEXT_ASSERTION_TIMEOUT (30 * MS_PER_SEC)
/**
* 128 bit identifier of authenticator
*/
#ifdef CONFIG_FIDO2_CTAP_DEVICE_AAGUID
#define CTAP_AAGUID CONFIG_FIDO2_CTAP_DEVICE_AAGUID
#else
/* randomly generated fallback value */
#define CTAP_AAGUID "9c295865fa2c36b705a42320af9c8f16"
#endif
/**
* @name CTAP credential types
*
* @{
*/
#define CTAP_PUB_KEY_CRED_PUB_KEY 0x01 /**< public key credential type */
#define CTAP_PUB_KEY_CRED_UNKNOWN 0x02 /**< unknown credential type */
/** @} */
/**
* @name CTAP COSE key CBOR map key values
*
* @{
*/
#define CTAP_COSE_KEY_LABEL_KTY 1 /**< key type identifier */
#define CTAP_COSE_KEY_LABEL_ALG 3 /**< algorithm identifier */
#define CTAP_COSE_KEY_LABEL_CRV -1 /**< elliptic curve identifier */
#define CTAP_COSE_KEY_LABEL_X -2 /**< x coordinate */
#define CTAP_COSE_KEY_LABEL_Y -3 /**< y coordinate */
#define CTAP_COSE_KEY_KTY_EC2 2 /**< 2 coordinate elliptic curve key identifier */
#define CTAP_COSE_KEY_CRV_P256 1 /**< secp256r1 elliptic curve key identifier */
/** @} */
/**
* @brief CTAP size of authenticator AAGUID in bytes
*/
#define CTAP_AAGUID_SIZE 16
/**
* @brief CTAP COSE Algorithms registry identifier for ES256
*/
#define CTAP_COSE_ALG_ES256 -7
/**
* @brief CTAP COSE Algorithms registry identifier for ECDH ES HKDF 256
*/
#define CTAP_COSE_ALG_ECDH_ES_HKDF_256 -25
/**
* @brief CTAP size of credential id
*
*/
#define CTAP_CREDENTIAL_ID_SIZE 16U
/**
* @brief CTAP state initialized marker
*
* Used to check if authenticator state has already been initialized when
* reading data from flash.
*/
#define CTAP_INITIALIZED_MARKER 0x4e
/**
* @brief Max size of allow list
*/
#define CTAP_MAX_EXCLUDE_LIST_SIZE 0x10
/**
* @brief CTAP cred struct forward declaration
*/
typedef struct ctap_cred_desc ctap_cred_desc_t;
/**
* @brief Alternative CTAP cred struct forward declaration
*/
typedef struct ctap_cred_desc_alt ctap_cred_desc_alt_t;
/**
* @brief CTAP resident key credential forward declaration
*/
typedef struct ctap_resident_key ctap_resident_key_t;
/**
* @brief CTAP authenticator config struct
*/
typedef struct {
uint8_t aaguid[CTAP_AAGUID_SIZE]; /**< AAGUID of device */
uint8_t options; /**< options */
} ctap_config_t;
/**
* @brief CTAP state struct
*
* state of authenticator. Stored in flash memory
*/
typedef struct {
ctap_config_t config; /**< configuration of authenticator */
ctap_crypto_key_agreement_key_t ag_key; /**< Platform key agreement key */
int rem_pin_att; /**< remaining PIN tries */
uint16_t rk_amount_stored; /**< total number of resident keys stored on device */
uint8_t initialized_marker; /**< CTAP initialized marker */
uint8_t pin_hash[SHA256_DIGEST_LENGTH / 2]; /**< LEFT(SHA-256(pin), 16) */
uint8_t cred_key[CTAP_CRED_KEY_LEN]; /**< AES CCM encryption key for cred */
bool cred_key_is_initialized; /**< AES CCM key initialized flag */
bool pin_is_set; /**< PIN is set or not */
} ctap_state_t;
/**
* @brief CTAP options struct
*/
typedef struct {
int rk; /**< resident key */
int uv; /**< user verification */
int up; /**< user presence */
} ctap_options_t;
/**
* @brief CTAP user entity struct
*/
typedef struct {
uint8_t id[CTAP_USER_ID_MAX_SIZE]; /**< RP-specific user account id */
uint8_t id_len; /**< actual length of user id */
uint8_t name[CTAP_USER_MAX_NAME_SIZE]; /**< user name */
uint8_t display_name[CTAP_USER_MAX_NAME_SIZE]; /**< user display name */
uint8_t icon[CTAP_DOMAIN_NAME_MAX_SIZE]; /**< URL referencing user icon image */
} ctap_user_ent_t;
/**
* @brief CTAP relying party entity struct
*
*/
typedef struct {
uint8_t id[CTAP_DOMAIN_NAME_MAX_SIZE + 1]; /**< relying party identifier */
uint8_t id_len; /**< actual length of
relying party identifier */
uint8_t name[CTAP_RP_MAX_NAME_SIZE + 1]; /**< human friendly relying
party name */
uint8_t icon[CTAP_DOMAIN_NAME_MAX_SIZE + 1]; /**< URL referencing relying
party icon image */
} ctap_rp_ent_t;
/**
* @brief CTAP cose key struct
*
* https://www.iana.org/assignments/cose/cose.xhtml
*/
typedef struct {
ctap_crypto_pub_key_t pubkey; /**< public key */
int kty; /**< identification of key type */
int crv; /**< EC identifier */
int32_t alg_type; /**< COSEAlgorithmIdentifier */
uint8_t cred_type; /**< type of credential */
} ctap_public_key_cose_t;
/**
* @brief CTAP credential description struct
*
* Webauthn specification (version 20190304) section 5.8.3
*
* @warning reordering this struct will break the AES CCM encryption of
* resident keys.
*/
struct ctap_cred_desc {
uint8_t cred_type; /**< type of credential */
union {
uint8_t cred_id[CTAP_CREDENTIAL_ID_SIZE]; /**< credential identifier */
uint8_t nonce[CTAP_AES_CCM_NONCE_SIZE]; /**< CTAP AES CCM nonce */
};
bool has_nonce; /**< Indicate if nonce or
cred_id */
};
/**
* @brief CTAP resident key struct
*
* A resident key is a fido2 credential that is being stored on the
* authenticator.
*/
struct __attribute__((packed)) ctap_resident_key {
uint8_t rp_id_hash[SHA256_DIGEST_LENGTH]; /**< hash of rp domain string */
uint8_t user_id[CTAP_USER_ID_MAX_SIZE]; /**< id of user */
uint8_t user_id_len; /**< length of the user id */
uint8_t priv_key[CTAP_CRYPTO_KEY_SIZE]; /**< private key */
uint32_t sign_count; /**< signature counter.
See webauthn specification
(version 20190304) section 6.1.1
for details. */
uint32_t creation_time; /**< timestamp for when credential
was created */
ctap_cred_desc_t cred_desc; /**< credential descriptor */
};
/**
* @brief CTAP credential ID
*
* Credential ID can either be 16 random bytes or the encrypted resident
* key. (AES CCM cipher + mac + nonce used)
*/
typedef struct __attribute__((packed)) {
uint8_t id[CTAP_CREDENTIAL_ID_ENC_SIZE]; /**< id */
uint8_t mac[CCM_MAC_MAX_LEN]; /**< AES CCM MAC */
uint8_t nonce[CTAP_AES_CCM_NONCE_SIZE]; /**< AES CCM nonce */
} ctap_cred_id_t;
/**
* @brief CTAP credential description alternative struct
*
* This struct is used when parsing an allow or exclude list.
*/
struct ctap_cred_desc_alt {
uint8_t cred_type; /**< type of credential */
ctap_cred_id_t cred_id; /**< credential id */
};
/**
* @brief CTAP make credential request struct
*/
typedef struct {
ctap_cred_desc_alt_t exclude_list[CTAP_MAX_EXCLUDE_LIST_SIZE]; /**< exclude list */
size_t exclude_list_len; /**< length of CBOR exclude list array */
ctap_rp_ent_t rp; /**< relying party */
ctap_user_ent_t user; /**< user */
ctap_options_t options; /**< parameters to influence authenticator operation */
uint8_t client_data_hash[SHA256_DIGEST_LENGTH]; /**< SHA-256 hash of JSON serialized client data */
uint8_t pin_auth[CTAP_PIN_AUTH_SZ]; /**< pin_auth if PIN is set */
size_t pin_auth_len; /**< pin_auth len */
int32_t alg_type; /**< cryptographic algorithm identifier */
bool pin_auth_present; /**< pin_auth present */
uint8_t pin_protocol; /**< PIN protocol version */
uint8_t cred_type; /**< type of credential */
} ctap_make_credential_req_t;
/**
* @brief CTAP get assertion request struct
*/
typedef struct {
ctap_options_t options; /**< parameters to influence authenticator operation */
ctap_cred_desc_alt_t allow_list[CTAP_MAX_EXCLUDE_LIST_SIZE]; /**< allow list */
uint8_t client_data_hash[SHA256_DIGEST_LENGTH]; /**< SHA-256 hash of JSON serialized client data */
uint8_t rp_id[CTAP_DOMAIN_NAME_MAX_SIZE + 1]; /**< Relying Party Identifier */
uint8_t rp_id_len; /**< Actual Length of Relying Party Identifier */
uint8_t allow_list_len; /**< length of CBOR allow list array */
uint8_t pin_auth[CTAP_PIN_AUTH_SZ]; /**< pin_auth if PIN is set */
size_t pin_auth_len; /**< pin_auth length */
uint8_t pin_protocol; /**< PIN protocol version */
bool pin_auth_present; /**< indicate if pin_auth present */
} ctap_get_assertion_req_t;
/**
* @brief CTAP client pin request struct
*/
typedef struct {
ctap_public_key_cose_t key_agreement; /**< public key of platform_key_agreement_key*/
uint16_t new_pin_enc_size; /**< size of encrypted new pin */
uint8_t pin_auth[CTAP_PIN_AUTH_SZ]; /**< first 16 bytes of HMAC-SHA-256 of encrypted contents */
uint8_t new_pin_enc[CTAP_PIN_ENC_MAX_SIZE]; /**< Encrypted new PIN using sharedSecret. */
uint8_t pin_hash_enc[SHA256_DIGEST_LENGTH / 2]; /**< Encrypted first 16 bytes of SHA-256 of PIN using sharedSecret. */
uint8_t sub_command; /**< authenticator Client PIN sub command */
uint8_t pin_protocol; /**< PIN protocol version chosen by the client */
bool pin_hash_enc_present; /**< indicate pin_hash_enc is present */
bool pin_auth_present; /**< indicate if pin_auth present */
bool key_agreement_present; /**< indicate if key_agreement present */
} ctap_client_pin_req_t;
/**
* @brief CTAP attested credential data header struct
*
* Defined for easier serialization
*/
typedef struct __attribute__((packed)){
uint8_t aaguid[CTAP_AAGUID_SIZE]; /**< authenticator aaguid */
uint8_t cred_len_h; /**< higher byte of credential length */
uint8_t cred_len_l; /**< lower byte of credential length */
ctap_cred_id_t cred_id; /**< credential id */
} ctap_attested_cred_data_header_t;
/**
* @brief CTAP attested credential data struct
*/
typedef struct {
ctap_attested_cred_data_header_t header; /**< attested credential data header */
ctap_public_key_cose_t key; /**< cose key */
} ctap_attested_cred_data_t;
/**
* @brief CTAP authenticator data header struct
*
* Defined for easier serialization
*/
typedef struct __attribute__((packed)){
uint8_t rp_id_hash[SHA256_DIGEST_LENGTH]; /**< hash of relying party id */
uint8_t flags; /**< flags indicating result of user verification */
uint32_t sign_count; /**< sign count of credential */
} ctap_auth_data_header_t;
/**
* @brief CTAP authenticator data struct
*/
typedef struct {
ctap_auth_data_header_t header; /**< auth data header */
ctap_attested_cred_data_t attested_cred_data; /**< attested credential data */
} ctap_auth_data_t;
/**
* @brief CTAP info struct
*/
typedef struct {
uint16_t max_msg_size; /**< max message size */
uint8_t aaguid[CTAP_AAGUID_SIZE]; /**< AAGUID */
uint8_t versions; /**< supported versions of FIDO */
uint8_t options; /**< supported options */
uint8_t pin_protocol; /**< supported PIN protocol versions */
bool pin_is_set; /**< PIN is set or not */
} ctap_info_t;
/**
* @brief Create signature from authenticator data
*
* Used for attestation and assertion statement.
*
* @param[in] auth_data authenticator data
* @param[in] auth_data_len length of @p auth_data
* @param[in] client_data_hash hash of client data sent by relying party in request
* @param[in] rk resident key used to sign the data
* @param[in] sig signature buffer
* @param[in] sig_len length of @p sig
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_get_sig(const uint8_t *auth_data, size_t auth_data_len,
const uint8_t *client_data_hash,
const ctap_resident_key_t *rk,
uint8_t *sig, size_t *sig_len);
/**
* @brief Check if requested algorithm is supported
*
* @param[in] cred_type type of credential
* @param[in] alg_type cryptographic algorithm identifier
*
* @return true if algorithm is supported
* @return false otherwise
*/
bool fido2_ctap_cred_params_supported(uint8_t cred_type, int32_t alg_type);
/**
* @brief Encrypt resident key with AES CCM
*
* @param[in] rk type of credential
* @param[in] nonce CCM nonce
* @param[in] nonce_len length of @p nonce
* @param[in] id credential id struct storing encrypted resident key
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_encrypt_rk(ctap_resident_key_t *rk, uint8_t *nonce,
size_t nonce_len, ctap_cred_id_t *id);
/**
* @brief Check if PIN has been set on authenticator
*
* @return true if PIN has been set
* @return false otherwise
*/
bool fido2_ctap_pin_is_set(void);
#ifdef __cplusplus
}
#endif
#endif /* FIDO2_CTAP_CTAP_H */
/** @} */

View File

@ -0,0 +1,338 @@
/*
* Copyright (C) 2021 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.
*/
/**
* @defgroup fido2_ctap_cbor FIDO2 CTAP CBOR
* @ingroup fido2_ctap
* @brief FIDO2 CTAP CBOR helper
*
* @{
*
* @file
* @brief CTAP CBOR helper function declarations
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
*/
#ifndef FIDO2_CTAP_CTAP_CBOR_H
#define FIDO2_CTAP_CTAP_CBOR_H
#include "fido2/ctap/ctap.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief CBOR map size of encoded getInfo response
*/
#define CTAP_CBOR_INFO_MAP_SZ 0x06
/**
* @brief CBOR map size of encoded attestation
*/
#define CTAP_CBOR_ATTESTATION_MAP_SZ 0x03
/**
* @brief CBOR map size of encoded attestation statement
*/
#define CTAP_CBOR_ATTESTATION_STMT_MAP_SZ 0x02
/**
* @brief CBOR map size of encoded credential description
*/
#define CTAP_CBOR_CRED_DESC_MAP_SZ 0x02
/**
* @brief CBOR map size of encoded pinToken
*/
#define CTAP_CBOR_PIN_TOKEN_MAP_SZ 0x01
/**
* @brief CBOR map size of encoded user entity
*/
#define CTAP_CBOR_USER_ENTITY_MAP_SZ 0x01
/**
* @brief CBOR map size of public key encoded in COSE format
*/
#define CTAP_CBOR_COSE_KEY_MAP_SZ 0x05
/**
* @brief CBOR map size of encoded clientPIN keyAgreement
*/
#define CTAP_CBOR_KEY_AGREEMENT_MAP_SZ 0x01
/**
* @brief CBOR map size of encoded clientPIN retries
*/
#define CTAP_CBOR_RETRIES_MAP_SZ 0x01
/**
* @brief Attestation statement data buffer size
*/
#define CTAP_CBOR_ATT_STMT_AUTH_DATA_SZ 0x134
/**
* @brief Max length of string key in CBOR map
*/
#define CTAP_CBOR_MAP_MAX_KEY_LEN 0x10
/**
* @brief Max length of PublicKeyCredentialType string
*/
#define CTAP_CBOR_MAX_CREDENTIAL_TYPE_LEN 0x10
/**
* @name CTAP CBOR map key string values
* @{
*/
#define CTAP_CBOR_STR_PACKED "packed" /**< packed key string */
#define CTAP_CBOR_STR_ALG "alg" /**< algorithm key string */
#define CTAP_CBOR_STR_SIG "sig" /**< signature key string */
#define CTAP_CBOR_STR_ID "id" /**< id key string */
#define CTAP_CBOR_STR_TYPE "type" /**< type key string */
#define CTAP_CBOR_STR_PUBLIC_KEY "public-key" /**< public-key key string */
#define CTAP_CBOR_STR_USER_VERIFIED "uv" /**< user verification key string */
#define CTAP_CBOR_STR_USER_PRESENT "up" /**< user presence key string */
#define CTAP_CBOR_STR_RESIDENT_KEY "rk" /**< resident key key string */
#define CTAP_CBOR_STR_NAME "name" /**< name key string */
#define CTAP_CBOR_STR_ICON "icon" /**< icon key string */
#define CTAP_CBOR_DISPLAY_NAME "displayName" /**< displayName key string */
/** @} */
/**
* @name CTAP Client PIN response CBOR map key values
*
* @{
*/
#define CTAP_CBOR_CP_RESP_KEY_AGREEMENT 0x01 /**< KeyAgreement key value */
#define CTAP_CBOR_CP_PIN_TOKEN_RESP 0x02 /**< pinToken key value */
#define CTAP_CBOR_CP_RETRIES_RESP 0x03 /**< retries key value */
/** @} */
/**
* @name CTAP make credential request CBOR key values
*
* @{
*/
#define CTAP_CBOR_MC_REQ_CLIENT_DATA_HASH 0x01 /**< clientDataHash key value */
#define CTAP_CBOR_MC_REQ_RP 0x02 /**< relying party key value */
#define CTAP_CBOR_MC_REQ_USER 0x03 /**< user key value */
#define CTAP_CBOR_MC_REQ_PUB_KEY_CRED_PARAMS 0x04 /**< pubKeyCredParams key value */
#define CTAP_CBOR_MC_REQ_EXCLUDE_LIST 0x05 /**< excludeList key value */
#define CTAP_CBOR_MC_REQ_EXTENSIONS 0x06 /**< extensions key value */
#define CTAP_CBOR_MC_REQ_OPTIONS 0x07 /**< options key value */
#define CTAP_CBOR_MC_REQ_PIN_AUTH 0x08 /**< pinAuth key value */
#define CTAP_CBOR_MC_REQ_PIN_PROTOCOL 0x09 /**< pinProtocol key value */
/** @} */
/**
* @name CTAP get info response CBOR key values
*
* @{
*/
#define CTAP_CBOR_GET_INFO_RESP_VERSIONS 0x01 /**< versions key value */
#define CTAP_CBOR_GET_INFO_RESP_EXTENSIONS 0x02 /**< extensions key value */
#define CTAP_CBOR_GET_INFO_RESP_AAGUID 0x03 /**< AAGUID key value */
#define CTAP_CBOR_GET_INFO_RESP_OPTIONS 0x04 /**< options key value */
#define CTAP_CBOR_GET_INFO_RESP_MAX_MSG_SIZE 0x05 /**< maxMsgSize key value */
#define CTAP_CBOR_GET_INFO_RESP_PIN_PROTOCOLS 0x06 /**< pinProtocol key value */
/** @} */
/**
* @name CTAP version strings
* @{
*/
#define CTAP_CBOR_VERSION_STRING_FIDO_PRE "FIDO_2_1_PRE" /**< FIDO 2.1 flag */
#define CTAP_CBOR_VERSION_STRING_FIDO "FIDO_2_0" /**< FIDO 2 flag */
#define CTAP_CBOR_VERSION_STRING_U2F_V2 "U2F_V2" /**< U2F V2 flag */
/** @} */
/**
* @name CTAP make credential response CBOR key values
*
* @{
*/
#define CTAP_CBOR_MC_RESP_FMT 0x01 /**< attestation statement format identifier key value */
#define CTAP_CBOR_MC_RESP_AUTH_DATA 0x02 /**< authData key value */
#define CTAP_CBOR_MC_RESP_ATT_STMT 0x03 /**< attestation statement key value */
/** @} */
/**
* @name CTAP get assertion request CBOR key values
*
* @{
*/
#define CTAP_CBOR_GA_REQ_RP_ID 0x01 /**< relying party identifier key value */
#define CTAP_CBOR_GA_REQ_CLIENT_DATA_HASH 0x02 /**< clientDataHash key value */
#define CTAP_CBOR_GA_REQ_ALLOW_LIST 0x03 /**< allowList key value */
#define CTAP_CBOR_GA_REQ_EXTENSIONS 0x04 /**< extensions key value */
#define CTAP_CBOR_GA_REQ_OPTIONS 0x05 /**< options key value */
#define CTAP_CBOR_GA_REQ_PIN_AUTH 0x06 /**< pinAuth key value */
#define CTAP_CBOR_GA_REQ_PIN_PROTOCOL 0x07 /**< pinProtocol key value */
/** @} */
/**
* @name CTAP get assertion response CBOR key values
*
* @{
*/
#define CTAP_CBOR_GA_RESP_CREDENTIAL 0x01 /**< credential key value */
#define CTAP_CBOR_GA_RESP_AUTH_DATA 0x02 /**< authData key value */
#define CTAP_CBOR_GA_RESP_SIGNATURE 0x03 /**< signature key value */
#define CTAP_CBOR_GA_RESP_USER 0x04 /**< user key value */
#define CTAP_CBOR_GA_RESP_NUMBER_OF_CREDENTIALS 0x05 /**< numberOfCredentials key value */
/** @} */
/**
* @name CTAP Client PIN request CBOR key values
*
* @{
*/
#define CTAP_CBOR_CP_REQ_PIN_PROTOCOL 0x01 /**< pinProtocol key value */
#define CTAP_CBOR_CP_REQ_SUB_COMMAND 0x02 /**< subCommand key value */
#define CTAP_CBOR_CP_REQ_KEY_AGREEMENT 0x03 /**< keyAgreement key value */
#define CTAP_CBOR_CP_REQ_PIN_AUTH 0x04 /**< pinAuth key value */
#define CTAP_CBOR_CP_REQ_NEW_PIN_ENC 0x05 /**< newPinEnc key value */
#define CTAP_CBOR_CP_REQ_PIN_HASH_ENC 0x06 /**< pinHashEnc key value */
/** @} */
/**
* @brief Parse MakeCredential method
*
* CTAP specification (version 20190130) section 5.1
*
* @param[in] req struct to parse into
* @param[in] req_raw raw request
* @param[in] len length of @p req_raw
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_cbor_parse_make_credential_req(ctap_make_credential_req_t *req,
const uint8_t *req_raw, size_t len);
/**
* @brief Parse GetAssertion method
*
* CTAP specification (version 20190130) section 5.2
*
* @param[in] req struct to parse into
* @param[in] req_raw raw request
* @param[in] len length of @p req_raw
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_cbor_parse_get_assertion_req(ctap_get_assertion_req_t *req,
const uint8_t *req_raw, size_t len);
/**
* @brief Encode CBOR info map
*
* CTAP specification (version 20190130) section 5.4
*
* @param[in] info information about capabilities
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_cbor_encode_info(const ctap_info_t *info);
/**
* @brief Parse ClientPIN method
*
* CTAP specification (version 20190130) section 5.5
*
* @param[in] req struct to parse into
* @param[in] req_raw raw request
* @param[in] len length of @p req_raw
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_cbor_parse_client_pin_req(ctap_client_pin_req_t *req,
const uint8_t *req_raw, size_t len);
/**
* @brief Encode attestation object
*
* Webauthn specification (version 20190304) section 6.5
*
* @param[in] auth_data authenticator data
* @param[in] client_data_hash SHA-256 hash of JSON serialized client data
* @param[in] rk resident key
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_cbor_encode_attestation_object(const ctap_auth_data_t *auth_data,
const uint8_t *client_data_hash,
ctap_resident_key_t *rk);
/**
* @brief Encode assertion object
*
* CTAP specification (version 20190130) section 5.2
*
* @param[in] auth_data authenticator data header
* @param[in] client_data_hash SHA-256 hash of JSON serialized client data
* @param[in] rk resident key
* @param[in] valid_cred_count amount of valid credentials found in allow list
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_cbor_encode_assertion_object(const ctap_auth_data_header_t *auth_data,
const uint8_t *client_data_hash,
ctap_resident_key_t *rk,
uint8_t valid_cred_count);
/**
* @brief Encode key agreement
*
* @param[in] key Public key in COSE format
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_cbor_encode_key_agreement(const ctap_public_key_cose_t *key);
/**
* @brief Encode encrypted pin token
*
* @param[in] token encrypted pin token
* @param[in] len length of @p token
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_cbor_encode_pin_token(uint8_t *token, size_t len);
/**
* @brief Encode PIN tries left
*
* @param[in] tries_left amount of tries left
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_cbor_encode_retries(uint8_t tries_left);
/**
* @brief Get size of CBOR encoded data
*
* @param[in] buf Buffer holding the data
*
* @return size of CBOR encoded data
*/
size_t fido2_ctap_cbor_get_buffer_size(const uint8_t *buf);
/**
* @brief Initialize CBOR encoder
*
* @param[in] buf Buffer to hold CBOR encoded data
* @param[in] len Length of @p buf
*/
void fido2_ctap_cbor_init_encoder(uint8_t *buf, size_t len);
#ifdef __cplusplus
}
#endif
#endif /* FIDO2_CTAP_CTAP_CBOR_H */
/** @} */

View File

@ -0,0 +1,301 @@
/*
* Copyright (C) 2021 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.
*/
/**
* @defgroup fido2_ctap_crypto FIDO2 CTAP crypto
* @ingroup fido2_ctap
* @brief FIDO2 CTAP crypto helper
*
* @{
*
* @file
* @brief FIDO2 CTAP crypto helper defines, structures and function
* declarations.
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
*/
#ifndef FIDO2_CTAP_CTAP_CRYPTO_H
#define FIDO2_CTAP_CTAP_CRYPTO_H
#include <stdint.h>
#include "hashes/sha256.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Size in bytes of cryptographic keys used
*/
#define CTAP_CRYPTO_KEY_SIZE 32
/**
* @brief Max size of ES256 signature in ASN.1 DER format
*/
#define CTAP_CRYPTO_ES256_DER_MAX_SIZE 72
/**
* @brief Elliptic curve public key
*/
typedef struct {
uint8_t x[CTAP_CRYPTO_KEY_SIZE]; /**< x coordinate of curve point */
uint8_t y[CTAP_CRYPTO_KEY_SIZE]; /**< y coordinate of curve point */
} ctap_crypto_pub_key_t;
/**
* @brief Key agreement key
*
* CTAP specification (version 20190130) section 5.5.4
*/
typedef struct {
ctap_crypto_pub_key_t pub; /**< public key */
uint8_t priv[CTAP_CRYPTO_KEY_SIZE]; /**< private key */
} ctap_crypto_key_agreement_key_t;
/**
* @brief Initialize crypto helper
*
* Initializes crypto libs and creates key_agreement key pair
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_init(void);
/**
* @brief Wrapper function for @ref random_bytes
*
* @param[in] buf buffer to hold random bytes
* @param[in] len length of @p buf
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_prng(uint8_t *buf, size_t len);
/**
* @brief Wrapper function for @ref sha256_init
*
* @param ctx sha256_context_t handle to init
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_sha256_init(sha256_context_t *ctx);
/**
* @brief Wrapper function for @ref sha256_update
*
* @param ctx sha256_context_t handle to use
* @param[in] data Input data
* @param[in] len Length of @p data
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_sha256_update(sha256_context_t *ctx, const void *data, size_t len);
/**
* @brief Wrapper for @ref sha256_final
*
* @param ctx sha256_context_t handle to use
* @param digest resulting digest, this is the hash of all the bytes
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_sha256_final(sha256_context_t *ctx, void *digest);
/**
* @brief Wrapper function for @ref sha256
*
* @param[in] data pointer to the buffer to generate hash from
* @param[in] len length of @p data
* @param[out] digest optional pointer to an array for the result, length must
* be SHA256_DIGEST_LENGTH
*
* @note discards the pointer returned by @ref sha256
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_sha256(const void *data, size_t len,
void *digest);
/**
* @brief Wrapper function for @ref hmac_sha256_init
*
* @param[in] ctx hmac_context_t handle to use
* @param[in] key key used in the hmac-sha256 computation
* @param[in] key_length length of @p key
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_hmac_sha256_init(hmac_context_t *ctx, const void *key,
size_t key_length);
/**
* @brief Wrapper function for @ref hmac_sha256_update
*
* @param[in] ctx hmac_context_t handle to use
* @param[in] data pointer to the buffer to generate hash from
* @param[in] len length of @p data
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_hmac_sha256_update(hmac_context_t *ctx, const void *data, size_t len);
/**
* @brief Wrapper function for @ref hmac_sha256_final
*
* @param[in] ctx hmac_context_t handle to use
* @param[out] digest the computed hmac-sha256,
* length MUST be SHA256_DIGEST_LENGTH
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_hmac_sha256_final(hmac_context_t *ctx, void *digest);
/**
* @brief Wrapper function for @ref hmac_sha256
*
* @param[in] key key used in the hmac-sha256 computation
* @param[in] key_length length of @p key
* @param[in] data pointer to the buffer to generate the hmac-sha256
* @param[in] len length of @p data
* @param[out] digest the computed hmac-sha256,
* length MUST be SHA256_DIGEST_LENGTH
*
* @note discards the pointer returned by @ref hmac_sha256
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_hmac_sha256(const void *key,
size_t key_length, const void *data, size_t len,
void *digest);
/**
* @brief Generate cryptographic key pair
*
* @param[in] pub_key public key buffer
* @param[in] priv_key private key buffer
* @param[in] len length of @p priv_key
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_gen_keypair(ctap_crypto_pub_key_t *pub_key, uint8_t *priv_key, size_t len);
/**
* @brief Elliptic-curve Diffie-Hellmann
*
* @param[in] out shared secret buffer
* @param[in] len length of @p out
* @param[in] pub_key public key of other party
* @param[in] priv_key private key
* @param[in] key_len length of @p priv_key
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_ecdh(uint8_t *out, size_t len,
ctap_crypto_pub_key_t *pub_key, uint8_t *priv_key, size_t key_len);
/**
* @brief Create cryptographic signature
*
* @param[in] hash Hash to be signed
* @param[in] hash_len length of @p hash
* @param[in] sig signature buffer
* @param[in] sig_len length of @p sig
* @param[in] key private key to use for signature
* @param[in] key_len length of @p key
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_get_sig(uint8_t *hash, size_t hash_len, uint8_t *sig,
size_t *sig_len, const uint8_t *key, size_t key_len);
/**
* @brief Encrypt data using AES-256-CBC
*
* @param[in] out encrypted data
* @param[in] out_len length of @p out
* @param[in] in data to be encrypted
* @param[in] in_len length of @p in
* @param[in] key symmetric key to use for encryption
* @param[in] key_len length of @p key
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_aes_enc(uint8_t *out, size_t *out_len, uint8_t * in,
size_t in_len, const uint8_t * key, size_t key_len);
/**
* @brief Decrypt data using AES-256-CBC
*
* @param[in] out decrypted data
* @param[in] out_len length of @p out
* @param[in] in encrypted data
* @param[in] in_len len of @p in
* @param[in] key symmetric key to use for decryption
* @param[in] key_len length of @p key
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_aes_dec(uint8_t *out, size_t *out_len, uint8_t * in,
size_t in_len, const uint8_t * key, size_t key_len);
/**
* @brief Encrypt data using AES-128-CCM
*
* @param[in] out encrypted data
* @param[in] out_len length of @p out
* @param[in] in data to be encrypted
* @param[in] in_len length of @p in
* @param[in] auth_data additional data to authenticate in MAC
* @param[in] auth_data_len length of @p auth_data
* @param[in] mac_len length of appended MAC
* @param[in] length_encoding max supported length of plaintext
* @param[in] nonce nonce for ctr mode encryption
* @param[in] nonce_len length of @p nonce
* @param[in] key symmetric key to use for encryption
* @param[in] key_len length of @p key
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_aes_ccm_enc(uint8_t *out, size_t out_len,
const uint8_t *in, size_t in_len,
uint8_t *auth_data, size_t auth_data_len,
uint8_t mac_len, uint8_t length_encoding,
const uint8_t *nonce, size_t nonce_len,
const uint8_t *key, size_t key_len);
/**
* @brief Encrypt data using AES-128-CCM
*
* @param[in] out encrypted data
* @param[in] out_len length of @p out
* @param[in] in data to be encrypted
* @param[in] in_len length of @p in
* @param[in] auth_data additional data to authenticate in MAC
* @param[in] auth_data_len length of @p auth_data
* @param[in] mac_len length of appended MAC
* @param[in] length_encoding max supported length of plaintext
* @param[in] nonce nonce for ctr mode encryption
* @param[in] nonce_len length of @p nonce
* @param[in] key symmetric key to use for encryption
* @param[in] key_len length of @p key
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_crypto_aes_ccm_dec(uint8_t *out, size_t out_len,
const uint8_t *in, size_t in_len,
uint8_t *auth_data, size_t auth_data_len,
uint8_t mac_len, uint8_t length_encoding,
const uint8_t *nonce, size_t nonce_len,
const uint8_t *key, size_t key_len);
#ifdef __cplusplus
}
#endif
#endif /* FIDO2_CTAP_CTAP_CRYPTO_H */
/** @} */

View File

@ -0,0 +1,162 @@
/*
* Copyright (C) 2021 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.
*/
/**
* @defgroup fido2_ctap_mem FIDO2 CTAP flash
* @ingroup fido2_ctap
* @brief FIDO2 CTAP flash memory helper
*
* @{
*
* @file
* @brief Definitions for CTAP flash memory helper functions
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
*/
#ifndef FIDO2_CTAP_CTAP_MEM_H
#define FIDO2_CTAP_CTAP_MEM_H
#include <stdint.h>
#include "fido2/ctap/ctap.h"
#include "periph/flashpage.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief MAX function for internal use
* @{
*/
#ifndef _MAX
#define _MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
/** @} */
/**
* @brief First flash page to store data in
*
* @note This can corrupt firmware if CTAP_FLASH_START_PAGE is set to a
* flash page containing firmware. Therefore make sure that CTAP_FLASH_START_PAGE
* is located after the firmware.
*/
#if defined(CONFIG_FIDO2_CTAP_FLASH_START_PAGE) && \
(CONFIG_FIDO2_CTAP_FLASH_START_PAGE >= 0)
#define CTAP_FLASH_START_PAGE CONFIG_FIDO2_CTAP_FLASH_START_PAGE
#else
#define CTAP_FLASH_START_PAGE (FLASHPAGE_NUMOF - 4)
#endif
/**
* @brief Start page for storing resident keys
*/
#define CTAP_FLASH_RK_START_PAGE CTAP_FLASH_START_PAGE
/**
* @brief Page for storing authenticator state information
*/
#define CTAP_FLASH_STATE_PAGE CTAP_FLASH_RK_START_PAGE - 1
/**
* @brief Calculate padding needed to align struct size for saving to flash
*/
#define CTAP_FLASH_ALIGN_PAD(x) (sizeof(x) % FLASHPAGE_WRITE_BLOCK_SIZE == \
0 ? \
0 : FLASHPAGE_WRITE_BLOCK_SIZE - \
sizeof(x) % FLASHPAGE_WRITE_BLOCK_SIZE)
/**
* @brief Resident key size with alignment padding
*/
#define CTAP_FLASH_RK_SZ (sizeof(ctap_resident_key_t) + \
CTAP_FLASH_ALIGN_PAD(ctap_resident_key_t))
/**
* @brief State struct size with alignment padding
*/
#define CTAP_FLASH_STATE_SZ (sizeof(ctap_state_t) + \
CTAP_FLASH_ALIGN_PAD(ctap_state_t))
/**
* @brief Minimum flash sector size needed to hold CTAP related data
*
* This is needed to ensure that the MTD work_area buffer is big enough
*/
#define CTAP_FLASH_MIN_SECTOR_SZ _MAX(CTAP_FLASH_STATE_SZ, CTAP_FLASH_RK_SZ)
/**
* @brief Pages per sector needed
*/
#define CTAP_FLASH_PAGES_PER_SECTOR ((CTAP_FLASH_MIN_SECTOR_SZ / FLASHPAGE_SIZE) + 1)
/**
* @brief Initialize memory helper
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_mem_init(void);
/**
* @brief Write to flash memory
*
* @param[in] buf buffer to write
* @param[in] page page to write to
* @param[in] offset offset from the start of the page (in bytes)
* @param[in] len number of bytes to write
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_mem_write(const void *buf, uint32_t page, uint32_t offset, uint32_t len);
/**
* @brief Read from flash memory
*
* @param[out] buf buffer to fil in
* @param[in] page page to read from
* @param[in] offset offset from the start of the page (in bytes)
* @param[in] len number of bytes to write
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_mem_read(void *buf, uint32_t page, uint32_t offset, uint32_t len);
/**
* @brief Get maximum amount of resident credentials that can be stored
*
* @return maximum amount that can be stored
*/
uint16_t fido2_ctap_mem_get_max_rk_amount(void);
/**
* @brief Get flashpage number resident key with index @p rk_idx.
*
* @param[in] rk_idx index of resident key
*
* @return page number if no error
* @return -1 if @p rk_idx is invalid
*/
int fido2_ctap_mem_get_flashpage_number_of_rk(uint16_t rk_idx);
/**
* @brief Get offset of resident key into flashpage where flashpage =
* fido2_ctap_mem_get_flashpage_number_of_r(i)
*
* @param[in] rk_idx index of resident key
*
* @return page number if no error
* @return -1 if @p rk_idx is invalid
*/
int fido2_ctap_mem_get_offset_of_rk_into_flashpage(uint16_t rk_idx);
#ifdef __cplusplus
}
#endif
#endif /* FIDO2_CTAP_CTAP_MEM_H */
/** @} */

View File

@ -0,0 +1,91 @@
/*
* Copyright (C) 2021 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.
*/
/**
* @defgroup fido2_ctap_utils FIDO2 CTAP utils
* @ingroup fido2_ctap
* @brief FIDO2 CTAP utility helper
*
* @{
*
* @file
* @brief Definition for CTAP utility functions
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
*/
#ifndef FIDO2_CTAP_CTAP_UTILS_H
#define FIDO2_CTAP_CTAP_UTILS_H
#include <stdint.h>
#include "fido2/ctap/ctap.h"
#include "periph/gpio.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief LED animation to indicate that user action is required
*/
void fido2_ctap_utils_led_animation(void);
/**
* @brief Initialize button to be used for user presence test
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_utils_init_gpio_pin(gpio_t pin, gpio_mode_t mode, gpio_flank_t flank);
/**
* @brief Test user presence
*
* Successful if user clicks button in less than @ref CTAP_UP_TIMEOUT
*
* @return @ref ctap_status_codes_t
*/
int fido2_ctap_utils_user_presence_test(void);
/**
* @brief Compare fido2 credentials based on creation time
*
* @param[in] k1 first resident key
* @param[in] k2 second resident key
*
* @return <0 if k2 has a bigger sign_count
* @return 0 if equal k1 and k2 have equal sign_count
* @return >0 if k1 has a bigger sign_count
*/
static inline int fido2_ctap_utils_cred_cmp(const void *k1, const void *k2)
{
ctap_resident_key_t *_k1 = (ctap_resident_key_t *)k1;
ctap_resident_key_t *_k2 = (ctap_resident_key_t *)k2;
return _k2->creation_time - _k1->creation_time;
}
/**
* @brief Check equality of resident keys based on rp_id_hash and user_id
*
* @param[in] k1 first resident key
* @param[in] k2 second resident key
*
* @return true if equal false otherwise
*/
static inline bool fido2_ctap_utils_ks_equal(const ctap_resident_key_t *k1,
const ctap_resident_key_t *k2)
{
return memcmp(k1->rp_id_hash, k2->rp_id_hash, sizeof(k1->rp_id_hash)) == 0 &&
memcmp(k1->user_id, k2->user_id, sizeof(k1->user_id)) == 0;
}
#ifdef __cplusplus
}
#endif
#endif /* FIDO2_CTAP_CTAP_UTILS_H */
/** @} */

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2021 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.
*/
/**
* @defgroup fido2_ctap_transport FIDO2 CTAP transport
* @ingroup fido2_ctap
* @brief CTAP transport layer
*
* @{
*
* @file
* @brief CTAP transport layer defines and function declarations
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
*/
#ifndef FIDO2_CTAP_TRANSPORT_CTAP_TRANSPORT_H
#define FIDO2_CTAP_TRANSPORT_CTAP_TRANSPORT_H
#include <stdint.h>
#include "mutex.h"
#include "timex.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief CTAP transport thread priority
*/
#ifndef CTAP_TRANSPORT_PRIO
#define CTAP_TRANSPORT_PRIO (THREAD_PRIORITY_MAIN - 5)
#endif
/**
* @brief Initialize ctap_transport layer and fido2_ctap
*/
void fido2_ctap_transport_init(void);
#ifdef __cplusplus
}
#endif
#endif /* FIDO2_CTAP_TRANSPORT_CTAP_TRANSPORT_H */
/** @} */

View File

@ -0,0 +1,256 @@
/*
* Copyright (C) 2021 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.
*/
/**
* @defgroup fido2_ctap_transport_hid FIDO2 CTAPHID
* @ingroup fido2_ctap_transport
* @brief FIDO2 CTAP USB_HID transport binding
*
* @{
*
* @file
* @brief Definition for CTAPHID helper functions
*
* @author Nils Ollrogge <nils.ollrogge@fu-berlin.de>
*/
#ifndef FIDO2_CTAP_TRANSPORT_HID_CTAP_HID_H
#define FIDO2_CTAP_TRANSPORT_HID_CTAP_HID_H
#include <stdint.h>
#include "usb/usbus/hid.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name CTAP_HID packet type payload sizes
*
* @{
*/
#define CTAP_HID_INIT_PAYLOAD_SIZE (CONFIG_USBUS_HID_INTERRUPT_EP_SIZE - 7) /**< endpoint size - init packet metadata */
#define CTAP_HID_CONT_PAYLOAD_SIZE (CONFIG_USBUS_HID_INTERRUPT_EP_SIZE - 5) /**< endpoint size - cont packet metadata */
/** @} */
/**
* @brief CTAP_HID protocol version
*/
#define CTAP_HID_PROTOCOL_VERSION 0x02
/**
* @name CTAP_HID packet type identifiers
*
* @{
*/
#define CTAP_HID_INIT_PACKET 0x80 /**< initialization packet identifier */
#define CTAP_HID_CONT_PACKET 0x00 /**< continuation packet identifier */
/** @} */
/**
* @brief CTAP_HID size of nonce for init request
*/
#define CTAP_HID_INIT_NONCE_SIZE 8
/**
* @brief CTAP_HID transaction timeout in microseconds
*/
#ifdef CONFIG_FIDO2_CTAP_TRANSPORT_HID_TRANSACTION_TIMEOUT
#define CTAP_HID_TRANSACTION_TIMEOUT (CONFIG_FIDO2_CTAP_TRANSPORT_HID_TRANSACTION_TIMEOUT * \
US_PER_MS)
#else
#define CTAP_HID_TRANSACTION_TIMEOUT (500 * US_PER_MS)
#endif
/**
* @brief CTAP_HID max message payload size
*
* CTAP specification (version 20190130) section 8.2.4.
*/
#define CTAP_HID_BUFFER_SIZE 7609
/**
* @name CTAP_HID commands
*
* @{
*/
#define CTAP_HID_COMMAND_PING (0x01 | CTAP_HID_INIT_PACKET) /**< CTAPHID_PING command */
#define CTAP_HID_COMMAND_MSG (0x03 | CTAP_HID_INIT_PACKET) /**< CTAPHID_MSG command */
#define CTAP_HID_COMMAND_LOCK (0x04 | CTAP_HID_INIT_PACKET) /**< CTAPHID_LOCK command */
#define CTAP_HID_COMMAND_INIT (0x06 | CTAP_HID_INIT_PACKET) /**< CTAPHID_INIT command */
#define CTAP_HID_COMMAND_WINK (0x08 | CTAP_HID_INIT_PACKET) /**< CTAPHID_WINK command */
#define CTAP_HID_COMMAND_CBOR (0x10 | CTAP_HID_INIT_PACKET) /**< CTAPHID_CBOR command */
#define CTAP_HID_COMMAND_CANCEL (0x11 | CTAP_HID_INIT_PACKET) /**< CTAPHID_CANCEL command */
#define CTAP_HID_COMMAND_KEEPALIVE (0x3b | CTAP_HID_INIT_PACKET) /**< CTAPHID_KEEPALIVE command */
#define CTAP_HID_COMMAND_ERROR (0x3f | CTAP_HID_INIT_PACKET) /**< CTAPHID_ERROR command */
/** @} */
/**
* @name CTAP_HID capability flags
*
* @{
*/
#define CTAP_HID_CAPABILITY_WINK 0x01 /**< If set, authenticator implements CTAPHID_WINK function */
#define CTAP_HID_CAPABILITY_CBOR 0x04 /**< If set, authenticator implements CTAPHID_CBOR function */
#define CTAP_HID_CAPABILITY_NMSG 0x08 /**< If set, authenticator DOES NOT implement CTAPHID_MSG function (CTAP1 / U2F) */
/** @} */
/**
* @name CTAP_HID error codes
*
* @{
*/
#define CTAP_HID_OK 0x00 /**< Success */
#define CTAP_HID_ERR_INVALID_CMD 0x01 /**< The command in the request is invalid */
#define CTAP_HID_ERR_INVALID_PAR 0x02 /**< The parameter(s) in the request is invalid */
#define CTAP_HID_ERR_INVALID_LEN 0x03 /**< The length field (BCNT) is invalid for the request */
#define CTAP_HID_ERR_INVALID_SEQ 0x04 /**< The sequence does not match expected value */
#define CTAP_HID_ERR_MSG_TIMEOUT 0x05 /**< The message has timed out */
#define CTAP_HID_ERR_CHANNEL_BUSY 0x06 /**< The device is busy for the requesting channel */
#define CTAP_HID_ERR_LOCK_REQUIRED 0x0a /**< Command requires channel lock */
#define CTAP_HID_ERR_INVALID_CHANNEL 0x0b /**< CID is not valid. */
#define CTAP_HID_ERR_OTHER 0x7f /**< Unspecified error */
/** @} */
/**
* @name CTAP_HID status codes
*
* @{
*/
#define CTAP_HID_STATUS_PROCESSING 0x01 /**< processing status code */
#define CTAP_HID_STATUS_UPNEEDED 0x02 /**< user presence needed status code */
/** @} */
/**
* @brief CTAP_HID max number of channels
*
*/
#define CTAP_HID_CIDS_MAX 0x08
/**
* @brief CTAP_HID animation delay in milliseconds for wink command
*/
#define CTAP_HID_WINK_DELAY 400
/**
* @brief CTAP_HID broadcast channel identifier
*
*/
#define CTAP_HID_BROADCAST_CID 0xffffffff
/**
* @name CTAP_HID buffer status
*
* @{
*/
#define CTAP_HID_BUFFER_STATUS_BUFFERING 0x00 /**< packets are being buffered */
#define CTAP_HID_BUFFER_STATUS_DONE 0x01 /**< packet processing done */
#define CTAP_HID_BUFFER_STATUS_ERROR 0x02 /**< error occurred processing packets */
/** @} */
/**
* @brief CTAP_HID initialization packet struct
*
*/
typedef struct {
uint8_t cmd; /**< CTAP_HID command */
uint8_t bcnth; /**< higher byte */
uint8_t bcntl; /**< lower byte */
uint8_t payload[CTAP_HID_INIT_PAYLOAD_SIZE]; /**< packet payload */
} ctap_hid_init_pkt_t;
/**
* @brief CTAP_HID continuation packet struct
*
*/
typedef struct {
uint8_t seq; /**< packet sequence number */
uint8_t payload[CTAP_HID_CONT_PAYLOAD_SIZE]; /**< packet payload */
} ctap_hid_cont_pkt_t;
/**
* @brief CTAP_HID packet struct
*
*/
typedef struct {
uint32_t cid; /**< channel identifier */
union {
ctap_hid_init_pkt_t init; /**< initialization packet */
ctap_hid_cont_pkt_t cont; /**< continuation packet */
};
} ctap_hid_pkt_t;
/**
* @brief CTAP_HID initialization response struct
*
* CTAP specification (version 20190130) 8.1.9.1.3
*/
typedef struct __attribute__((packed)){
uint8_t nonce[CTAP_HID_INIT_NONCE_SIZE]; /**< nonce */
uint32_t cid; /**< channel identifier */
uint8_t protocol_version; /**< CTAP_HID protocol version */
uint8_t version_major; /**< major device version */
uint8_t version_minor; /**< minor device version */
uint8_t build_version; /**< build device version */
uint8_t capabilities; /**< capabilities flags */
} ctap_hid_init_resp_t;
/**
* @brief CTAP_HID channel identifier struct
*
* Used to keep state information about logical channels
*/
typedef struct {
bool taken; /**< is cid taken? */
uint32_t cid; /**< channel identifier */
uint64_t last_used; /**< timestamp of last usage */
} ctap_hid_cid_t;
/**
* @brief Initialize CTAPHID
*
* @param[in] queue CTAP transport layer event queue
*/
void fido2_ctap_transport_hid_init(event_queue_t *queue);
/**
* @brief Handle CTAP_HID packet
*
* @param[in] pkt_raw raw CTAP_HID packet
*/
void fido2_ctap_transport_hid_handle_packet(void *pkt_raw);
/**
* @brief Check logical channels for timeouts
*
* This function is used to prevent one channel from locking the authenticator.
* E.g. if a device starts a transaction that does not fit in one packet and
* sends a CTAPHID initialization packet but not continuation packet the
* authenticator will keep waiting. This function will prevent this by
* cancelling a transaction if it takes longer than
*
* CTAP specification (version 20190130) section 5.6
*
* @ref CTAP_HID_TRANSACTION_TIMEOUT
*/
void fido2_ctap_transport_hid_check_timeouts(void);
/**
* @brief Check if CTAPHID layer has received CANCEL command
*
* @return true if CANCEL command has been received
* @return false otherwise
*/
bool fido2_ctap_transport_hid_should_cancel(void);
#ifdef __cplusplus
}
#endif
#endif /* FIDO2_CTAP_TRANSPORT_HID_CTAP_HID_H */
/** @} */

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,53 @@
# Test Application for FIDO2 CTAP
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.
### 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,36 @@
/*
* Copyright (C) 2021 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@outlook.de>
* @}
*/
#include <stdio.h>
#include <stdlib.h>
#define ENABLE_DEBUG (0)
#include "debug.h"
#include "xtimer.h"
#include "fido2/ctap.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

@ -26,7 +26,7 @@
/*
this descriptor is used, because the basic usb_hid interface was developed in
conjunction with FIDO2. Descriptor is taken from CTAP2 specification
conjunction with FIDO2. Descriptor is taken from CTAP specification
(version 20190130) section 8.1.8.2
*/
static const uint8_t report_desc_ctap[] = {