diff --git a/CMakeLists.txt b/CMakeLists.txt index 63ccae8..966475f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -62,6 +62,7 @@ target_sources(pico_fido PUBLIC ${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_get_assertion.c ${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_selection.c ${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_cred_mgmt.c + ${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_config.c ) set(HSM_DRIVER "hid") include(pico-hsm-sdk/pico_hsm_sdk_import.cmake) diff --git a/src/fido/cbor.c b/src/fido/cbor.c index d23a20a..3aa1b2b 100644 --- a/src/fido/cbor.c +++ b/src/fido/cbor.c @@ -36,6 +36,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next); int cbor_get_next_assertion(const uint8_t *data, size_t len); int cbor_selection(); int cbor_cred_mgmt(const uint8_t *data, size_t len); +int cbor_config(const uint8_t *data, size_t len); const uint8_t aaguid[16] = {0x89, 0xFB, 0x94, 0xB7, 0x06, 0xC9, 0x36, 0x73, 0x9B, 0x7E, 0x30, 0x52, 0x6D, 0x96, 0x81, 0x45}; // First 16 bytes of SHA256("Pico FIDO2") @@ -63,6 +64,8 @@ int cbor_parse(const uint8_t *data, size_t len) { return cbor_selection(); else if (data[0] == CTAP_CREDENTIAL_MGMT) return cbor_cred_mgmt(data + 1, len - 1); + else if (data[0] == CTAP_CONFIG) + return cbor_config(data + 1, len - 1); return CTAP2_ERR_INVALID_CBOR; } diff --git a/src/fido/cbor_client_pin.c b/src/fido/cbor_client_pin.c index 711116c..d8373a8 100644 --- a/src/fido/cbor_client_pin.c +++ b/src/fido/cbor_client_pin.c @@ -35,8 +35,8 @@ uint8_t permissions_rp_id = 0, permission_set = 0; uint32_t usage_timer = 0, initial_usage_time_limit = 0; uint32_t max_usage_time_period = 600*1000; bool needs_power_cycle = false; -mbedtls_ecdh_context hkey; -bool hkey_init = false; +static mbedtls_ecdh_context hkey; +static bool hkey_init = false; int beginUsingPinUvAuthToken(bool userIsPresent) { paut.user_present = userIsPresent; diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c new file mode 100644 index 0000000..6d1e96d --- /dev/null +++ b/src/fido/cbor_config.c @@ -0,0 +1,228 @@ + +/* + * This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido). + * Copyright (c) 2022 Pol Henarejos. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ctap2_cbor.h" +#include "fido.h" +#include "ctap.h" +#include "bsp/board.h" +#include "files.h" +#include "apdu.h" +#include "credential.h" +#include "hsm.h" +#include "random.h" +#include "mbedtls/ecdh.h" +#include "mbedtls/chachapoly.h" +#include "mbedtls/hkdf.h" + + +static mbedtls_ecdh_context hkey; +static bool hkey_init = false; + +int cbor_config(const uint8_t *data, size_t len) { + CborParser parser; + CborValue map; + CborError error = CborNoError; + uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0; + int64_t kty = 0, alg = 0, crv = 0; + CborByteString pinUvAuthParam = {0}, vendorAutCt = {0}, kax = {0}, kay = {0}; + size_t resp_size = 0; + CborEncoder encoder, mapEncoder, mapEncoder2; + + CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map)); + uint64_t val_c = 1; + CBOR_PARSE_MAP_START(map, 1) { + uint64_t val_u = 0; + CBOR_FIELD_GET_UINT(val_u, 1); + if (val_c <= 1 && val_c != val_u) + CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); + if (val_u < val_c) + CBOR_ERROR(CTAP2_ERR_INVALID_CBOR); + val_c = val_u + 1; + if (val_u == 0x01) { + CBOR_FIELD_GET_UINT(subcommand, 1); + } + else if (val_u == 0x02) { + uint64_t subpara = 0; + CBOR_PARSE_MAP_START(_f1, 2) { + if (subcommand == 0xff) { + CBOR_FIELD_GET_UINT(subpara, 2); + if (subpara == 0x01) { + CBOR_FIELD_GET_UINT(vendorCommandId, 2); + } + else if (subpara == 0x02) { + int64_t key = 0; + CBOR_PARSE_MAP_START(_f2, 3) { + CBOR_FIELD_GET_INT(key, 3); + if (key == 1) { + CBOR_FIELD_GET_INT(kty, 3); + } + else if (key == 3) { + CBOR_FIELD_GET_INT(alg, 3); + } + else if (key == -1) { + CBOR_FIELD_GET_INT(crv, 3); + } + else if (key == -2) { + CBOR_FIELD_GET_BYTES(kax, 3); + } + else if (key == -3) { + CBOR_FIELD_GET_BYTES(kay, 3); + } + else + CBOR_ADVANCE(3); + } + CBOR_PARSE_MAP_END(_f2, 3); + } + else if (subpara == 0x03) { + CBOR_FIELD_GET_BYTES(vendorAutCt, 2); + } + } + } + CBOR_PARSE_MAP_END(_f1, 2); + } + else if (val_u == 0x03) { + CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1); + } + else if (val_u == 0x04) { // pubKeyCredParams + CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1); + } + } + CBOR_PARSE_MAP_END(map, 1); + + cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0); + + if (subcommand == 0xff) { + if (vendorCommandId == CTAP_CONFIG_KEY_AGREEMENT) { + if (hkey_init == true) + mbedtls_ecdh_free(&hkey); + + mbedtls_ecdh_init(&hkey); + mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1); + int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_gen, NULL); + mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1); + if (ret != 0) { + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + hkey_init = true; + CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); + + CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 5)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 1)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 2)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 3)); + CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -FIDO2_ALG_ECDH_ES_HKDF_256)); + CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 1)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder2, FIDO2_CURVE_P256)); + CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 2)); + uint8_t pkey[32]; + mbedtls_mpi_write_binary(&hkey.ctx.mbed_ecdh.Q.X, pkey, 32); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, 32)); + CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 3)); + mbedtls_mpi_write_binary(&hkey.ctx.mbed_ecdh.Q.Y, pkey, 32); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, 32)); + CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2)); + } + else if (vendorCommandId == CTAP_CONFIG_AUT) { + + if (*file_get_data(ef_keydev) == 0x01) { // Already encrypted + CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); + } + + if (kax.present == false || kay.present == false || alg == 0) + CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); + + if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.X, kax.data, kax.len) != 0) { + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) { + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + + mbedtls_mpi z; + mbedtls_mpi_init(&z); + int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, &z, &hkey.ctx.mbed_ecdh.Qp, &hkey.ctx.mbed_ecdh.d, random_gen, NULL); + if (ret != 0) { + mbedtls_mpi_free(&z); + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + uint8_t buf[32], Qpt[65]; + size_t olen = 0; + ret = mbedtls_ecp_point_write_binary(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.Qp, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, Qpt, sizeof(Qpt)); + if (ret != 0) { + mbedtls_mpi_free(&z); + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + ret = mbedtls_mpi_write_binary(&z, buf, sizeof(buf)); + mbedtls_mpi_free(&z); + if (ret != 0) { + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + uint8_t key_enc[12+32]; + ret = mbedtls_hkdf(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), NULL, 0, buf, sizeof(buf), Qpt, 65, key_enc, 12+32); + if (ret != 0){ + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + + mbedtls_chachapoly_context chatx; + mbedtls_chachapoly_init(&chatx); + mbedtls_chachapoly_setkey(&chatx, key_enc + 12); + ret = mbedtls_chachapoly_auth_decrypt(&chatx, vendorAutCt.len - 16, key_enc, Qpt, 65, vendorAutCt.data + vendorAutCt.len - 16, vendorAutCt.data, vendorAutCt.data); + mbedtls_chachapoly_free(&chatx); + mbedtls_ecdh_free(&hkey); + hkey_init = false; + if (ret != 0){ + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + + file_t *ef_keydev_enc = search_by_fid(EF_KEY_DEV_ENC, NULL, SPECIFY_EF); + uint8_t key_dev_enc[12+32+16]; + random_gen(NULL, key_dev_enc, 12); + mbedtls_chachapoly_init(&chatx); + mbedtls_chachapoly_setkey(&chatx, vendorAutCt.data); + ret = mbedtls_chachapoly_encrypt_and_tag(&chatx, file_get_size(ef_keydev), key_dev_enc, NULL, 0, file_get_data(ef_keydev), key_dev_enc + 12, key_dev_enc + 12 + file_get_size(ef_keydev)); + mbedtls_chachapoly_free(&chatx); + if (ret != 0){ + CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); + } + + flash_write_data_to_file(ef_keydev_enc, key_dev_enc, sizeof(key_dev_enc)); + low_flash_available(); + + goto err; //No return + } + } + else + CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); + CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); + resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1); + + err: + CBOR_FREE_BYTE_STRING(pinUvAuthParam); + CBOR_FREE_BYTE_STRING(vendorAutCt); + CBOR_FREE_BYTE_STRING(kax); + CBOR_FREE_BYTE_STRING(kay); + + if (error != CborNoError) { + if (error == CborErrorImproperValue) + return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; + return error; + } + res_APDU_size = resp_size; + return 0; +} diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c index f55d93b..4307bca 100644 --- a/src/fido/cbor_get_info.c +++ b/src/fido/cbor_get_info.c @@ -26,7 +26,7 @@ int cbor_get_info() { CborEncoder encoder, mapEncoder, arrayEncoder; CborError error = CborNoError; cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0); - CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 9)); + CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 10)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3)); @@ -79,6 +79,12 @@ int cbor_get_info() { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0E)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, PICO_FIDO_VERSION)); // firmwareVersion + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15)); + CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2)); + CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT)); + CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_KEY_AGREEMENT)); + CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); + CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); err: if (error != CborNoError) diff --git a/src/fido/ctap.h b/src/fido/ctap.h index d058be7..2561aef 100644 --- a/src/fido/ctap.h +++ b/src/fido/ctap.h @@ -114,6 +114,10 @@ typedef struct { #define CTAP_GET_NEXT_ASSERTION 0x08 #define CTAP_CREDENTIAL_MGMT 0x0A #define CTAP_SELECTION 0x0B +#define CTAP_CONFIG 0x0D + +#define CTAP_CONFIG_AUT 0x03e43f56b34285e2 +#define CTAP_CONFIG_KEY_AGREEMENT 0x1831a40f04a25ed9 // Command status responses