30 Commits
v2.2 ... v2.8

Author SHA1 Message Date
Pol Henarejos
71c0e865dc Fixed RP attachment to token.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-11-28 18:27:12 +01:00
Pol Henarejos
052ff2d60a Fix requesting a UV token.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-11-28 18:02:53 +01:00
Pol Henarejos
8b70c864a4 Added support for enterprise attestation.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-11-28 17:39:21 +01:00
Pol Henarejos
765db0e98b Update README.md
Added new features for version 2.6.
2022-11-24 15:35:34 +01:00
Pol Henarejos
6b2e95deb0 Adding support for minPinLength extension.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-11-23 19:11:03 +01:00
Pol Henarejos
d45fa9aae0 Added support for setMinPinLength.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-11-23 17:01:18 +01:00
Pol Henarejos
23c7e16e6e Fix counting PIN retries.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-11-23 16:42:49 +01:00
Pol Henarejos
5923f435fe Add support for authenticatorConfig verification.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-11-23 15:24:09 +01:00
Pol Henarejos
04868f2d7b Added permissions support.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-11-23 13:00:28 +01:00
Pol Henarejos
54c0769dbd Upgrading to version 2.4
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-11-15 12:12:06 +01:00
Pol Henarejos
0bbcba2f60 Upgrade to version 2.4
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-11-15 11:59:46 +01:00
Pol Henarejos
723648173d Update README.md 2022-11-15 11:59:06 +01:00
Pol Henarejos
e6c128fe0d Linux uses the generic interface. Needs deep testing.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-11-07 13:15:24 +01:00
Pol Henarejos
2174b516c3 Using ecdh interface from mbedtls.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-11-07 13:15:09 +01:00
Pol Henarejos
4577e4430c Moving AUT UNLOCK to Vendor command instead of using VendorConfig.
To do this a MSE command is added, to manage a secure environment. It performs a ephemeral ECDH exchange to derive a shared secret that will be used by vendor commands to convey ciphered data.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-30 00:47:50 +02:00
Pol Henarejos
9a8f4c0f4d Moving to last pico-hsm-sdk to support Vendor command.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-29 19:41:28 +02:00
Pol Henarejos
e21d985344 Adding support for specific vendor HID command (0x41).
It is a self implementation, based on CBOR command.
data[0] conveys the command and the contents mapped in CBOR encoding.
The map uses the authConfig template, where the fist item in the map is the subcommand (enable/disable at this moment), the second is a map of the parameters, the third and fourth are the pinUvParam and pinUvProtocol.

With this format only a single vendor HID command is necessary (0x41), which will be used for all my own commands, by using the command id in data[0] like with CBOR.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-29 19:41:00 +02:00
Pol Henarejos
43cd8869f9 Adding support for backup.
Now it is possible to backup and restore the internal keys to recover a pico fido. The process is splitted in two parts: a list of 24 words and a file, which stores the security key.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-28 00:31:50 +02:00
Pol Henarejos
a42131876f Adding disable secure key.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-27 20:11:12 +02:00
Pol Henarejos
e1f4e3035d Adding first backend, for macOS.
In macOS, a SECP256R1 key is generated locally and stored in the keyring.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-27 19:02:03 +02:00
Pol Henarejos
71ecb23af6 Adding support for disabling secure aut.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-24 00:04:55 +02:00
Pol Henarejos
8c21a2bbcd Adding command line parsing.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-23 23:24:35 +02:00
Pol Henarejos
53cc16ab6d Preliminar test tool for device lock/unlock
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-19 18:33:11 +02:00
Pol Henarejos
f213854f8b Added unlock config command to unlock the device at every boot with an external key.
Signed-off-by: trocotronic <trocotronic@redyc.com>
2022-10-19 16:46:32 +02:00
Pol Henarejos
2c125e76eb Add ef of keydev encrypted.
Signed-off-by: trocotronic <trocotronic@redyc.com>
2022-10-19 16:46:31 +02:00
Pol Henarejos
19d8f16056 Clean struct before return.
Signed-off-by: trocotronic <trocotronic@redyc.com>
2022-10-19 16:46:31 +02:00
Pol Henarejos
40065217fd Add a config command to unlock.
Signed-off-by: trocotronic <trocotronic@redyc.com>
2022-10-19 16:46:31 +02:00
Pol Henarejos
32c938674a Adding pico-fido-tool for enabling some configs.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-17 17:37:54 +02:00
Pol Henarejos
4425722a71 Adding support for CBOR CONFIG.
This first support includes a vendor command for encrypting the key device with external key.

Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-17 17:37:39 +02:00
Pol Henarejos
69eef7651c Adding EF_KEY_DEV_ENC.
Signed-off-by: Pol Henarejos <pol.henarejos@cttc.es>
2022-10-17 17:35:57 +02:00
24 changed files with 1187 additions and 83 deletions

View File

@@ -62,6 +62,8 @@ 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
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_vendor.c
)
set(HSM_DRIVER "hid")
include(pico-hsm-sdk/pico_hsm_sdk_import.cmake)

View File

@@ -16,6 +16,13 @@ Pico FIDO has implemented the following features:
- ECDSA authentication
- App registration and login
- Device selection
- Support for vendor Config
- Backup with 24 words
- Secure lock to protect the device from flash dumpings
- Permissions support (MC, GA, CM, ACFG)
- Authenticator configuration
- minPinLength extension
- Self attestation
All these features are compliant with the specification. Therefore, if you detect some behaviour that is not expected or it does not follow the rules of specs, please open an issue.

View File

@@ -1,7 +1,7 @@
#!/bin/bash
VERSION_MAJOR="2"
VERSION_MINOR="2"
VERSION_MINOR="4"
rm -rf release/*
cd build_release

View File

@@ -27,7 +27,6 @@
const bool _btrue = true, _bfalse = false;
extern int cbor_process(const uint8_t *data, size_t len);
int cbor_reset();
int cbor_get_info();
int cbor_make_credential(const uint8_t *data, size_t len);
@@ -36,33 +35,43 @@ 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);
int cbor_vendor(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")
const uint8_t *cbor_data = NULL;
size_t cbor_len = 0;
static const uint8_t *cbor_data = NULL;
static size_t cbor_len = 0;
static uint8_t cmd = 0;
int cbor_parse(const uint8_t *data, size_t len) {
int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
if (len == 0)
return CTAP1_ERR_INVALID_LEN;
DEBUG_DATA(data+1,len-1);
driver_prepare_response();
if (data[0] == CTAP_MAKE_CREDENTIAL)
return cbor_make_credential(data + 1, len - 1);
if (data[0] == CTAP_GET_INFO)
return cbor_get_info();
else if (data[0] == CTAP_RESET)
return cbor_reset();
else if (data[0] == CTAP_CLIENT_PIN)
return cbor_client_pin(data + 1, len - 1);
else if (data[0] == CTAP_GET_ASSERTION)
return cbor_get_assertion(data + 1, len - 1, false);
else if (data[0] == CTAP_GET_NEXT_ASSERTION)
return cbor_get_next_assertion(data + 1, len - 1);
else if (data[0] == CTAP_SELECTION)
return cbor_selection();
else if (data[0] == CTAP_CREDENTIAL_MGMT)
return cbor_cred_mgmt(data + 1, len - 1);
if (cmd == CTAPHID_CBOR) {
if (data[0] == CTAP_MAKE_CREDENTIAL)
return cbor_make_credential(data + 1, len - 1);
if (data[0] == CTAP_GET_INFO)
return cbor_get_info();
else if (data[0] == CTAP_RESET)
return cbor_reset();
else if (data[0] == CTAP_CLIENT_PIN)
return cbor_client_pin(data + 1, len - 1);
else if (data[0] == CTAP_GET_ASSERTION)
return cbor_get_assertion(data + 1, len - 1, false);
else if (data[0] == CTAP_GET_NEXT_ASSERTION)
return cbor_get_next_assertion(data + 1, len - 1);
else if (data[0] == CTAP_SELECTION)
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);
}
else if (cmd == CTAP_VENDOR_CBOR) {
return cbor_vendor(data, len);
}
return CTAP2_ERR_INVALID_CBOR;
}
@@ -78,7 +87,7 @@ void cbor_thread() {
break;
}
apdu.sw = cbor_parse(cbor_data, cbor_len);
apdu.sw = cbor_parse(cmd, cbor_data, cbor_len);
if (apdu.sw == 0)
DEBUG_DATA(res_APDU + 1, res_APDU_size);
@@ -88,9 +97,10 @@ void cbor_thread() {
}
}
int cbor_process(const uint8_t *data, size_t len) {
int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) {
cbor_data = data;
cbor_len = len;
cmd = last_cmd;
res_APDU = ctap_resp->init.data + 1;
res_APDU_size = 0;
return 1;

View File

@@ -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;
@@ -59,7 +59,7 @@ void clearUserVerifiedFlag() {
void clearPinUvAuthTokenPermissionsExceptLbw() {
if (paut.in_use == true)
paut.permissions = FIDO2_PERMISSION_LBW;
paut.permissions = CTAP_PERMISSION_LBW;
}
void stopUsingPinUvAuthToken() {
@@ -369,12 +369,17 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
uint8_t pin_len = 0;
while (paddedNewPin[pin_len] != 0 && pin_len < sizeof(paddedNewPin))
pin_len++;
if (pin_len < 4)
uint8_t minPin = 4;
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
if (file_has_data(ef_minpin))
minPin = *file_get_data(ef_minpin);
if (pin_len < minPin)
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
uint8_t hsh[33];
uint8_t hsh[34];
hsh[0] = MAX_PIN_RETRIES;
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 1);
flash_write_data_to_file(ef_pin, hsh, 1+16);
hsh[1] = pin_len;
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 2);
flash_write_data_to_file(ef_pin, hsh, 2+16);
low_flash_available();
goto err; //No return
}
@@ -408,16 +413,19 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
uint8_t retries = *file_get_data(ef_pin) - 1;
flash_write_data_to_file(ef_pin, &retries, 1);
uint8_t pin_data[18];
memcpy(pin_data, file_get_data(ef_pin), 18);
pin_data[0] -= 1;
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
low_flash_available();
uint8_t retries = pin_data[0];
uint8_t paddedNewPin[64];
ret = decrypt(pinUvAuthProtocol, sharedSecret, pinHashEnc.data, pinHashEnc.len, paddedNewPin);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
low_flash_available();
if (memcmp(paddedNewPin, file_get_data(ef_pin)+1, 16) != 0) {
if (memcmp(paddedNewPin, file_get_data(ef_pin)+2, 16) != 0) {
regenerate();
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
if (retries == 0) {
@@ -430,9 +438,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
else
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
retries = MAX_PIN_RETRIES;
pin_data[0] = MAX_PIN_RETRIES;
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
low_flash_available();
new_pin_mismatches = 0;
flash_write_data_to_file(ef_pin, &retries, 1);
ret = decrypt(pinUvAuthProtocol, sharedSecret, newPinEnc.data, newPinEnc.len, paddedNewPin);
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
if (ret != 0) {
@@ -443,23 +452,44 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
uint8_t pin_len = 0;
while (paddedNewPin[pin_len] != 0 && pin_len < sizeof(paddedNewPin))
pin_len++;
if (pin_len < 4)
uint8_t minPin = 4;
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
if (file_has_data(ef_minpin))
minPin = *file_get_data(ef_minpin);
if (pin_len < minPin)
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
uint8_t hsh[33];
hsh[0] = MAX_PIN_RETRIES;
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 1);
flash_write_data_to_file(ef_pin, hsh, 1+16);
hsh[1] = pin_len;
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 2);
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 && memcmp(hsh+2, file_get_data(ef_pin)+2, 16) == 0)
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
flash_write_data_to_file(ef_pin, hsh, 2+16);
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
uint8_t *tmp = (uint8_t *)calloc(1, file_get_size(ef_minpin));
memcpy(tmp, file_get_data(ef_minpin), file_get_size(ef_minpin));
tmp[1] = 0;
flash_write_data_to_file(ef_minpin, tmp, file_get_size(ef_minpin));
free(tmp);
}
low_flash_available();
resetPinUvAuthToken();
goto err; // No return
}
else if (subcommand == 0x9 || subcommand == 0x5) { //getUVRgetPinUvAuthTokenUsingPinWithPermissionsetries
else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions
if (kax.present == false || kay.present == false || pinUvAuthProtocol == 0 || alg == 0 || pinHashEnc.present == false)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2)
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
if ((subcommand == 0x9 && permissions == 0) || (subcommand == 0x5 && (permissions != 0 || rpId.present == true)))
if (subcommand == 0x5 && (permissions != 0 || rpId.present == true))
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
if (subcommand == 0x9) {
if (permissions == 0)
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
if ((permissions & CTAP_PERMISSION_BE) || (permissions & CTAP_PERMISSION_LBW)) // Not supported yet
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
}
if (!file_has_data(ef_pin))
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
if (*file_get_data(ef_pin) == 0)
@@ -476,16 +506,19 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t retries = *file_get_data(ef_pin) - 1;
flash_write_data_to_file(ef_pin, &retries, 1);
uint8_t pin_data[18];
memcpy(pin_data, file_get_data(ef_pin), 18);
pin_data[0] -= 1;
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
low_flash_available();
uint8_t retries = pin_data[0];
uint8_t paddedNewPin[64], poff = (pinUvAuthProtocol-1)*IV_SIZE;
ret = decrypt(pinUvAuthProtocol, sharedSecret, pinHashEnc.data, pinHashEnc.len, paddedNewPin);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
low_flash_available();
if (memcmp(paddedNewPin, file_get_data(ef_pin)+1, 16) != 0) {
if (memcmp(paddedNewPin, file_get_data(ef_pin)+2, 16) != 0) {
regenerate();
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
if (retries == 0) {
@@ -498,16 +531,22 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
else
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
retries = MAX_PIN_RETRIES;
pin_data[0] = MAX_PIN_RETRIES;
new_pin_mismatches = 0;
flash_write_data_to_file(ef_pin, &retries, 1);
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
low_flash_available();
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1)
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
resetPinUvAuthToken();
beginUsingPinUvAuthToken(false);
if (subcommand == 0x05)
permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA;
paut.permissions = permissions;
if (rpId.present == true)
memcpy(paut.rp_id_hash, rpId.data, 32);
else
memset(paut.rp_id_hash, 0, sizeof(paut.rp_id_hash));
if (rpId.present == true) {
mbedtls_sha256((uint8_t *)rpId.data, rpId.len, paut.rp_id_hash, 0);
paut.has_rp_id = true;
}
uint8_t pinUvAuthToken_enc[32+IV_SIZE];
encrypt(pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));

215
src/fido/cbor_config.c Normal file
View File

@@ -0,0 +1,215 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "common.h"
#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/sha256.h"
extern uint8_t keydev_dec[32];
extern bool has_keydev_dec;
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, newMinPinLength = 0;
CborByteString pinUvAuthParam = {0}, vendorAutCt = {0};
CborCharString minPinLengthRPIDs[32] = {0};
size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0;
CborEncoder encoder, mapEncoder;
uint8_t *raw_subpara = NULL;
const bool *forceChangePin = NULL;
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;
raw_subpara = (uint8_t *)cbor_value_get_next_byte(&_f1);
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) {
CBOR_FIELD_GET_BYTES(vendorAutCt, 2);
}
}
else if (subcommand == 0x03) {
CBOR_FIELD_GET_UINT(subpara, 2);
if (subpara == 0x01) {
CBOR_FIELD_GET_UINT(newMinPinLength, 2);
}
else if (subpara == 0x02) {
CBOR_PARSE_ARRAY_START(_f2, 3) {
CBOR_FIELD_GET_TEXT(minPinLengthRPIDs[minPinLengthRPIDs_len], 3);
minPinLengthRPIDs_len++;
if (minPinLengthRPIDs_len >= 32)
CBOR_ERROR(CTAP2_ERR_KEY_STORE_FULL);
}
CBOR_PARSE_ARRAY_END(_f2, 3);
}
else if (subpara == 0x03) {
CBOR_FIELD_GET_BOOL(forceChangePin, 2);
}
}
}
CBOR_PARSE_MAP_END(_f1, 2);
raw_subpara_len = cbor_value_get_next_byte(&_f1) - raw_subpara;
}
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 (pinUvAuthParam.present == false)
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
if (pinUvAuthProtocol == 0)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
uint8_t *verify_payload = (uint8_t *)calloc(1, 32 + 1 + 1 + raw_subpara_len);
memset(verify_payload, 0xff, 32);
verify_payload[32] = 0x0d;
verify_payload[33] = subcommand;
memcpy(verify_payload + 34, raw_subpara, raw_subpara_len);
error = verify(pinUvAuthProtocol, paut.data, verify_payload, 32 + 1 + 1 + raw_subpara_len, pinUvAuthParam.data);
free(verify_payload);
if (error != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (!(paut.permissions & CTAP_PERMISSION_ACFG))
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (subcommand == 0xff) {
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) {
if (!file_has_data(ef_keydev_enc))
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
if (has_keydev_dec == false)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
flash_write_data_to_file(ef_keydev, keydev_dec, sizeof(keydev_dec));
mbedtls_platform_zeroize(keydev_dec, sizeof(keydev_dec));
flash_write_data_to_file(ef_keydev_enc, NULL, 0); // Set ef to 0 bytes
low_flash_available();
}
else if (vendorCommandId == CTAP_CONFIG_AUT_ENABLE) {
if (!file_has_data(ef_keydev))
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
if (mse.init == false)
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
mbedtls_chachapoly_context chatx;
int ret = mse_decrypt_ct(vendorAutCt.data, vendorAutCt.len);
if (ret != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
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));
mbedtls_platform_zeroize(key_dev_enc, sizeof(key_dev_enc));
flash_write_data_to_file(ef_keydev, key_dev_enc, file_get_size(ef_keydev)); // Overwrite ef with 0
flash_write_data_to_file(ef_keydev, NULL, 0); // Set ef to 0 bytes
low_flash_available();
}
else {
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
}
goto err;
}
else if (subcommand == 0x03) {
uint8_t currentMinPinLen = 4;
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
if (file_has_data(ef_minpin))
currentMinPinLen = *file_get_data(ef_minpin);
if (newMinPinLength == 0)
newMinPinLength = currentMinPinLen;
else if (newMinPinLength > 0 && newMinPinLength < currentMinPinLen)
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
if (forceChangePin == ptrue && !file_has_data(ef_pin))
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength)
forceChangePin = ptrue;
uint8_t *data = (uint8_t *)calloc(1, 2 + minPinLengthRPIDs_len * 32);
data[0] = newMinPinLength;
data[1] = forceChangePin == ptrue ? 1 : 0;
for (int m = 0; m < minPinLengthRPIDs_len; m++) {
mbedtls_sha256((uint8_t *)minPinLengthRPIDs[m].data, minPinLengthRPIDs[m].len, data + 2 + m*32, 0);
}
flash_write_data_to_file(ef_minpin, data, 2 + minPinLengthRPIDs_len * 32);
low_flash_available();
goto err; //No return
}
else if (subcommand == 0x01) {
set_opts(get_opts() | FIDO2_OPT_EA);
goto err;
}
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);
for (int i = 0; i < minPinLengthRPIDs_len; i++) {
CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]);
}
if (error != CborNoError)
{
if (error == CborErrorImproperValue)
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
return error;
}
res_APDU_size = resp_size;
return 0;
}

View File

@@ -1,4 +1,3 @@
/*
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
* Copyright (c) 2022 Pol Henarejos.
@@ -116,6 +115,8 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
if(subcommand == 0x01) {
if (verify(pinUvAuthProtocol, paut.data, (const uint8_t *)"\x01", 1, pinUvAuthParam.data) != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
uint8_t existing = 0;
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
if (file_has_data(search_dynamic_file(EF_CRED + i)))
@@ -132,6 +133,8 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
if (subcommand == 0x02) {
if (verify(pinUvAuthProtocol, paut.data, (const uint8_t *)"\x02", 1, pinUvAuthParam.data) != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
rp_counter = 1;
rp_total = 0;
}
@@ -176,6 +179,8 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
*(raw_subpara-1) = 0x04;
if (verify(pinUvAuthProtocol, paut.data, raw_subpara-1, raw_subpara_len+1, pinUvAuthParam.data) != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (!(paut.permissions & CTAP_PERMISSION_CM) || (paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
cred_counter = 1;
cred_total = 0;
}
@@ -285,6 +290,8 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
*(raw_subpara - 1) = 0x06;
if (verify(pinUvAuthProtocol, paut.data, raw_subpara-1, raw_subpara_len+1, pinUvAuthParam.data) != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (!(paut.permissions & CTAP_PERMISSION_CM) || (paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file(EF_CRED + i);
if (file_has_data(ef) && memcmp(file_get_data(ef)+32, credentialId.id.data, MIN(file_get_size(ef)-32, credentialId.id.len)) == 0) {
@@ -317,6 +324,8 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
*(raw_subpara - 1) = 0x07;
if (verify(pinUvAuthProtocol, paut.data, raw_subpara-1, raw_subpara_len+1, pinUvAuthParam.data) != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (!(paut.permissions & CTAP_PERMISSION_CM) || (paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file(EF_CRED + i);
if (file_has_data(ef) && memcmp(file_get_data(ef)+32, credentialId.id.data, MIN(file_get_size(ef)-32, credentialId.id.len)) == 0) {

View File

@@ -245,6 +245,10 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (getUserVerifiedFlagValue() == false)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (!(paut.permissions & CTAP_PERMISSION_GA))
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (paut.has_rp_id == true && memcmp(paut.rp_id_hash, rp_id_hash, 32) != 0)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
flags |= FIDO2_AUT_FLAG_UV;
// Check pinUvAuthToken permissions. See 6.2.2.4
}

View File

@@ -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, 11));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3));
@@ -36,16 +36,19 @@ int cbor_get_info() {
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "minPinLength"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aaguid, sizeof(aaguid)));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &arrayEncoder, 5));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &arrayEncoder, 7));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "ep"));
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, get_opts() & FIDO2_OPT_EA));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "rk"));
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credMgmt"));
@@ -59,6 +62,8 @@ int cbor_get_info() {
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, false));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "pinUvAuthToken"));
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "setMinPINLength"));
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x06));
@@ -73,12 +78,27 @@ int cbor_get_info() {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x08));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CRED_ID_LENGTH)); // MAX_CRED_ID_MAX_LENGTH
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C));
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1)
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
else
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, false));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0D));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 4)); // minPINLength
if (file_has_data(ef_minpin))
CBOR_CHECK(cbor_encode_uint(&mapEncoder, *file_get_data(ef_minpin))); // minPINLength
else
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 4)); // minPINLength
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_ENABLE));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
err:
if (error != CborNoError)

View File

@@ -118,6 +118,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_FIELD_GET_KEY_TEXT(2);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", extensions.hmac_secret);
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "minPinLength", extensions.minPinLength);
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
@@ -206,6 +207,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
}
if (enterpriseAttestation > 0) {
if (!(get_opts() & FIDO2_OPT_EA)) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (enterpriseAttestation != 1 && enterpriseAttestation != 2) { //9.2.1
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
}
@@ -215,10 +219,17 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
int ret = verify(pinUvAuthProtocol, paut.data, clientDataHash.data, clientDataHash.len, pinUvAuthParam.data);
if (ret != CborNoError)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (!(paut.permissions & CTAP_PERMISSION_MC))
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (paut.has_rp_id == true && memcmp(paut.rp_id_hash, rp_id_hash, 32) != 0)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (getUserVerifiedFlagValue() == false)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
flags |= FIDO2_AUT_FLAG_UV;
// Check pinUvAuthToken permissions. See 6.1.2.11
if (paut.has_rp_id == false) {
memcpy(paut.rp_id_hash, rp_id_hash, 32);
paut.has_rp_id = true;
}
}
for (int e = 0; e < excludeList_len; e++) { //12.1
@@ -251,14 +262,6 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options, &extensions, (!ka || ka->use_sign_count == ptrue), alg, curve, cred_id, &cred_id_len));
mbedtls_ecdsa_context ekey;
mbedtls_ecdsa_init(&ekey);
int ret = fido_load_key(curve, cred_id, &ekey);
if (ret != 0) {
mbedtls_ecdsa_free(&ekey);
CBOR_ERROR(CTAP1_ERR_OTHER);
}
if (getUserVerifiedFlagValue())
flags |= FIDO2_AUT_FLAG_UV;
size_t ext_len = 0;
@@ -267,10 +270,25 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
if (extensions.present == true) {
cbor_encoder_init(&encoder, ext, sizeof(ext), 0);
int l = 0;
uint8_t minPinLen = 0;
if (extensions.hmac_secret != NULL)
l++;
if (extensions.credProtect != 0)
l++;
if (extensions.minPinLength != NULL) {
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
if (file_has_data(ef_minpin)) {
uint8_t *minpin_data = file_get_data(ef_minpin);
for (int o = 2; o < file_get_size(ef_minpin); o += 32) {
if (memcmp(minpin_data + o, rp_id_hash, 32) == 0) {
minPinLen = minpin_data[0];
if (minPinLen > 0)
l++;
break;
}
}
}
}
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
if (extensions.credProtect != 0) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect"));
@@ -281,15 +299,29 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, *extensions.hmac_secret));
}
if (minPinLen > 0) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen));
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
flags |= FIDO2_AUT_FLAG_ED;
}
uint8_t pkey[66];
const mbedtls_ecp_curve_info *cinfo = mbedtls_ecp_curve_info_from_grp_id(ekey.grp.id);
if (cinfo == NULL)
mbedtls_ecdsa_context ekey;
mbedtls_ecdsa_init(&ekey);
int ret = fido_load_key(curve, cred_id, &ekey);
if (ret != 0) {
mbedtls_ecdsa_free(&ekey);
CBOR_ERROR(CTAP1_ERR_OTHER);
}
const mbedtls_ecp_curve_info *cinfo = mbedtls_ecp_curve_info_from_grp_id(ekey.grp.id);
if (cinfo == NULL) {
mbedtls_ecdsa_free(&ekey);
CBOR_ERROR(CTAP1_ERR_OTHER);
}
size_t olen = 0;
uint32_t ctr = get_sign_counter();
uint8_t cbor_buf[1024];
@@ -326,15 +358,17 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
memcpy(pa, cred_id, cred_id_len); pa += cred_id_len;
memcpy(pa, cbor_buf, rs); pa += rs;
memcpy(pa, ext, ext_len); pa += ext_len;
if (pa-aut_data != aut_data_len)
if (pa-aut_data != aut_data_len) {
mbedtls_ecdsa_free(&ekey);
CBOR_ERROR(CTAP1_ERR_OTHER);
}
memcpy(pa, clientDataHash.data, clientDataHash.len);
uint8_t hash[32], sig[MBEDTLS_ECDSA_MAX_LEN];
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), aut_data, aut_data_len+clientDataHash.len, hash);
bool self_attestation = true;
if (ka && ka->use_self_attestation == pfalse) {
if (enterpriseAttestation == 2 || (ka && ka->use_self_attestation == pfalse)) {
mbedtls_ecdsa_free(&ekey);
mbedtls_ecdsa_init(&ekey);
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, file_get_data(ef_keydev), 32);
@@ -344,7 +378,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
mbedtls_ecdsa_free(&ekey);
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 3));
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 4));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "packed"));
@@ -366,6 +400,8 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
}
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, enterpriseAttestation == 2));
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);

253
src/fido/cbor_vendor.c Normal file
View File

@@ -0,0 +1,253 @@
/*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "common.h"
#include "ctap2_cbor.h"
#include "fido.h"
#include "ctap.h"
#include "files.h"
#include "apdu.h"
#include "hsm.h"
#include "random.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/chachapoly.h"
#include "mbedtls/hkdf.h"
extern uint8_t keydev_dec[32];
extern bool has_keydev_dec;
mse_t mse = {.init = false};
int mse_decrypt_ct(uint8_t *data, size_t len) {
mbedtls_chachapoly_context chatx;
mbedtls_chachapoly_init(&chatx);
mbedtls_chachapoly_setkey(&chatx, mse.key_enc + 12);
int ret = mbedtls_chachapoly_auth_decrypt(&chatx, len - 16, mse.key_enc, mse.Qpt, 65, data + len - 16, data, data);
mbedtls_chachapoly_free(&chatx);
return ret;
}
int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
CborParser parser;
CborValue map;
CborError error = CborNoError;
CborByteString pinUvAuthParam = {0}, vendorParam = {0}, kax = {0}, kay = {0};
size_t resp_size = 0;
uint64_t vendorCmd = 0, pinUvAuthProtocol = 0;
int64_t kty = 0, alg = 0, crv = 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(vendorCmd, 1);
}
else if (val_u == 0x02) {
uint64_t subpara = 0;
CBOR_PARSE_MAP_START(_f1, 2) {
CBOR_FIELD_GET_UINT(subpara, 2);
if (subpara == 0x01) {
CBOR_FIELD_GET_BYTES(vendorParam, 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
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
}
else if (val_u == 0x03) {
CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1);
}
else if (val_u == 0x04) {
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 (cmd == CTAP_VENDOR_BACKUP) {
if (vendorCmd == 0x01) {
if (has_keydev_dec == false)
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_keydev_enc), file_get_size(ef_keydev_enc)));
}
else if (vendorCmd == 0x02) {
if (vendorParam.present == false)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
uint8_t zeros[32];
memset(zeros, 0, sizeof(zeros));
flash_write_data_to_file(ef_keydev_enc, vendorParam.data, vendorParam.len);
flash_write_data_to_file(ef_keydev, zeros, file_get_size(ef_keydev)); // Overwrite ef with 0
flash_write_data_to_file(ef_keydev, NULL, 0); // Set ef to 0 bytes
low_flash_available();
goto err;
}
else {
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
}
}
else if (cmd == CTAP_VENDOR_MSE) {
if (vendorCmd == 0x01) { // KeyAgreement
if (kax.present == false || kay.present == false || alg == 0)
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
mbedtls_ecdh_context 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);
}
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.X, kax.data, kax.len) != 0) {
mbedtls_ecdh_free(&hkey);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) {
mbedtls_ecdh_free(&hkey);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t buf[MBEDTLS_ECP_MAX_BYTES];
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, mse.Qpt, sizeof(mse.Qpt));
if (ret != 0) {
mbedtls_ecdh_free(&hkey);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
ret = mbedtls_ecdh_calc_secret(&hkey, &olen, buf, MBEDTLS_ECP_MAX_BYTES, random_gen, NULL);
if (ret != 0) {
mbedtls_ecdh_free(&hkey);
mbedtls_platform_zeroize(buf, sizeof(buf));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
ret = mbedtls_hkdf(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), NULL, 0, buf, olen, mse.Qpt, sizeof(mse.Qpt), mse.key_enc, sizeof(mse.key_enc));
mbedtls_platform_zeroize(buf, sizeof(buf));
if (ret != 0) {
mbedtls_ecdh_free(&hkey);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
mse.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));
mbedtls_ecdh_free(&hkey);
}
}
else if (cmd == CTAP_VENDOR_UNLOCK) {
if (mse.init == false)
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
mbedtls_chachapoly_context chatx;
int ret = mse_decrypt_ct(vendorParam.data, vendorParam.len);
if (ret != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (!file_has_data(ef_keydev_enc))
CBOR_ERROR(CTAP2_ERR_INTEGRITY_FAILURE);
uint8_t *keyenc = file_get_data(ef_keydev_enc);
size_t keyenc_len = file_get_size(ef_keydev_enc);
mbedtls_chachapoly_init(&chatx);
mbedtls_chachapoly_setkey(&chatx, vendorParam.data);
ret = mbedtls_chachapoly_auth_decrypt(&chatx, sizeof(keydev_dec), keyenc, NULL, 0, keyenc + keyenc_len - 16, keyenc + 12, keydev_dec);
mbedtls_chachapoly_free(&chatx);
if (ret != 0){
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
has_keydev_dec = true;
goto err;
}
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(vendorParam);
if (error != CborNoError) {
if (error == CborErrorImproperValue)
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
return error;
}
res_APDU_size = resp_size;
return 0;
}
int cbor_vendor(const uint8_t *data, size_t len) {
if (len == 0)
return CTAP1_ERR_INVALID_LEN;
if (data[0] >= CTAP_VENDOR_BACKUP)
return cbor_vendor_generic(data[0], data + 1, len - 1);
return CTAP2_ERR_INVALID_CBOR;
}

View File

@@ -38,7 +38,9 @@ int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id
mbedtls_chachapoly_context chatx;
mbedtls_chachapoly_init(&chatx);
mbedtls_chachapoly_setkey(&chatx, key);
return mbedtls_chachapoly_auth_decrypt(&chatx, cred_id_len - (4 + 12 + 16), iv, rp_id_hash, 32, tag, cipher, cipher);
int ret = mbedtls_chachapoly_auth_decrypt(&chatx, cred_id_len - (4 + 12 + 16), iv, rp_id_hash, 32, tag, cipher, cipher);
mbedtls_chachapoly_free(&chatx);
return ret;
}
int credential_create(CborCharString *rpId, CborByteString *userId, CborCharString *userName, CborCharString *userDisplayName, CredOptions *opts, CredExtensions *extensions, bool use_sign_count, int alg, int curve, uint8_t *cred_id, size_t *cred_id_len) {

View File

@@ -30,6 +30,7 @@ typedef struct CredOptions {
typedef struct CredExtensions {
const bool *hmac_secret;
uint64_t credProtect;
const bool *minPinLength;
bool present;
} CredExtensions;

View File

@@ -114,6 +114,32 @@ 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_ENABLE 0x03e43f56b34285e2
#define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9
#define CTAP_VENDOR_CBOR (CTAPHID_VENDOR_FIRST + 1)
#define CTAP_VENDOR_BACKUP 0x01
#define CTAP_VENDOR_MSE 0x02
#define CTAP_VENDOR_UNLOCK 0x03
#define CTAP_PERMISSION_MC 0x01 // MakeCredential
#define CTAP_PERMISSION_GA 0x02 // GetAssertion
#define CTAP_PERMISSION_CM 0x04 // CredentialManagement
#define CTAP_PERMISSION_BE 0x08 // BioEnrollment
#define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite
#define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration
typedef struct mse {
uint8_t Qpt[65];
uint8_t key_enc[12 + 32];
bool init;
} mse_t;
extern mse_t mse;
extern int mse_decrypt_ct(uint8_t *, size_t);
// Command status responses

View File

@@ -25,7 +25,7 @@
extern uint8_t *driver_prepare_response();
extern void driver_exec_finished(size_t size_next);
extern int cbor_process(const uint8_t *data, size_t len);
extern int cbor_process(uint8_t, const uint8_t *data, size_t len);
extern const uint8_t aaguid[16];
extern const bool _btrue, _bfalse;

View File

@@ -39,6 +39,9 @@ int fido_unload();
pinUvAuthToken_t paut = {0};
uint8_t keydev_dec[32];
bool has_keydev_dec = false;
const uint8_t fido_aid[] = {
8,
0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01
@@ -117,9 +120,12 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
}
int load_keydev(uint8_t *key) {
if (!ef_keydev || file_get_size(ef_keydev) == 0)
if (has_keydev_dec == false && !file_has_data(ef_keydev))
return CCID_ERR_MEMORY_FATAL;
memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev));
if (has_keydev_dec == true)
memcpy(key, keydev_dec, sizeof(keydev_dec));
else
memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev));
//return mkek_decrypt(key, file_get_size(ef_keydev));
return CCID_OK;
}
@@ -201,8 +207,9 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
int scan_files(bool core1) {
ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF);
ef_keydev_enc = search_by_fid(EF_KEY_DEV_ENC, NULL, SPECIFY_EF);
if (ef_keydev) {
if (!file_has_data(ef_keydev)) {
if (!file_has_data(ef_keydev) && !file_has_data(ef_keydev_enc)) {
printf("KEY DEVICE is empty. Generating SECP256R1 curve...");
mbedtls_ecdsa_context ecdsa;
mbedtls_ecdsa_init(&ecdsa);
@@ -318,6 +325,19 @@ uint32_t get_sign_counter() {
return (*caddr) | (*(caddr + 1) << 8) | (*(caddr + 2) << 16) | (*(caddr + 3) << 24);
}
uint8_t get_opts() {
file_t *ef = search_by_fid(EF_OPTS, NULL, SPECIFY_EF);
if (file_has_data(ef))
return *file_get_data(ef);
return 0;
}
void set_opts(uint8_t opts) {
file_t *ef = search_by_fid(EF_OPTS, NULL, SPECIFY_EF);
flash_write_data_to_file(ef, &opts, sizeof(uint8_t));
low_flash_available();
}
typedef struct cmd
{
uint8_t ins;

View File

@@ -63,12 +63,7 @@ extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSec
#define FIDO2_AUT_FLAG_AT 0x40
#define FIDO2_AUT_FLAG_ED 0x80
#define FIDO2_PERMISSION_MC 0x1
#define FIDO2_PERMISSION_GA 0x2
#define FIDO2_PERMISSION_CM 0x4
#define FIDO2_PERMISSION_BE 0x8
#define FIDO2_PERMISSION_LBW 0x10
#define FIDO2_PERMISSION_ACFG 0x20
#define FIDO2_OPT_EA 0x01 // Enterprise Attestation
#define MAX_PIN_RETRIES 8
extern bool getUserPresentFlagValue();
@@ -78,6 +73,8 @@ extern void clearUserVerifiedFlag();
extern void clearPinUvAuthTokenPermissionsExceptLbw();
extern void send_keepalive();
extern uint32_t get_sign_counter();
extern uint8_t get_opts();
extern void set_opts(uint8_t);
#define MAX_CREDENTIAL_COUNT_IN_LIST 16
#define MAX_CRED_ID_LENGTH 1024
#define MAX_RESIDENT_CREDENTIALS 256
@@ -101,6 +98,7 @@ typedef struct pinUvAuthToken {
bool in_use;
uint8_t permissions;
uint8_t rp_id_hash[32];
bool has_rp_id;
bool user_present;
bool user_verified;
} pinUvAuthToken_t;

View File

@@ -21,10 +21,13 @@
file_t file_entries[] = {
{.fid = 0x3f00, .parent = 0xff, .name = NULL, .type = FILE_TYPE_DF, .data = NULL, .ef_structure = 0, .acl = {0}}, // MF
{.fid = EF_KEY_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Device Key
{.fid = EF_KEY_DEV_ENC, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Device Key Enc
{.fid = EF_EE_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // End Entity Certificate Device
{.fid = EF_COUNTER, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Global counter
{.fid = EF_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // PIN
{.fid = EF_AUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // AUTH TOKEN
{.fid = EF_MINPINLEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // MIN PIN LENGTH
{.fid = EF_OPTS, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Global options
{ .fid = 0x0000, .parent = 0xff, .name = NULL, .type = FILE_TYPE_UNKNOWN, .data = NULL, .ef_structure = 0, .acl = {0} } //end
};
@@ -35,3 +38,4 @@ file_t *ef_certdev = NULL;
file_t *ef_counter = NULL;
file_t *ef_pin = NULL;
file_t *ef_authtoken = NULL;
file_t *ef_keydev_enc = NULL;

View File

@@ -21,10 +21,13 @@
#include "file.h"
#define EF_KEY_DEV 0xCC00
#define EF_KEY_DEV_ENC 0xCC01
#define EF_EE_DEV 0xCE00
#define EF_COUNTER 0xC000
#define EF_OPTS 0xC001
#define EF_PIN 0x1080
#define EF_AUTHTOKEN 0x1090
#define EF_MINPINLEN 0x1100
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF
#define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF
@@ -33,5 +36,6 @@ extern file_t *ef_certdev;
extern file_t *ef_counter;
extern file_t *ef_pin;
extern file_t *ef_authtoken;
extern file_t *ef_keydev_enc;
#endif //_FILES_H_

View File

@@ -18,7 +18,7 @@
#ifndef __VERSION_H_
#define __VERSION_H_
#define PICO_FIDO_VERSION 0x0202
#define PICO_FIDO_VERSION 0x0204
#define PICO_FIDO_VERSION_MAJOR ((PICO_FIDO_VERSION >> 8) & 0xff)
#define PICO_FIDO_VERSION_MINOR (PICO_FIDO_VERSION & 0xff)

394
tools/pico-fido-tool.py Normal file
View File

@@ -0,0 +1,394 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
/*
* 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 <http://www.gnu.org/licenses/>.
*/
"""
import sys
import argparse
import platform
from binascii import hexlify
from words import words
from threading import Event
from typing import Mapping, Any, Optional, Callable
import struct
from enum import IntEnum, unique
try:
from fido2.ctap2.config import Config
from fido2.ctap2 import Ctap2
from fido2.hid import CtapHidDevice, CTAPHID
from fido2.utils import bytes2int, int2bytes
from fido2 import cbor
from fido2.ctap import CtapDevice, CtapError
from fido2.ctap2.pin import PinProtocol, _PinUv
from fido2.ctap2.base import args
except:
print('ERROR: fido2 module not found! Install fido2 package.\nTry with `pip install fido2`')
sys.exit(-1)
try:
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
from cryptography.hazmat.primitives import hashes
except:
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
sys.exit(-1)
from enum import IntEnum
from binascii import hexlify
if (platform.system() == 'Windows' or platform.system() == 'Linux'):
from secure_key import windows as skey
elif (platform.system() == 'Darwin'):
from secure_key import macos as skey
else:
print('ERROR: platform not supported')
sys.exit(-1)
class VendorConfig(Config):
class PARAM(IntEnum):
VENDOR_COMMAND_ID = 0x01
VENDOR_AUT_CT = 0x02
class CMD(IntEnum):
CONFIG_AUT_ENABLE = 0x03e43f56b34285e2
CONFIG_AUT_DISABLE = 0x1831a40f04a25ed9
class RESP(IntEnum):
KEY_AGREEMENT = 0x01
def __init__(self, ctap, pin_uv_protocol=None, pin_uv_token=None):
super().__init__(ctap, pin_uv_protocol, pin_uv_token)
def enable_device_aut(self, ct):
self._call(
Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_ENABLE,
VendorConfig.PARAM.VENDOR_AUT_CT: ct
},
)
def disable_device_aut(self):
self._call(
Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_DISABLE
},
)
class Ctap2Vendor(Ctap2):
def __init__(self, device: CtapDevice, strict_cbor: bool = True):
super().__init__(device=device, strict_cbor=strict_cbor)
def send_vendor(
self,
cmd: int,
data: Optional[Mapping[int, Any]] = None,
*,
event: Optional[Event] = None,
on_keepalive: Optional[Callable[[int], None]] = None,
) -> Mapping[int, Any]:
"""Sends a VENDOR message to the device, and waits for a response.
:param cmd: The command byte of the request.
:param data: The payload to send (to be CBOR encoded).
:param event: Optional threading.Event used to cancel the request.
:param on_keepalive: Optional function called when keep-alive is sent by
the authenticator.
"""
request = struct.pack(">B", cmd)
if data is not None:
request += cbor.encode(data)
response = self.device.call(CTAPHID.VENDOR_FIRST + 1, request, event, on_keepalive)
status = response[0]
if status != 0x00:
raise CtapError(status)
enc = response[1:]
if not enc:
return {}
decoded = cbor.decode(enc)
if self._strict_cbor:
expected = cbor.encode(decoded)
if expected != enc:
raise ValueError(
"Non-canonical CBOR from Authenticator.\n"
f"Got: {enc.hex()}\nExpected: {expected.hex()}"
)
if isinstance(decoded, Mapping):
return decoded
raise TypeError("Decoded value of wrong type")
def vendor(
self,
cmd: int,
sub_cmd: int,
sub_cmd_params: Optional[Mapping[int, Any]] = None,
pin_uv_protocol: Optional[int] = None,
pin_uv_param: Optional[bytes] = None,
) -> Mapping[int, Any]:
"""CTAP2 authenticator vendor command.
This command is used to configure various authenticator features through the
use of its subcommands.
This method is not intended to be called directly. It is intended to be used by
an instance of the Config class.
:param sub_cmd: A Config sub command.
:param sub_cmd_params: Sub command specific parameters.
:param pin_uv_protocol: PIN/UV auth protocol version used.
:param pin_uv_param: PIN/UV Auth parameter.
"""
return self.send_vendor(
cmd,
args(sub_cmd, sub_cmd_params, pin_uv_protocol, pin_uv_param),
)
class Vendor:
"""Implementation of the CTAP2.1 Authenticator Vendor API. It is vendor implementation.
:param ctap: An instance of a CTAP2Vendor object.
:param pin_uv_protocol: An instance of a PinUvAuthProtocol.
:param pin_uv_token: A valid PIN/UV Auth Token for the current CTAP session.
"""
@unique
class CMD(IntEnum):
VENDOR_BACKUP = 0x01
VENDOR_MSE = 0x02
VENDOR_UNLOCK = 0x03
@unique
class PARAM(IntEnum):
PARAM = 0x01
COSE_KEY = 0x02
class SUBCMD(IntEnum):
ENABLE = 0x01
DISABLE = 0x02
KEY_AGREEMENT = 0x01
class RESP(IntEnum):
PARAM = 0x01
COSE_KEY = 0x02
def __init__(
self,
ctap: Ctap2Vendor,
pin_uv_protocol: Optional[PinProtocol] = None,
pin_uv_token: Optional[bytes] = None,
):
self.ctap = ctap
self.pin_uv = (
_PinUv(pin_uv_protocol, pin_uv_token)
if pin_uv_protocol and pin_uv_token
else None
)
self.__key_enc = None
self.__iv = None
self.vcfg = VendorConfig(ctap)
def _call(self, cmd, sub_cmd, params=None):
if params:
params = {k: v for k, v in params.items() if v is not None}
else:
params = None
if self.pin_uv:
msg = (
b"\xff" * 32
+ b"\x0d"
+ struct.pack("<b", sub_cmd)
+ (cbor.encode(params) if params else b"")
)
pin_uv_protocol = self.pin_uv.protocol.VERSION
pin_uv_param = self.pin_uv.protocol.authenticate(self.pin_uv.token, msg)
else:
pin_uv_protocol = None
pin_uv_param = None
return self.ctap.vendor(cmd, sub_cmd, params, pin_uv_protocol, pin_uv_param)
def backup_save(self, filename):
ret = self._call(
Vendor.CMD.VENDOR_BACKUP,
Vendor.SUBCMD.ENABLE,
)
data = ret[Vendor.RESP.PARAM]
d = int.from_bytes(skey.get_secure_key(), 'big')
with open(filename, 'wb') as fp:
fp.write(b'\x01')
fp.write(data)
pk = ec.derive_private_key(d, ec.SECP256R1())
signature = pk.sign(data, ec.ECDSA(hashes.SHA256()))
fp.write(signature)
print('Remember the following words in this order:')
for c in range(24):
coef = (d//(2048**c))%2048
print(f'{(c+1):02d} - {words[coef]}')
def backup_load(self, filename):
d = 0
if (d == 0):
for c in range(24):
word = input(f'Introduce word {(c+1):02d}: ')
while (word not in words):
word = input(f'Word not found. Please, tntroduce the correct word {(c+1):02d}: ')
coef = words.index(word)
d = d+(2048**c)*coef
pk = ec.derive_private_key(d, ec.SECP256R1())
pb = pk.public_key()
with open(filename, 'rb') as fp:
format = fp.read(1)[0]
if (format == 0x1):
data = fp.read(60)
signature = fp.read()
pb.verify(signature, data, ec.ECDSA(hashes.SHA256()))
skey.set_secure_key(pk)
return self._call(
Vendor.CMD.VENDOR_BACKUP,
Vendor.SUBCMD.DISABLE,
{
Vendor.PARAM.PARAM: data
},
)
def mse(self):
sk = ec.generate_private_key(ec.SECP256R1())
pn = sk.public_key().public_numbers()
self.__pb = sk.public_key().public_bytes(Encoding.X962, PublicFormat.UncompressedPoint)
key_agreement = {
1: 2,
3: -25, # Per the spec, "although this is NOT the algorithm actually used"
-1: 1,
-2: int2bytes(pn.x, 32),
-3: int2bytes(pn.y, 32),
}
ret = self._call(
Vendor.CMD.VENDOR_MSE,
Vendor.SUBCMD.KEY_AGREEMENT,
{
Vendor.PARAM.COSE_KEY: key_agreement,
},
)
peer_cose_key = ret[VendorConfig.RESP.KEY_AGREEMENT]
x = bytes2int(peer_cose_key[-2])
y = bytes2int(peer_cose_key[-3])
pk = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()).public_key()
shared_key = sk.exchange(ec.ECDH(), pk)
xkdf = HKDF(
algorithm=hashes.SHA256(),
length=12+32,
salt=None,
info=self.__pb
)
kdf_out = xkdf.derive(shared_key)
self.__key_enc = kdf_out[12:]
self.__iv = kdf_out[:12]
def encrypt_chacha(self, data):
chacha = ChaCha20Poly1305(self.__key_enc)
ct = chacha.encrypt(self.__iv, data, self.__pb)
return ct
def unlock_device(self):
ct = self.get_skey()
self._call(
Vendor.CMD.VENDOR_UNLOCK,
Vendor.SUBCMD.ENABLE,
{
Vendor.PARAM.PARAM: ct
},
)
def _get_key_device(self):
return skey.get_secure_key()
def get_skey(self):
self.mse()
ct = self.encrypt_chacha(self._get_key_device())
return ct
def enable_device_aut(self):
ct = self.get_skey()
self.vcfg.enable_device_aut(ct)
def disable_device_aut(self):
self.vcfg.disable_device_aut()
def parse_args():
parser = argparse.ArgumentParser()
subparser = parser.add_subparsers(title="commands", dest="command")
parser_secure = subparser.add_parser('secure', help='Manages security of Pico Fido.')
parser_secure.add_argument('subcommand', choices=['enable', 'disable', 'unlock'], help='Enables, disables or unlocks the security.')
parser_backup = subparser.add_parser('backup', help='Manages the backup of Pico Fido.')
parser_backup.add_argument('subcommand', choices=['save', 'load'], help='Saves or loads a backup.')
parser_backup.add_argument('filename', help='File to save or load the backup.')
args = parser.parse_args()
return args
def secure(vdr, args):
if (args.subcommand == 'enable'):
vdr.enable_device_aut()
elif (args.subcommand == 'unlock'):
vdr.unlock_device()
elif (args.subcommand == 'disable'):
vdr.disable_device_aut()
def backup(vdr, args):
if (args.subcommand == 'save'):
vdr.backup_save(args.filename)
elif (args.subcommand == 'load'):
vdr.backup_load(args.filename)
def main(args):
print('Pico Fido Tool v1.2')
print('Author: Pol Henarejos')
print('Report bugs to https://github.com/polhenarejos/pico-fido/issues')
print('')
print('')
dev = next(CtapHidDevice.list_devices(), None)
vdr = Vendor(Ctap2Vendor(dev))
if (args.command == 'secure'):
secure(vdr, args)
elif (args.command == 'backup'):
backup(vdr, args)
def run():
args = parse_args()
main(args)
if __name__ == "__main__":
run()

59
tools/secure_key/macos.py Normal file
View File

@@ -0,0 +1,59 @@
import sys
import keyring
DOMAIN = "PicoKeys.com"
USERNAME = "Pico-Fido"
try:
import keyring
from keyrings.osx_keychain_keys.backend import OSXKeychainKeysBackend, OSXKeychainKeyType, OSXKeyChainKeyClassType
except:
print('ERROR: keyring module not found! Install keyring package.\nTry with `pip install keyrings.osx-keychain-keys`')
sys.exit(-1)
try:
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
except:
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
sys.exit(-1)
def get_backend(use_secure_enclave=False):
backend = OSXKeychainKeysBackend(
key_type=OSXKeychainKeyType.EC, # Key type, e.g. RSA, RC, DSA, ...
key_class_type=OSXKeyChainKeyClassType.Private, # Private key, Public key, Symmetric-key
key_size_in_bits=256,
is_permanent=True, # If set, saves the key in keychain; else, returns a transient key
use_secure_enclave=use_secure_enclave, # Saves the key in the T2 (TPM) chip, requires a code-signed interpreter
access_group=None, # Limits key management and retrieval to set group, requires a code-signed interpreter
is_extractable=True # If set, private key is extractable; else, it can't be retrieved, but only operated against
)
return backend
def generate_secure_key(use_secure_enclave=False):
backend = get_backend(use_secure_enclave)
backend.set_password(DOMAIN, USERNAME, password=None)
return backend.get_password(DOMAIN, USERNAME)
def get_d(key):
return key.private_numbers().private_value.to_bytes(32, 'big')
def set_secure_key(pk):
backend = get_backend(False)
try:
backend.delete_password(DOMAIN, USERNAME)
except:
pass
backend.set_password(DOMAIN, USERNAME, pk.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()))
def get_secure_key():
key = None
try:
backend = get_backend(False)
key = backend.get_password(DOMAIN, USERNAME)[0]
except keyring.errors.KeyringError:
try:
key = generate_secure_key(False)[0] # It should be True, but secure enclave causes python segfault
except keyring.errors.PasswordSetError:
key = generate_secure_key(False)[0]
return get_d(key)

1
tools/words.py Normal file

File diff suppressed because one or more lines are too long