Merge branch 'development'

This commit is contained in:
Pol Henarejos
2025-10-27 09:30:13 +01:00
42 changed files with 1315 additions and 753 deletions

View File

@@ -35,11 +35,6 @@ project(pico_fido C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
if(ENABLE_EMULATION)
else()
pico_sdk_init()
endif()
add_executable(pico_fido)
endif()
@@ -156,6 +151,12 @@ if(ENABLE_EMULATION)
target_link_options(pico_fido PUBLIC
-Wl,-dead_strip
)
if(DEBUG_APDU)
target_compile_options(pico_fido PUBLIC
-fsanitize=address -g -O1 -fno-omit-frame-pointer)
target_link_options(pico_fido PUBLIC
-fsanitize=address -g -O1 -fno-omit-frame-pointer)
endif()
else()
target_link_options(pico_fido PUBLIC
-Wl,--gc-sections

View File

@@ -16,7 +16,7 @@
*/
#include "pico_keys.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif
#include "hid/ctap_hid.h"
@@ -104,7 +104,8 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
return CTAP1_ERR_INVALID_CMD;
}
void cbor_thread(void) {
void *cbor_thread(void *arg) {
(void)arg;
card_init_core1();
while (1) {
uint32_t m;
@@ -115,17 +116,17 @@ void cbor_thread(void) {
if (m == EV_EXIT) {
break;
}
apdu.sw = cbor_parse(cbor_cmd, cbor_data, cbor_len);
apdu.sw = (uint16_t)cbor_parse(cbor_cmd, cbor_data, cbor_len);
if (apdu.sw == 0) {
DEBUG_DATA(res_APDU, res_APDU_size);
}
else {
if (apdu.sw >= CTAP1_ERR_INVALID_CHANNEL) {
res_APDU[-1] = apdu.sw;
res_APDU[-1] = (uint8_t)apdu.sw;
apdu.sw = 0;
}
else {
res_APDU[0] = apdu.sw;
res_APDU[0] = (uint8_t)apdu.sw;
}
}
@@ -137,6 +138,7 @@ void cbor_thread(void) {
#ifdef ESP_PLATFORM
vTaskDelete(NULL);
#endif
return NULL;
}
int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) {
@@ -214,6 +216,9 @@ CborError COSE_key(mbedtls_ecp_keypair *key, CborEncoder *mapEncoderParent,
else if (key->grp.id == MBEDTLS_ECP_DP_ED25519) {
alg = FIDO2_ALG_EDDSA;
}
else if (key->grp.id == MBEDTLS_ECP_DP_ED448) {
alg = FIDO2_ALG_ED448;
}
#endif
return COSE_key_params(crv, alg, &key->grp, &key->Q, mapEncoderParent, mapEncoder);
}

View File

@@ -15,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#ifndef ESP_PLATFORM
#include "common.h"
#else
@@ -27,7 +28,7 @@
#include "cbor.h"
#include "ctap.h"
#include "ctap2_cbor.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include "hid/ctap_hid.h"
@@ -35,7 +36,6 @@
#include "files.h"
#include "random.h"
#include "crypto_utils.h"
#include "pico_keys.h"
#include "apdu.h"
#include "kek.h"
@@ -44,6 +44,7 @@ uint32_t max_usage_time_period = 600 * 1000;
bool needs_power_cycle = false;
static mbedtls_ecdh_context hkey;
static bool hkey_init = false;
extern int encrypt_keydev_f1(const uint8_t keydev[32]);
int beginUsingPinUvAuthToken(bool userIsPresent) {
paut.user_present = userIsPresent;
@@ -105,11 +106,7 @@ int regenerate() {
mbedtls_ecdh_init(&hkey);
hkey_init = true;
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);
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) {
return ret;
@@ -125,34 +122,15 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
return ret;
}
if (protocol == 1) {
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
buf,
sizeof(buf),
sharedSecret);
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), buf, sizeof(buf), sharedSecret);
}
else if (protocol == 2) {
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
ret = mbedtls_hkdf(md_info,
NULL,
0,
buf,
sizeof(buf),
(uint8_t *) "CTAP2 HMAC key",
14,
sharedSecret,
32);
ret = mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 HMAC key", 14, sharedSecret, 32);
if (ret != 0) {
return ret;
}
return mbedtls_hkdf(md_info,
NULL,
0,
buf,
sizeof(buf),
(uint8_t *) "CTAP2 AES key",
13,
sharedSecret + 32,
32);
return mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 AES key", 13, sharedSecret + 32, 32);
}
return -1;
}
@@ -160,26 +138,38 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret) {
mbedtls_mpi z;
mbedtls_mpi_init(&z);
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp,
&z,
Q,
&hkey.ctx.mbed_ecdh.d,
random_gen,
NULL);
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, &z, Q, &hkey.ctx.mbed_ecdh.d, random_gen, NULL);
ret = kdf(protocol, &z, sharedSecret);
mbedtls_mpi_free(&z);
return ret;
}
int resetPinUvAuthToken() {
void resetAuthToken(bool persistent) {
uint16_t fid = EF_AUTHTOKEN;
if (persistent) {
fid = EF_PAUTHTOKEN;
}
file_t *ef = search_by_fid(fid, NULL, SPECIFY_EF);
uint8_t t[32];
random_gen(NULL, t, sizeof(t));
file_put_data(ef_authtoken, t, sizeof(t));
file_put_data(ef, t, sizeof(t));
low_flash_available();
}
int resetPinUvAuthToken() {
resetAuthToken(false);
paut.permissions = 0;
paut.data = file_get_data(ef_authtoken);
paut.len = file_get_size(ef_authtoken);
return 0;
}
low_flash_available();
int resetPersistentPinUvAuthToken() {
resetAuthToken(true);
file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF);
ppaut.permissions = 0;
ppaut.data = file_get_data(ef_pauthtoken);
ppaut.len = file_get_size(ef_pauthtoken);
return 0;
}
@@ -210,11 +200,7 @@ int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in
return -1;
}
int authenticate(uint8_t protocol,
const uint8_t *key,
const uint8_t *data,
size_t len,
uint8_t *sign) {
int authenticate(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign) {
uint8_t hmac[32];
int ret =
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac);
@@ -242,10 +228,10 @@ int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t l
return ret;
}
if (protocol == 1) {
return memcmp(sign, hmac, 16);
return ct_memcmp(sign, hmac, 16);
}
else if (protocol == 2) {
return memcmp(sign, hmac, 32);
return ct_memcmp(sign, hmac, 32);
}
return -1;
}
@@ -280,17 +266,15 @@ int pinUvAuthTokenUsageTimerObserver() {
return 0;
}
int check_mkek_encrypted(const uint8_t *dhash) {
if (file_get_size(ef_mkek) == MKEK_IV_SIZE + MKEK_KEY_SIZE) {
hash_multi(dhash, 16, session_pin); // Only for storing MKEK
uint8_t mkek[MKEK_SIZE] = {0};
memcpy(mkek, file_get_data(ef_mkek), MKEK_IV_SIZE + MKEK_KEY_SIZE);
int ret = store_mkek(mkek);
mbedtls_platform_zeroize(mkek, sizeof(mkek));
mbedtls_platform_zeroize(session_pin, sizeof(session_pin));
if (ret != PICOKEY_OK) {
return CTAP2_ERR_PIN_AUTH_INVALID;
}
int check_keydev_encrypted(const uint8_t pin_token[32]) {
if (file_get_data(ef_keydev) && *file_get_data(ef_keydev) == 0x01) {
uint8_t tmp_keydev[61];
tmp_keydev[0] = 0x02; // Change format to encrypted
encrypt_with_aad(pin_token, file_get_data(ef_keydev) + 1, 32, tmp_keydev + 1);
DEBUG_DATA(tmp_keydev, sizeof(tmp_keydev));
file_put_data(ef_keydev, tmp_keydev, sizeof(tmp_keydev));
mbedtls_platform_zeroize(tmp_keydev, sizeof(tmp_keydev));
low_flash_available();
}
return PICOKEY_OK;
}
@@ -305,11 +289,11 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CborEncoder encoder, mapEncoder;
CborValue map;
CborError error = CborNoError;
CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 },
kay = { 0 };
CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 }, kay = { 0 };
CborCharString rpId = { 0 };
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
uint64_t val_c = 1;
uint8_t keydev[32] = {0};
if (hkey_init == false) {
initialize();
}
@@ -431,15 +415,17 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if (pin_len < minPin) {
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
}
uint8_t hsh[34], dhash[32];
uint8_t hsh[35], dhash[32];
hsh[0] = MAX_PIN_RETRIES;
hsh[1] = pin_len;
hsh[2] = 1; // New format indicator
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
double_hash_pin(dhash, 16, hsh + 2);
file_put_data(ef_pin, hsh, 2 + 32);
pin_derive_verifier(dhash, 16, hsh + 3);
file_put_data(ef_pin, hsh, sizeof(hsh));
low_flash_available();
ret = check_mkek_encrypted(dhash);
pin_derive_session(dhash, 16, session_pin);
ret = check_keydev_encrypted(session_pin);
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
@@ -486,10 +472,10 @@ 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 pin_data[34];
memcpy(pin_data, file_get_data(ef_pin), 34);
uint8_t pin_data[35];
memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
pin_data[0] -= 1;
file_put_data(ef_pin, pin_data, sizeof(pin_data));
file_put_data(ef_pin, pin_data, file_get_size(ef_pin));
low_flash_available();
uint8_t retries = pin_data[0];
uint8_t paddedNewPin[64];
@@ -498,9 +484,16 @@ 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 dhash[32];
double_hash_pin(paddedNewPin, 16, dhash);
if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) {
uint8_t dhash[32], off = 3;
if (file_get_size(ef_pin) == 34) {
off = 2;
double_hash_pin(paddedNewPin, 16, dhash);
}
else {
pin_derive_verifier(paddedNewPin, 16, dhash);
}
if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) {
regenerate();
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
if (retries == 0) {
@@ -514,10 +507,28 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
}
hash_multi(paddedNewPin, 16, session_pin);
if (off == 2) {
// Upgrade pin file to new format
pin_data[2] = 1; // New format indicator
pin_derive_verifier(paddedNewPin, 16, pin_data + 3);
hash_multi(paddedNewPin, 16, session_pin);
ret = load_keydev(keydev);
if (ret != PICOKEY_OK) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
encrypt_keydev_f1(keydev);
}
pin_derive_session(paddedNewPin, 16, session_pin);
pin_data[0] = MAX_PIN_RETRIES;
file_put_data(ef_pin, pin_data, sizeof(pin_data));
low_flash_available();
ret = check_keydev_encrypted(session_pin);
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
new_pin_mismatches = 0;
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin);
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
@@ -539,35 +550,33 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if (pin_len < minPin) {
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
}
uint8_t hsh[34];
hsh[0] = MAX_PIN_RETRIES;
hsh[1] = pin_len;
// New PIN is valid and verified
ret = load_keydev(keydev);
if (ret != PICOKEY_OK) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
encrypt_keydev_f1(keydev);
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
double_hash_pin(dhash, 16, hsh + 2);
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 &&
memcmp(hsh + 2, file_get_data(ef_pin) + 2, 32) == 0) {
pin_derive_session(dhash, 16, session_pin);
ret = check_keydev_encrypted(session_pin);
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
low_flash_available();
pin_data[0] = MAX_PIN_RETRIES;
pin_data[1] = pin_len;
pin_data[2] = 1; // New format indicator
pin_derive_verifier(dhash, 16, pin_data + 3);
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 && ct_memcmp(pin_data + 3, file_get_data(ef_pin) + 3, 32) == 0) {
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
}
file_put_data(ef_pin, pin_data, sizeof(pin_data));
uint8_t mkek[MKEK_SIZE] = {0};
ret = load_mkek(mkek);
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
file_put_data(ef_pin, hsh, 2 + 32);
ret = check_mkek_encrypted(dhash);
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
hash_multi(dhash, 16, session_pin);
ret = store_mkek(mkek);
mbedtls_platform_zeroize(mkek, sizeof(mkek));
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
mbedtls_platform_zeroize(hsh, sizeof(hsh));
mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
mbedtls_platform_zeroize(dhash, sizeof(dhash));
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
uint8_t *tmpf = (uint8_t *) calloc(1, file_get_size(ef_minpin));
@@ -578,6 +587,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
}
low_flash_available();
resetPinUvAuthToken();
resetPersistentPinUvAuthToken();
goto err; // No return
}
else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions
@@ -598,7 +608,9 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if ((permissions & CTAP_PERMISSION_BE)) { // Not supported yet
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
}
if ((permissions & CTAP_PERMISSION_PCMR) && permissions != CTAP_PERMISSION_PCMR) {
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
}
}
if (!file_has_data(ef_pin)) {
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
@@ -618,10 +630,10 @@ 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 pin_data[34];
memcpy(pin_data, file_get_data(ef_pin), 34);
uint8_t pin_data[35];
memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
pin_data[0] -= 1;
file_put_data(ef_pin, pin_data, sizeof(pin_data));
file_put_data(ef_pin, pin_data, file_get_size(ef_pin));
low_flash_available();
uint8_t retries = pin_data[0];
uint8_t paddedNewPin[64], poff = ((uint8_t)pinUvAuthProtocol - 1) * IV_SIZE;
@@ -630,11 +642,18 @@ 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 dhash[32];
double_hash_pin(paddedNewPin, 16, dhash);
if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) {
uint8_t dhash[32], off = 3;
if (file_get_size(ef_pin) == 34) {
off = 2;
double_hash_pin(paddedNewPin, 16, dhash);
}
else {
pin_derive_verifier(paddedNewPin, 16, dhash);
}
if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) {
regenerate();
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
mbedtls_platform_zeroize(dhash, sizeof(dhash));
if (retries == 0) {
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
}
@@ -646,39 +665,59 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
}
mbedtls_platform_zeroize(dhash, sizeof(dhash));
ret = check_mkek_encrypted(paddedNewPin);
if (off == 2) {
// Upgrade pin file to new format
pin_data[2] = 1; // New format indicator
pin_derive_verifier(paddedNewPin, 16, pin_data + 3);
hash_multi(paddedNewPin, 16, session_pin);
ret = load_keydev(keydev);
if (ret != PICOKEY_OK) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
encrypt_keydev_f1(keydev);
}
pin_derive_session(paddedNewPin, 16, session_pin);
ret = check_keydev_encrypted(session_pin);
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
hash_multi(paddedNewPin, 16, session_pin);
pin_data[0] = MAX_PIN_RETRIES;
new_pin_mismatches = 0;
file_put_data(ef_pin, pin_data, sizeof(pin_data));
mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
mbedtls_platform_zeroize(dhash, sizeof(dhash));
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 = (uint8_t)permissions;
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], *pdata = NULL;
if (permissions & CTAP_PERMISSION_PCMR) {
ppaut.permissions = CTAP_PERMISSION_PCMR;
pdata = ppaut.data;
}
else {
paut.has_rp_id = false;
resetPinUvAuthToken();
beginUsingPinUvAuthToken(false);
if (subcommand == 0x05) {
permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA;
}
paut.permissions = (uint8_t)permissions;
if (rpId.present == true) {
mbedtls_sha256((uint8_t *) rpId.data, rpId.len, paut.rp_id_hash, 0);
paut.has_rp_id = true;
}
else {
paut.has_rp_id = false;
}
pdata = paut.data;
}
uint8_t pinUvAuthToken_enc[32 + IV_SIZE];
encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc);
encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pdata, 32, pinUvAuthToken_enc);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32 + poff));
@@ -695,6 +734,7 @@ err:
CBOR_FREE_BYTE_STRING(kax);
CBOR_FREE_BYTE_STRING(kay);
CBOR_FREE_BYTE_STRING(rpId);
mbedtls_platform_zeroize(keydev, sizeof(keydev));
if (error != CborNoError) {
if (error == CborErrorImproperValue) {
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;

View File

@@ -15,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "ctap2_cbor.h"
#include "fido.h"
#include "ctap.h"
@@ -22,7 +23,6 @@
#include "files.h"
#include "apdu.h"
#include "credential.h"
#include "pico_keys.h"
#include "random.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/chachapoly.h"
@@ -31,14 +31,16 @@
extern uint8_t keydev_dec[32];
extern bool has_keydev_dec;
extern void resetPersistentPinUvAuthToken();
extern void resetPinUvAuthToken();
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, vendorParam = 0;
CborByteString pinUvAuthParam = { 0 }, vendorAutCt = { 0 };
CborCharString minPinLengthRPIDs[32] = { 0 };
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0;
CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 };
CborCharString minPinLengthRPIDs[32] = { 0 }, vendorParamTextString = { 0 };
size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0;
CborEncoder encoder;
//CborEncoder mapEncoder;
@@ -66,13 +68,19 @@ int cbor_config(const uint8_t *data, size_t len) {
raw_subpara = (uint8_t *) cbor_value_get_next_byte(&_f1);
CBOR_PARSE_MAP_START(_f1, 2)
{
if (subcommand == 0x7f) { // Config Aut
if (subcommand == 0xFF) { // Vendor
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);
CBOR_FIELD_GET_BYTES(vendorParamByteString, 2);
}
else if (subpara == 0x03) {
CBOR_FIELD_GET_UINT(vendorParamInt, 2);
}
else if (subpara == 0x04) {
CBOR_FIELD_GET_TEXT(vendorParamTextString, 2);
}
}
else if (subcommand == 0x03) { // Extensions
@@ -95,15 +103,6 @@ int cbor_config(const uint8_t *data, size_t len) {
CBOR_FIELD_GET_BOOL(forceChangePin, 2);
}
}
else if (subcommand == 0x1B) { // PHY
CBOR_FIELD_GET_UINT(subpara, 2);
if (subpara == 0x01) {
CBOR_FIELD_GET_UINT(vendorCommandId, 2);
}
else if (subpara == 0x02) {
CBOR_FIELD_GET_UINT(vendorParam, 2);
}
}
}
CBOR_PARSE_MAP_END(_f1, 2);
raw_subpara_len = cbor_value_get_next_byte(&_f1) - raw_subpara;
@@ -122,9 +121,12 @@ int cbor_config(const uint8_t *data, size_t len) {
if (pinUvAuthParam.present == false) {
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
}
if (pinUvAuthProtocol == 0) {
if (pinUvAuthProtocol == 0) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t *verify_payload = (uint8_t *) calloc(1, 32 + 1 + 1 + raw_subpara_len);
memset(verify_payload, 0xff, 32);
@@ -141,8 +143,14 @@ int cbor_config(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (subcommand == 0x7f) {
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) {
if (subcommand == 0xFF) {
#ifndef ENABLE_EMULATION
const bool is_phy = (vendorCommandId == CTAP_CONFIG_PHY_VIDPID ||
vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO ||
vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS ||
vendorCommandId == CTAP_CONFIG_PHY_OPTS);
#endif
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE){
if (!file_has_data(ef_keydev_enc)) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
@@ -163,7 +171,7 @@ int cbor_config(const uint8_t *data, size_t len) {
}
mbedtls_chachapoly_context chatx;
int ret = mse_decrypt_ct(vendorAutCt.data, vendorAutCt.len);
int ret = mse_decrypt_ct(vendorParamByteString.data, vendorParamByteString.len);
if (ret != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
@@ -171,7 +179,7 @@ int cbor_config(const uint8_t *data, size_t len) {
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);
mbedtls_chachapoly_setkey(&chatx, vendorParamByteString.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) {
@@ -184,6 +192,56 @@ int cbor_config(const uint8_t *data, size_t len) {
file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
low_flash_available();
}
#ifndef ENABLE_EMULATION
else if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) {
phy_data.vid = (vendorParamInt >> 16) & 0xFFFF;
phy_data.pid = vendorParamInt & 0xFFFF;
phy_data.vidpid_present = true;
}
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) {
phy_data.led_gpio = (uint8_t)vendorParamInt;
phy_data.led_gpio_present = true;
}
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) {
phy_data.led_brightness = (uint8_t)vendorParamInt;
phy_data.led_brightness_present = true;
}
else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) {
phy_data.opts = (uint16_t)vendorParamInt;
}
else {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
}
if (is_phy && phy_save() != PICOKEY_OK) {
CBOR_ERROR(CTAP2_ERR_PROCESSING);
}
#endif
else if (vendorCommandId == CTAP_CONFIG_EA_UPLOAD) {
if (vendorParamByteString.present == false) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
if (ef_ee_ea) {
file_put_data(ef_ee_ea, vendorParamByteString.data, (uint16_t)vendorParamByteString.len);
}
low_flash_available();
}
else if (vendorCommandId == CTAP_CONFIG_PIN_POLICY) {
file_t *ef_pin_policy = file_new(EF_PIN_COMPLEXITY_POLICY);
if (ef_pin_policy) {
uint8_t *val = calloc(1, 2 + vendorParamByteString.len);
if (val) {
// Not ready yet for integer param
// val[0] = (uint8_t)(vendorParamInt >> 8);
// val[1] = (uint8_t)(vendorParamInt & 0xFF);
memcpy(val + 2, vendorParamByteString.data, vendorParamByteString.len);
file_put_data(ef_pin_policy, val, 2 + (uint16_t)vendorParamByteString.len);
free(val);
}
}
low_flash_available();
}
else {
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
}
@@ -207,6 +265,10 @@ int cbor_config(const uint8_t *data, size_t len) {
if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength) {
forceChangePin = ptrue;
}
if (forceChangePin) {
resetPersistentPinUvAuthToken();
resetPinUvAuthToken();
}
uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32);
dataf[0] = (uint8_t)newMinPinLength;
dataf[1] = forceChangePin == ptrue ? 1 : 0;
@@ -222,35 +284,6 @@ int cbor_config(const uint8_t *data, size_t len) {
set_opts(get_opts() | FIDO2_OPT_EA);
goto err;
}
#ifndef ENABLE_EMULATION
else if (subcommand == 0x1B) {
if (vendorParam == 0) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) {
phy_data.vid = (vendorParam >> 16) & 0xFFFF;
phy_data.pid = vendorParam & 0xFFFF;
phy_data.vidpid_present = true;
}
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) {
phy_data.led_gpio = (uint8_t)vendorParam;
phy_data.led_gpio_present = true;
}
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) {
phy_data.led_brightness = (uint8_t)vendorParam;
phy_data.led_brightness_present = true;
}
else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) {
phy_data.opts = (uint16_t)vendorParam;
}
else {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
}
if (phy_save() != PICOKEY_OK) {
CBOR_ERROR(CTAP2_ERR_PROCESSING);
}
}
#endif
else {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
}
@@ -259,7 +292,8 @@ int cbor_config(const uint8_t *data, size_t len) {
err:
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
CBOR_FREE_BYTE_STRING(vendorAutCt);
CBOR_FREE_BYTE_STRING(vendorParamByteString);
CBOR_FREE_BYTE_STRING(vendorParamTextString);
for (size_t i = 0; i < minPinLengthRPIDs_len; i++) {
CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]);
}

View File

@@ -15,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "ctap.h"
#include "hid/ctap_hid.h"
@@ -22,7 +23,6 @@
#include "files.h"
#include "apdu.h"
#include "credential.h"
#include "pico_keys.h"
uint8_t rp_counter = 1;
uint8_t rp_total = 0;
@@ -79,8 +79,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
if (strcmp(_fd3, "transports") == 0) {
CBOR_PARSE_ARRAY_START(_f3, 4)
{
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.
transports_len], 4);
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.transports_len], 4);
credentialId.transports_len++;
}
CBOR_PARSE_ARRAY_END(_f3, 4);
@@ -122,12 +121,19 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
if (subcommand == 0x01) {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) == CborNoError) {
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
}
if (is_preview == false &&
(!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
else {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (is_preview == false &&
(!(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++) {
@@ -144,11 +150,18 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
else if (subcommand == 0x02 || subcommand == 0x03) {
file_t *rp_ef = NULL;
if (subcommand == 0x02) {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) == CborNoError) {
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
}
if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
else {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
}
rp_counter = 1;
rp_total = 0;
@@ -199,13 +212,20 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
if (subcommand == 0x04) {
*(raw_subpara - 1) = 0x04;
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) == CborNoError) {
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
}
if (is_preview == false &&
(!(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);
else {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
if (is_preview == false &&
(!(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;
@@ -239,7 +259,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
Credential cred = { 0 };
if (credential_load(file_get_data(cred_ef) + 32, file_get_size(cred_ef) - 32, rpIdHash.data, &cred) != 0) {
if (credential_load_resident(cred_ef, rpIdHash.data, &cred) != 0) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
@@ -296,7 +316,9 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred.id.data, cred.id.len));
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
credential_derive_resident(cred.id.data, cred.id.len, cred_idr);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred_idr, sizeof(cred_idr)));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
@@ -352,7 +374,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file((uint16_t)(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) {
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, CRED_RESIDENT_LEN) == 0) {
uint8_t *rp_id_hash = file_get_data(ef);
if (delete_file(ef) != 0) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
@@ -394,10 +416,10 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file((uint16_t)(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) {
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, CRED_RESIDENT_LEN) == 0) {
Credential cred = { 0 };
uint8_t *rp_id_hash = file_get_data(ef);
if (credential_load(rp_id_hash + 32, file_get_size(ef) - 32, rp_id_hash, &cred) != 0) {
if (credential_load_resident(ef, rp_id_hash, &cred) != 0) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
if (memcmp(user.id.data, cred.userId.data, MIN(user.id.len, cred.userId.len)) != 0) {
@@ -405,11 +427,11 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t newcred[MAX_CRED_ID_LENGTH];
size_t newcred_len = 0;
uint16_t newcred_len = 0;
if (credential_create(&cred.rpId, &cred.userId, &user.parent.name,
&user.displayName, &cred.opts, &cred.extensions,
cred.use_sign_count, (int)cred.alg,
(int)cred.curve, newcred, &newcred_len) != 0) {
&user.displayName, &cred.opts, &cred.extensions,
cred.use_sign_count, (int)cred.alg,
(int)cred.curve, newcred, &newcred_len) != 0) {
credential_free(&cred);
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}

View File

@@ -15,16 +15,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "cbor.h"
#include "ctap.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include "hid/ctap_hid.h"
#include "fido.h"
#include "files.h"
#include "crypto_utils.h"
#include "pico_keys.h"
#include "apdu.h"
#include "cbor_make_credential.h"
#include "credential.h"
@@ -295,27 +295,48 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
if (strcmp(allowList[e].type.data, "public-key") != 0) {
continue;
}
if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) {
credential_free(&creds[creds_len]);
}
else {
creds_len++;
silent = false; // If we are able to load a credential, we are not silent
// Even we provide allowList, we need to check if the credential is resident
if (!resident) {
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue;
}
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) {
if (credential_load_resident(ef, rp_id_hash, &creds[creds_len]) != 0) {
// Should never happen
credential_free(&creds[creds_len]);
continue;
}
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, allowList[e].id.len) == 0) {
resident = true;
resident = true;
creds_len++;
silent = false; // If we are able to load a credential, we are not silent
break;
}
}
}
else {
if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) {
credential_free(&creds[creds_len]);
}
else {
creds_len++;
silent = false; // If we are able to load a credential, we are not silent
// Even we provide allowList, we need to check if the credential is resident
if (!resident) {
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue;
}
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, allowList[e].id.len) == 0) {
resident = true;
break;
}
}
if (resident) {
break;
}
}
if (resident) {
break;
}
}
}
}
@@ -326,7 +347,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue;
}
int ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &creds[creds_len]);
int ret = credential_load_resident(ef, rp_id_hash, &creds[creds_len]);
if (ret != 0) {
credential_free(&creds[creds_len]);
}
@@ -343,8 +364,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
if (creds[i].extensions.credProtect == CRED_PROT_UV_REQUIRED && !(flags & FIDO2_AUT_FLAG_UV)) {
credential_free(&creds[i]);
}
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST &&
resident == true && !(flags & FIDO2_AUT_FLAG_UV)) {
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && allowList_len == 0 && !(flags & FIDO2_AUT_FLAG_UV)) {
credential_free(&creds[i]);
}
else {
@@ -375,8 +395,24 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
if (strcmp(allowList[e].type.data, "public-key") != 0) {
continue;
}
if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) {
numberOfCredentials++;
if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue;
}
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) {
if (credential_verify(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, true) == 0) {
numberOfCredentials++;
}
break;
}
}
}
else {
if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) {
numberOfCredentials++;
}
}
}
}
@@ -427,10 +463,16 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
}
else {
selcred = &creds[0];
if (resident && allowList_len > 1) {
numberOfCredentials = 1;
}
if (numberOfCredentials > 1) {
asserted = true;
residentx = resident;
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
credential_free(&credsx[i]);
}
for (int i = 0; i < numberOfCredentials; i++) {
credsx[i] = creds[i];
}
numberOfCredentialsx = numberOfCredentials;
@@ -633,7 +675,14 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
if (selcred) {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len));
if (resident) {
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
credential_derive_resident(selcred->id.data, selcred->id.len, cred_idr);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred_idr, sizeof(cred_idr)));
}
else {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len));
}
}
else {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, (uint8_t *)"\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01", 16));
@@ -660,8 +709,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
}
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, lu));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data,
selcred->userId.len));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data, selcred->userId.len));
if (numberOfCredentials > 1 && allowList_len == 0) {
if (selcred->userName.present == true) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name"));

View File

@@ -15,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "ctap2_cbor.h"
#include "hid/ctap_hid.h"
#include "fido.h"
@@ -27,22 +28,36 @@ int cbor_get_info() {
CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2;
CborError error = CborNoError;
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 15));
uint8_t lfields = 14;
#ifndef ENABLE_EMULATION
if (phy_data.vid != 0x1050) {
lfields++;
}
#else
lfields++;
#endif
file_t *ef_pin_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF);
if (file_has_data(ef_pin_policy)) {
lfields += 2;
}
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 4));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "U2F_V2"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_0"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_1"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_2"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 6));
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 7));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credBlob"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobKey"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "minPinLength"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret-mc"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "thirdPartyPayment"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
@@ -150,13 +165,36 @@ int cbor_get_info() {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0F));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CREDBLOB_LENGTH)); // maxCredBlobLength
#ifndef ENABLE_EMULATION
if (phy_data.vid != 0x1050) {
#endif
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
uint8_t enabled_cmds = 4;
#ifndef ENABLE_EMULATION
enabled_cmds += 4;
#endif
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, enabled_cmds));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_EA_UPLOAD));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PIN_POLICY));
#ifndef ENABLE_EMULATION
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_VIDPID));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_BTNESS));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_GPIO));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_OPTS));
#endif
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
#ifndef ENABLE_EMULATION
}
if (file_has_data(ef_pin_policy)) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1B));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1C));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_pin_policy) + 2, file_get_size(ef_pin_policy) - 2));
}
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));
#endif
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
err:
if (error != CborNoError) {

View File

@@ -15,13 +15,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "ctap2_cbor.h"
#include "fido.h"
#include "ctap.h"
#include "hid/ctap_hid.h"
#include "files.h"
#include "apdu.h"
#include "pico_keys.h"
#include "mbedtls/sha256.h"
static uint64_t expectedLength = 0, expectedNextOffset = 0;
@@ -129,7 +129,7 @@ int cbor_large_blobs(const uint8_t *data, size_t len) {
uint8_t verify_data[70] = { 0 };
memset(verify_data, 0xff, 32);
verify_data[32] = 0x0C;
put_uint32_t_le(offset, verify_data + 34);
put_uint32_t_le((uint32_t)offset, verify_data + 34);
mbedtls_sha256(set.data, set.len, verify_data + 38, 0);
if (verify((uint8_t)pinUvAuthProtocol, paut.data, verify_data, (uint16_t)sizeof(verify_data), pinUvAuthParam.data) != 0) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);

View File

@@ -15,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "cbor_make_credential.h"
#include "ctap2_cbor.h"
#include "hid/ctap_hid.h"
@@ -25,7 +26,7 @@
#include "credential.h"
#include "mbedtls/sha256.h"
#include "random.h"
#include "pico_keys.h"
#include "crypto_utils.h"
int cbor_make_credential(const uint8_t *data, size_t len) {
CborParser parser;
@@ -39,7 +40,11 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
PublicKeyCredentialDescriptor excludeList[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
size_t excludeList_len = 0;
CredOptions options = { 0 };
uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0;
uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0, hmacSecretPinUvAuthProtocol = 1;
int64_t kty = 2, hmac_alg = 0, crv = 0;
CborByteString kax = { 0 }, kay = { 0 }, salt_enc = { 0 }, salt_auth = { 0 };
bool hmac_secret_mc = false;
const bool *pin_complexity_policy = NULL;
uint8_t *aut_data = NULL;
size_t resp_size = 0;
CredExtensions extensions = { 0 };
@@ -127,12 +132,39 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_PARSE_MAP_START(_f1, 2)
{
CBOR_FIELD_GET_KEY_TEXT(2);
if (strcmp(_fd2, "hmac-secret-mc") == 0) {
hmac_secret_mc = true;
uint64_t ukey = 0;
CBOR_PARSE_MAP_START(_f2, 3)
{
CBOR_FIELD_GET_UINT(ukey, 3);
if (ukey == 0x01) {
CBOR_CHECK(COSE_read_key(&_f3, &kty, &hmac_alg, &crv, &kax, &kay));
}
else if (ukey == 0x02) {
CBOR_FIELD_GET_BYTES(salt_enc, 3);
}
else if (ukey == 0x03) {
CBOR_FIELD_GET_BYTES(salt_auth, 3);
}
else if (ukey == 0x04) {
CBOR_FIELD_GET_UINT(hmacSecretPinUvAuthProtocol, 3);
}
else {
CBOR_ADVANCE(3);
}
}
CBOR_PARSE_MAP_END(_f2, 3);
continue;
}
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_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "pinComplexityPolicy", pin_complexity_policy);
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
@@ -202,21 +234,36 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0) {
CBOR_ERROR(CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
}
if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256) {
if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP256) {
if (curve <= 0) {
curve = FIDO2_CURVE_P256;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384) {
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP384) {
if (curve <= 0) {
curve = FIDO2_CURVE_P384;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512) {
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP512) {
if (curve <= 0) {
curve = FIDO2_CURVE_P521;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB256) {
if (curve <= 0) {
curve = FIDO2_CURVE_BP256R1;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB384) {
if (curve <= 0) {
curve = FIDO2_CURVE_BP384R1;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB512) {
if (curve <= 0) {
curve = FIDO2_CURVE_BP512R1;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256K
#ifndef ENABLE_EMULATION
&& (phy_data.enabled_curves & PHY_CURVE_SECP256K1)
@@ -227,11 +274,16 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
}
}
#ifdef MBEDTLS_EDDSA_C
else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA) {
else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA || pubKeyCredParams[i].alg == FIDO2_ALG_ED25519) {
if (curve <= 0) {
curve = FIDO2_CURVE_ED25519;
}
}
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ED448) {
if (curve <= 0) {
curve = FIDO2_CURVE_ED448;
}
}
#endif
else if (pubKeyCredParams[i].alg <= FIDO2_ALG_RS256 && pubKeyCredParams[i].alg >= FIDO2_ALG_RS512) {
// pass
@@ -298,12 +350,26 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
continue;
}
Credential ecred = {0};
if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash,
&ecred) == 0 &&
(ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED ||
(flags & FIDO2_AUT_FLAG_UV))) {
if (credential_is_resident(excludeList[e].id.data, excludeList[e].id.len)) {
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef_cred = search_dynamic_file((uint16_t)(EF_CRED + i));
if (!file_has_data(ef_cred) || memcmp(file_get_data(ef_cred), rp_id_hash, 32) != 0) {
continue;
}
uint8_t *cred_idr = file_get_data(ef_cred) + 32;
if (memcmp(cred_idr, excludeList[e].id.data, CRED_RESIDENT_LEN) == 0) {
if (credential_load_resident(ef_cred, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) {
credential_free(&ecred);
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
}
}
}
}
else {
if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) {
credential_free(&ecred);
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
}
}
credential_free(&ecred);
}
@@ -313,6 +379,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
}
if (hmac_secret_mc && extensions.hmac_secret != ptrue) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
if (options.up == ptrue || options.up == NULL) { //14.1
if (pinUvAuthParam.present == true) {
if (getUserPresentFlagValue() == false) {
@@ -332,11 +402,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
const known_app_t *ka = find_app_by_rp_id_hash(rp_id_hash);
uint8_t cred_id[MAX_CRED_ID_LENGTH] = {0};
size_t cred_id_len = 0;
uint16_t cred_id_len = 0;
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));
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));
if (getUserVerifiedFlagValue()) {
flags |= FIDO2_AUT_FLAG_UV;
@@ -372,6 +440,12 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
if (extensions.credBlob.present == true) {
l++;
}
if (hmac_secret_mc) {
l++;
}
if (pin_complexity_policy == ptrue) {
l++;
}
if (l > 0) {
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
if (extensions.credBlob.present == true) {
@@ -383,15 +457,69 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
}
if (extensions.hmac_secret == ptrue) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
}
if (minPinLen > 0) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen));
}
if (hmac_secret_mc) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret-mc"));
uint8_t sharedSecret[64] = {0};
mbedtls_ecp_point Qp;
mbedtls_ecp_point_init(&Qp);
mbedtls_mpi_lset(&Qp.Z, 1);
if (mbedtls_mpi_read_binary(&Qp.X, kax.data, kax.len) != 0) {
mbedtls_ecp_point_free(&Qp);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (mbedtls_mpi_read_binary(&Qp.Y, kay.data, kay.len) != 0) {
mbedtls_ecp_point_free(&Qp);
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
int ret = ecdh((uint8_t)hmacSecretPinUvAuthProtocol, &Qp, sharedSecret);
mbedtls_ecp_point_free(&Qp);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (verify((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_auth.data) != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_EXTENSION_FIRST);
}
uint8_t salt_dec[64] = {0}, poff = ((uint8_t)hmacSecretPinUvAuthProtocol - 1) * IV_SIZE;
ret = decrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_dec);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t cred_random[64] = {0}, *crd = NULL;
ret = credential_derive_hmac_key(cred_id, cred_id_len, cred_random);
if (ret != 0) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
if (flags & FIDO2_AUT_FLAG_UV) {
crd = cred_random + 32;
}
else {
crd = cred_random;
}
uint8_t out1[64] = {0}, hmac_res[80] = {0};
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec, 32, out1);
if ((uint8_t)salt_enc.len == 64 + poff) {
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec + 32, 32, out1 + 32);
}
encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res);
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len));
}
if (pin_complexity_policy == ptrue) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "pinComplexityPolicy"));
file_t *ef_pin_complexity_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF);
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, file_has_data(ef_pin_complexity_policy)));
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
@@ -417,15 +545,23 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_CHECK(COSE_key(&ekey, &encoder, &mapEncoder));
size_t rs = cbor_encoder_get_buffer_size(&encoder, cbor_buf);
size_t aut_data_len = 32 + 1 + 4 + (16 + 2 + cred_id_len + rs) + ext_len;
size_t aut_data_len = 32 + 1 + 4 + (16 + 2 + (options.rk == ptrue ? CRED_RESIDENT_LEN : cred_id_len) + rs) + ext_len;
aut_data = (uint8_t *) calloc(1, aut_data_len + clientDataHash.len);
uint8_t *pa = aut_data;
memcpy(pa, rp_id_hash, 32); pa += 32;
*pa++ = flags;
pa += put_uint32_t_be(ctr, pa);
memcpy(pa, aaguid, 16); pa += 16;
pa += put_uint16_t_be(cred_id_len, pa);
memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len;
if (options.rk == ptrue) {
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
pa += put_uint16_t_be(sizeof(cred_idr), pa);
credential_derive_resident(cred_id, cred_id_len, cred_idr);
memcpy(pa, cred_idr, sizeof(cred_idr)); pa += sizeof(cred_idr);
}
else {
pa += put_uint16_t_be(cred_id_len, pa);
memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len;
}
memcpy(pa, cbor_buf, rs); pa += (uint16_t)rs;
memcpy(pa, ext, ext_len); pa += (uint16_t)ext_len;
if ((size_t)(pa - aut_data) != aut_data_len) {
@@ -436,14 +572,14 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
memcpy(pa, clientDataHash.data, clientDataHash.len);
uint8_t hash[64] = {0}, sig[MBEDTLS_ECDSA_MAX_LEN] = {0};
const mbedtls_md_info_t *md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1) {
if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1 || ekey.grp.id == MBEDTLS_ECP_DP_BP384R1) {
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384);
}
else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) {
else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1 || ekey.grp.id == MBEDTLS_ECP_DP_BP512R1) {
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
}
#ifdef MBEDTLS_EDDSA_C
else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519) {
else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519 || ekey.grp.id == MBEDTLS_ECP_DP_ED448) {
md = NULL;
}
#endif
@@ -483,7 +619,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
#ifndef ENABLE_EMULATION
uint8_t *p = (uint8_t *)user.parent.name.data + 5;
if (memcmp(p, "CommissionProfile", 17) == 0) {
ret = phy_unserialize_data(user.id.data, user.id.len, &phy_data);
ret = phy_unserialize_data(user.id.data, (uint16_t)user.id.len, &phy_data);
if (ret == PICOKEY_OK) {
ret = phy_save();
}
@@ -519,12 +655,12 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aut_data, aut_data_len));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false || is_nitrokey ? 3 : 2));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false || is_nk ? 3 : 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg"));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation || is_nitrokey ? -alg : -FIDO2_ALG_ES256));
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation || is_nk ? -alg : -FIDO2_ALG_ES256));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "sig"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, sig, olen));
if (self_attestation == false || is_nitrokey) {
if (self_attestation == false || is_nk) {
CborEncoder arrEncoder;
file_t *ef_cert = NULL;
if (enterpriseAttestation == 2) {
@@ -569,6 +705,10 @@ err:
CBOR_FREE_BYTE_STRING(user.id);
CBOR_FREE_BYTE_STRING(user.displayName);
CBOR_FREE_BYTE_STRING(user.parent.name);
CBOR_FREE_BYTE_STRING(kax);
CBOR_FREE_BYTE_STRING(kay);
CBOR_FREE_BYTE_STRING(salt_enc);
CBOR_FREE_BYTE_STRING(salt_auth);
if (extensions.present == true) {
CBOR_FREE_BYTE_STRING(extensions.credBlob);
}

View File

@@ -15,10 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "file.h"
#include "fido.h"
#include "ctap.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#ifdef ESP_PLATFORM

View File

@@ -15,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "ctap.h"

View File

@@ -15,13 +15,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "ctap2_cbor.h"
#include "fido.h"
#include "ctap.h"
#include "hid/ctap_hid.h"
#include "files.h"
#include "apdu.h"
#include "pico_keys.h"
#include "random.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/chachapoly.h"
@@ -237,25 +237,14 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, buffer + sizeof(buffer) - ret, ret));
}
else if (vendorCmd == 0x02) {
if (vendorParam.present == false) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
if (ef_ee_ea) {
file_put_data(ef_ee_ea, vendorParam.data, (uint16_t)vendorParam.len);
}
low_flash_available();
goto err;
}
}
#ifndef ENABLE_EMULATION
else if (cmd == CTAP_VENDOR_PHY_OPTS) {
if (vendorCmd == 0x01) {
uint16_t opts = 0;
if (file_has_data(ef_phy)) {
uint8_t *data = file_get_data(ef_phy);
opts = get_uint16_t_be(data + PHY_OPTS);
uint8_t *pdata = file_get_data(ef_phy);
opts = get_uint16_t_be(pdata + PHY_OPTS);
}
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));

View File

@@ -15,8 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "fido.h"
#include "pico_keys.h"
#include "fido.h"
#include "apdu.h"
#include "ctap.h"
#include "random.h"

View File

@@ -15,8 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "fido.h"
#include "pico_keys.h"
#include "fido.h"
#include "apdu.h"
#include "ctap.h"
#include "random.h"
@@ -69,11 +69,7 @@ int cmd_register() {
}
if (memcmp(req->appId, bogus_firefox,
CTAP_APPID_SIZE) == 0 || memcmp(req->appId, bogus_chrome, CTAP_APPID_SIZE) == 0)
#ifndef ENABLE_EMULATION
{ return ctap_error(CTAP1_ERR_CHANNEL_BUSY); }
#else
{ return SW_DATA_INVALID(); }
#endif
mbedtls_ecdsa_context key;
mbedtls_ecdsa_init(&key);
int ret = derive_key(req->appId, true, resp->keyHandleCertSig, MBEDTLS_ECP_DP_SECP256R1, &key);

View File

@@ -15,10 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "mbedtls/chachapoly.h"
#include "mbedtls/sha256.h"
#include "credential.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include "hid/ctap_hid.h"
@@ -26,7 +27,6 @@
#include "ctap.h"
#include "random.h"
#include "files.h"
#include "pico_keys.h"
#include "otp.h"
int credential_derive_chacha_key(uint8_t *outk, const uint8_t *);
@@ -93,7 +93,7 @@ int credential_create(CborCharString *rpId,
int alg,
int curve,
uint8_t *cred_id,
size_t *cred_id_len) {
uint16_t *cred_id_len) {
CborEncoder encoder, mapEncoder, mapEncoder2;
CborError error = CborNoError;
uint8_t rp_id_hash[32];
@@ -150,7 +150,7 @@ int credential_create(CborCharString *rpId,
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
size_t rs = cbor_encoder_get_buffer_size(&encoder, cred_id);
*cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN;
*cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + (uint16_t)rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN;
uint8_t key[32] = {0};
credential_derive_chacha_key(key, (const uint8_t *)CRED_PROTO);
uint8_t iv[CRED_IV_LEN] = {0};
@@ -314,7 +314,7 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
if (memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue;
}
ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &rcred);
ret = credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &rcred);
if (ret != 0) {
credential_free(&rcred);
continue;
@@ -330,11 +330,14 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
if (sloti == -1) {
return -1;
}
uint8_t *data = (uint8_t *) calloc(1, cred_id_len + 32);
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
credential_derive_resident(cred_id, cred_id_len, cred_idr);
uint8_t *data = (uint8_t *) calloc(1, cred_id_len + 32 + CRED_RESIDENT_LEN);
memcpy(data, rp_id_hash, 32);
memcpy(data + 32, cred_id, cred_id_len);
memcpy(data + 32, cred_idr, CRED_RESIDENT_LEN);
memcpy(data + 32 + CRED_RESIDENT_LEN, cred_id, cred_id_len);
file_t *ef = file_new((uint16_t)(EF_CRED + sloti));
file_put_data(ef, data, (uint16_t)cred_id_len + 32);
file_put_data(ef, data, (uint16_t)cred_id_len + 32 + CRED_RESIDENT_LEN);
free(data);
if (new_record == true) { //increase rps
@@ -421,3 +424,33 @@ int credential_derive_large_blob_key(const uint8_t *cred_id, size_t cred_id_len,
mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk);
return 0;
}
int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk) {
memset(outk, 0, CRED_RESIDENT_LEN);
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
uint8_t *cred_idr = outk + CRED_RESIDENT_HEADER_LEN;
mbedtls_md_hmac(md_info, cred_idr, 32, pico_serial.id, sizeof(pico_serial.id), outk);
memcpy(outk + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN);
outk[4 + CRED_PROTO_RESIDENT_LEN] = 0x00;
outk[4 + CRED_PROTO_RESIDENT_LEN + 1] = 0x00;
mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "SLIP-0022", 9, cred_idr);
mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) cred_id, CRED_PROTO_LEN, cred_idr);
mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "resident", 8, cred_idr);
mbedtls_md_hmac(md_info, cred_idr, 32, cred_id, cred_id_len, cred_idr);
return 0;
}
bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len) {
if (cred_id_len < 4 + CRED_PROTO_RESIDENT_LEN) {
return false;
}
return memcmp(cred_id + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN) == 0;
}
int credential_load_resident(const file_t *ef, const uint8_t *rp_id_hash, Credential *cred) {
if (credential_is_resident(file_get_data(ef) + 32, file_get_size(ef) - 32)) {
return credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, cred);
}
return credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, cred);
}

View File

@@ -19,6 +19,7 @@
#define _CREDENTIAL_H_
#include "ctap2_cbor.h"
#include "file.h"
typedef struct CredOptions {
const bool *rk;
@@ -58,6 +59,7 @@ typedef struct Credential {
#define CRED_PROTO_21_S "\xf1\xd0\x02\x01"
#define CRED_PROTO_22_S "\xf1\xd0\x02\x02"
#define CRED_PROTO_23_S "\xf1\xd0\x02\x03"
#define CRED_PROTO CRED_PROTO_22_S
@@ -66,6 +68,11 @@ typedef struct Credential {
#define CRED_TAG_LEN 16
#define CRED_SILENT_TAG_LEN 16
#define CRED_PROTO_RESIDENT CRED_PROTO_23_S
#define CRED_PROTO_RESIDENT_LEN 4
#define CRED_RESIDENT_HEADER_LEN (CRED_PROTO_RESIDENT_LEN + 6)
#define CRED_RESIDENT_LEN (CRED_RESIDENT_HEADER_LEN + 32)
typedef enum
{
CRED_PROTO_21 = 0x01,
@@ -83,7 +90,7 @@ extern int credential_create(CborCharString *rpId,
int alg,
int curve,
uint8_t *cred_id,
size_t *cred_id_len);
uint16_t *cred_id_len);
extern void credential_free(Credential *cred);
extern int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash);
extern int credential_load(const uint8_t *cred_id,
@@ -94,5 +101,8 @@ extern int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len
extern int credential_derive_large_blob_key(const uint8_t *cred_id,
size_t cred_id_len,
uint8_t *outk);
extern int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk);
extern bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len);
extern int credential_load_resident(const file_t *ef, const uint8_t *rp_id_hash, Credential *cred);
#endif // _CREDENTIAL_H_

View File

@@ -114,10 +114,14 @@ typedef struct {
#define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2
#define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9
#define CTAP_CONFIG_EA_UPLOAD 0x66f2a674c29a8dcf
#define CTAP_CONFIG_PIN_POLICY 0x6c07d70fe96c3897
#ifndef ENABLE_EMULATION
#define CTAP_CONFIG_PHY_VIDPID 0x6fcb19b0cbe3acfa
#define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948
#define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd
#define CTAP_CONFIG_PHY_OPTS 0x969f3b09eceb805f
#define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948
#define CTAP_CONFIG_PHY_OPTS 0x269f3b09eceb805f
#endif
#define CTAP_VENDOR_CBOR (CTAPHID_VENDOR_FIRST + 1)
@@ -134,6 +138,7 @@ typedef struct {
#define CTAP_PERMISSION_BE 0x08 // BioEnrollment
#define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite
#define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration
#define CTAP_PERMISSION_PCMR 0x40 // PerCredentialManagementReadOnly
typedef struct mse {
uint8_t Qpt[65];

View File

@@ -15,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "fido.h"
#include "pico_keys.h"
#include "fido.h"
uint8_t PICO_PRODUCT = 2; // Pico FIDO

View File

@@ -15,9 +15,9 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "kek.h"
#include "pico_keys.h"
#include "apdu.h"
#include "ctap.h"
#include "files.h"
@@ -25,10 +25,10 @@
#include "random.h"
#include "mbedtls/x509_crt.h"
#include "mbedtls/hkdf.h"
#if defined(USB_ITF_CCID) || defined(ENABLE_EMULATION)
#if defined(USB_ITF_CCID)
#include "ccid/ccid.h"
#endif
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include <math.h>
@@ -42,6 +42,7 @@ int fido_process_apdu();
int fido_unload();
pinUvAuthToken_t paut = { 0 };
persistentPinUvAuthToken_t ppaut = { 0 };
uint8_t keydev_dec[32];
bool has_keydev_dec = false;
@@ -118,6 +119,15 @@ mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) {
return MBEDTLS_ECP_DP_ED448;
}
#endif
else if (curve == FIDO2_CURVE_BP256R1) {
return MBEDTLS_ECP_DP_BP256R1;
}
else if (curve == FIDO2_CURVE_BP384R1) {
return MBEDTLS_ECP_DP_BP384R1;
}
else if (curve == FIDO2_CURVE_BP512R1) {
return MBEDTLS_ECP_DP_BP512R1;
}
return MBEDTLS_ECP_DP_NONE;
}
int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) {
@@ -158,7 +168,7 @@ int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key) {
uint8_t key_path[KEY_PATH_LEN];
memcpy(key_path, cred_id, KEY_PATH_LEN);
*(uint32_t *) key_path = 0x80000000 | 10022;
for (int i = 1; i < KEY_PATH_ENTRIES; i++) {
for (size_t i = 1; i < KEY_PATH_ENTRIES; i++) {
*(uint32_t *) (key_path + i * sizeof(uint32_t)) |= 0x80000000;
}
return derive_key(NULL, false, key_path, mbedtls_curve, key);
@@ -194,7 +204,7 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
return ret;
}
int load_keydev(uint8_t *key) {
int load_keydev(uint8_t key[32]) {
if (has_keydev_dec == false && !file_has_data(ef_keydev)) {
return PICOKEY_ERR_MEMORY_FATAL;
}
@@ -203,13 +213,39 @@ int load_keydev(uint8_t *key) {
memcpy(key, keydev_dec, sizeof(keydev_dec));
}
else {
memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev));
if (mkek_decrypt(key, 32) != PICOKEY_OK) {
return PICOKEY_EXEC_ERROR;
uint16_t fid_size = file_get_size(ef_keydev);
if (fid_size == 32) {
memcpy(key, file_get_data(ef_keydev), 32);
if (mkek_decrypt(key, 32) != PICOKEY_OK) {
return PICOKEY_EXEC_ERROR;
}
if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) {
return PICOKEY_EXEC_ERROR;
}
}
if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) {
return PICOKEY_EXEC_ERROR;
else if (fid_size == 33 || fid_size == 61) {
uint8_t format = *file_get_data(ef_keydev);
if (format == 0x01 || format == 0x02) { // Format indicator
if (format == 0x02) {
uint8_t tmp_key[61];
memcpy(tmp_key, file_get_data(ef_keydev), sizeof(tmp_key));
int ret = decrypt_with_aad(session_pin, tmp_key + 1, 60, key);
if (ret != PICOKEY_OK) {
return PICOKEY_EXEC_ERROR;
}
}
else {
memcpy(key, file_get_data(ef_keydev) + 1, 32);
}
uint8_t kbase[32];
derive_kbase(kbase);
int ret = aes_decrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32);
if (ret != PICOKEY_OK) {
mbedtls_platform_zeroize(kbase, sizeof(kbase));
return PICOKEY_EXEC_ERROR;
}
mbedtls_platform_zeroize(kbase, sizeof(kbase));
}
}
}
@@ -217,7 +253,7 @@ int load_keydev(uint8_t *key) {
}
int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecp_keypair *key) {
for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) {
uint32_t k = *(uint32_t *) &keyHandle[i * sizeof(uint32_t)];
if (!(k & 0x80000000)) {
return -1;
@@ -258,7 +294,7 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
return r;
}
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) {
if (new_key == true) {
uint32_t val = 0;
random_gen(NULL, (uint8_t *) &val, sizeof(val));
@@ -305,6 +341,23 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
return r;
}
int encrypt_keydev_f1(const uint8_t keydev[32]) {
uint8_t kdata[33] = {0};
kdata[0] = 0x01; // Format indicator
memcpy(kdata + 1, keydev, 32);
uint8_t kbase[32];
derive_kbase(kbase);
int ret = aes_encrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata + 1, 32);
mbedtls_platform_zeroize(kbase, sizeof(kbase));
if (ret != PICOKEY_OK) {
return ret;
}
ret = file_put_data(ef_keydev, kdata, 33);
mbedtls_platform_zeroize(kdata, sizeof(kdata));
low_flash_available();
return ret;
}
int scan_files_fido() {
ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF);
ef_keydev_enc = search_by_fid(EF_KEY_DEV_ENC, NULL, SPECIFY_EF);
@@ -320,17 +373,16 @@ int scan_files_fido() {
mbedtls_ecdsa_free(&ecdsa);
return ret;
}
uint8_t kdata[64];
uint8_t keydev[32] = {0};
size_t key_size = 0;
ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, kdata, sizeof(kdata));
if (ret != PICOKEY_OK) {
return ret;
ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, keydev, sizeof(keydev));
if (ret != 0 || key_size != 32) {
mbedtls_platform_zeroize(keydev, sizeof(keydev));
mbedtls_ecdsa_free(&ecdsa);
return ret != 0 ? ret : PICOKEY_EXEC_ERROR;
}
if (otp_key_1) {
ret = aes_encrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata, 32);
}
ret = file_put_data(ef_keydev, kdata, (uint16_t)key_size);
mbedtls_platform_zeroize(kdata, sizeof(kdata));
encrypt_keydev_f1(keydev);
mbedtls_platform_zeroize(keydev, sizeof(keydev));
mbedtls_ecdsa_free(&ecdsa);
if (ret != PICOKEY_OK) {
return ret;
@@ -341,21 +393,6 @@ int scan_files_fido() {
else {
printf("FATAL ERROR: KEY DEV not found in memory!\r\n");
}
if (ef_mkek) { // No encrypted MKEK
if (!file_has_data(ef_mkek)) {
uint8_t mkek[MKEK_IV_SIZE + MKEK_KEY_SIZE];
random_gen(NULL, mkek, sizeof(mkek));
file_put_data(ef_mkek, mkek, sizeof(mkek));
int ret = aes_encrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), file_get_data(ef_keydev), 32);
mbedtls_platform_zeroize(mkek, sizeof(mkek));
if (ret != 0) {
printf("FATAL ERROR: MKEK encryption failed!\r\n");
}
}
}
else {
printf("FATAL ERROR: MKEK not found in memory!\r\n");
}
ef_certdev = search_by_fid(EF_EE_DEV, NULL, SPECIFY_EF);
if (ef_certdev) {
if (!file_has_data(ef_certdev)) {
@@ -399,13 +436,6 @@ int scan_files_fido() {
printf("FATAL ERROR: Global counter not found in memory!\r\n");
}
ef_pin = search_by_fid(EF_PIN, NULL, SPECIFY_EF);
if (file_get_size(ef_pin) == 18) { // Upgrade PIN storage
uint8_t pin_data[34] = { 0 }, dhash[32];
memcpy(pin_data, file_get_data(ef_pin), 18);
double_hash_pin(pin_data + 2, 16, dhash);
memcpy(pin_data + 2, dhash, 32);
file_put_data(ef_pin, pin_data, 34);
}
ef_authtoken = search_by_fid(EF_AUTHTOKEN, NULL, SPECIFY_EF);
if (ef_authtoken) {
if (!file_has_data(ef_authtoken)) {
@@ -419,6 +449,19 @@ int scan_files_fido() {
else {
printf("FATAL ERROR: Auth Token not found in memory!\r\n");
}
file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF);
if (ef_pauthtoken) {
if (!file_has_data(ef_pauthtoken)) {
uint8_t t[32];
random_gen(NULL, t, sizeof(t));
file_put_data(ef_pauthtoken, t, sizeof(t));
}
ppaut.data = file_get_data(ef_pauthtoken);
ppaut.len = file_get_size(ef_pauthtoken);
}
else {
printf("FATAL ERROR: Persistent Auth Token not found in memory!\r\n");
}
ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF);
if (!file_has_data(ef_largeblob)) {
file_put_data(ef_largeblob, (const uint8_t *) "\x80\x76\xbe\x8b\x52\x8d\x00\x75\xf7\xaa\xe9\x8d\x6f\xa5\x7a\x6d\x3c", 17);
@@ -436,12 +479,14 @@ void scan_all() {
extern void init_otp();
void init_fido() {
scan_all();
#ifdef ENABLE_OTP_APP
init_otp();
#endif
}
bool wait_button_pressed() {
uint32_t val = EV_PRESS_BUTTON;
#ifndef ENABLE_EMULATION
#if defined(PICO_PLATFORM) || defined(ESP_PLATFORM)
queue_try_add(&card_to_usb_q, &val);
do {
queue_remove_blocking(&usb_to_card_q, &val);
@@ -485,11 +530,13 @@ extern int cmd_register();
extern int cmd_authenticate();
extern int cmd_version();
extern int cbor_parse(int, uint8_t *, size_t);
extern void driver_init_hid();
#define CTAP_CBOR 0x10
int cmd_cbor() {
uint8_t *old_buf = res_APDU;
driver_init_hid();
int ret = cbor_parse(0x90, apdu.data, apdu.nc);
if (ret != 0) {
return SW_EXEC_ERROR();

View File

@@ -18,7 +18,7 @@
#ifndef _FIDO_H_
#define _FIDO_H_
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif
#ifndef ESP_PLATFORM
@@ -31,11 +31,7 @@
#ifdef MBEDTLS_EDDSA_C
#include "mbedtls/eddsa.h"
#endif
#ifndef ENABLE_EMULATION
#include "hid/ctap_hid.h"
#else
#include <stdbool.h>
#endif
#define CTAP_PUBKEY_LEN (65)
#define KEY_PATH_LEN (32)
@@ -55,20 +51,28 @@ extern void init_fido();
extern mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve);
extern int mbedtls_curve_to_fido(mbedtls_ecp_group_id id);
extern int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key);
extern int load_keydev(uint8_t *key);
extern int load_keydev(uint8_t key[32]);
extern int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out);
extern int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out);
extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret);
#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256 P256
#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256
#define FIDO2_ALG_EDDSA -8 //EdDSA
#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384 P384
#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512 P521
#define FIDO2_ALG_ESP256 -9 //ECDSA-SHA256 P256
#define FIDO2_ALG_ED25519 -19 //EDDSA Ed25519
#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384
#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512
#define FIDO2_ALG_ECDH_ES_HKDF_256 -25 //ECDH-ES + HKDF-256
#define FIDO2_ALG_ES256K -47
#define FIDO2_ALG_ESP384 -51 //ECDSA-SHA384 P384
#define FIDO2_ALG_ESP512 -52 //ECDSA-SHA512 P521
#define FIDO2_ALG_ED448 -53 //EDDSA Ed448
#define FIDO2_ALG_RS256 -257
#define FIDO2_ALG_RS384 -258
#define FIDO2_ALG_RS512 -259
#define FIDO2_ALG_ESB256 -265 //ECDSA-SHA256 BP256r1
#define FIDO2_ALG_ESB384 -267 //ECDSA-SHA384 BP384r1
#define FIDO2_ALG_ESB512 -268 //ECDSA-SHA512 BP512r1
#define FIDO2_CURVE_P256 1
#define FIDO2_CURVE_P384 2
@@ -78,6 +82,9 @@ extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSec
#define FIDO2_CURVE_ED25519 6
#define FIDO2_CURVE_ED448 7
#define FIDO2_CURVE_P256K1 8
#define FIDO2_CURVE_BP256R1 9
#define FIDO2_CURVE_BP384R1 10
#define FIDO2_CURVE_BP512R1 11
#define FIDO2_AUT_FLAG_UP 0x1
#define FIDO2_AUT_FLAG_UV 0x4
@@ -129,9 +136,17 @@ typedef struct pinUvAuthToken {
bool user_verified;
} pinUvAuthToken_t;
typedef struct persistentPinUvAuthToken {
uint8_t *data;
size_t len;
uint8_t permissions;
} persistentPinUvAuthToken_t;
extern uint32_t user_present_time_limit;
extern pinUvAuthToken_t paut;
extern persistentPinUvAuthToken_t ppaut;
extern int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t len, uint8_t *sign);
extern uint8_t session_pin[32];

View File

@@ -27,6 +27,7 @@ file_t file_entries[] = {
{ .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_PAUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PERSISTENT 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 = EF_LARGEBLOB, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Large Blob

View File

@@ -29,7 +29,9 @@
#define EF_OPTS 0xC001
#define EF_PIN 0x1080
#define EF_AUTHTOKEN 0x1090
#define EF_PAUTHTOKEN 0x1091
#define EF_MINPINLEN 0x1100
#define EF_PIN_COMPLEXITY_POLICY 0x1102
#define EF_DEV_CONF 0x1122
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF
#define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF

View File

@@ -15,10 +15,10 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "fido.h"
#include "pico_keys.h"
#include "fido.h"
#include "stdlib.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif
#include "kek.h"
@@ -85,46 +85,6 @@ void release_mkek(uint8_t *mkek) {
mbedtls_platform_zeroize(mkek, MKEK_SIZE);
}
int store_mkek(const uint8_t *mkek) {
uint8_t tmp_mkek[MKEK_SIZE];
if (mkek == NULL) {
const uint8_t *rd = random_bytes_get(MKEK_IV_SIZE + MKEK_KEY_SIZE);
memcpy(tmp_mkek, rd, MKEK_IV_SIZE + MKEK_KEY_SIZE);
}
else {
memcpy(tmp_mkek, mkek, MKEK_SIZE);
}
if (otp_key_1) {
mkek_masked(tmp_mkek, otp_key_1);
}
*(uint32_t *) MKEK_CHECKSUM(tmp_mkek) = crc32c(MKEK_KEY(tmp_mkek), MKEK_KEY_SIZE);
uint8_t tmp_mkek_pin[MKEK_SIZE];
memcpy(tmp_mkek_pin, tmp_mkek, MKEK_SIZE);
file_t *tf = search_file(EF_MKEK);
if (!tf) {
release_mkek(tmp_mkek);
release_mkek(tmp_mkek_pin);
return PICOKEY_ERR_FILE_NOT_FOUND;
}
aes_encrypt_cfb_256(session_pin, MKEK_IV(tmp_mkek_pin), MKEK_KEY(tmp_mkek_pin), MKEK_KEY_SIZE + MKEK_KEY_CS_SIZE);
file_put_data(tf, tmp_mkek_pin, MKEK_SIZE);
release_mkek(tmp_mkek_pin);
low_flash_available();
release_mkek(tmp_mkek);
return PICOKEY_OK;
}
int mkek_encrypt(uint8_t *data, uint16_t len) {
int r;
uint8_t mkek[MKEK_SIZE + 4];
if ((r = load_mkek(mkek)) != PICOKEY_OK) {
return r;
}
r = aes_encrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), data, len);
release_mkek(mkek);
return r;
}
int mkek_decrypt(uint8_t *data, uint16_t len) {
int r;
uint8_t mkek[MKEK_SIZE + 4];

View File

@@ -15,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "pico_keys.h"
#include "fido.h"
#include "ctap2_cbor.h"

View File

@@ -15,8 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "fido.h"
#include "pico_keys.h"
#include "fido.h"
#include "apdu.h"
#include "version.h"
#include "files.h"
@@ -40,7 +40,9 @@ int man_select(app_t *a, uint8_t force) {
apdu.ne = res_APDU_size;
if (force) {
scan_all();
#ifdef ENABLE_OTP_APP
init_otp();
#endif
}
return PICOKEY_OK;
}
@@ -114,7 +116,7 @@ int man_get_config() {
if (!file_has_data(ef)) {
res_APDU[res_APDU_size++] = TAG_USB_ENABLED;
res_APDU[res_APDU_size++] = 2;
uint16_t caps = 0;
caps = 0;
if (cap_supported(CAP_FIDO2)) {
caps |= CAP_FIDO2;
}

View File

@@ -19,7 +19,7 @@
#define _MANAGEMENT_H_
#include <stdlib.h>
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif

View File

@@ -15,8 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "fido.h"
#include "pico_keys.h"
#include "fido.h"
#include "apdu.h"
#include "files.h"
#include "random.h"
@@ -24,6 +24,7 @@
#include "asn1.h"
#include "crypto_utils.h"
#include "management.h"
extern bool is_nk;
#define MAX_OATH_CRED 255
#define CHALLENGE_LEN 8
@@ -44,6 +45,10 @@
#define TAG_PASSWORD 0x80
#define TAG_NEW_PASSWORD 0x81
#define TAG_PIN_COUNTER 0x82
#define TAG_PWS_LOGIN 0x83
#define TAG_PWS_PASSWORD 0x84
#define TAG_PWS_METADATA 0x85
#define TAG_SERIAL_NUMBER 0x8F
#define ALG_HMAC_SHA1 0x01
#define ALG_HMAC_SHA256 0x02
@@ -56,6 +61,7 @@
#define PROP_INC 0x01
#define PROP_TOUCH 0x02
#define PROP_PIN 0x03
int oath_process_apdu();
int oath_unload();
@@ -99,6 +105,12 @@ int oath_select(app_t *a, uint8_t force) {
res_APDU[res_APDU_size++] = TAG_ALGO;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = ALG_HMAC_SHA1;
if (is_nk) {
res_APDU[res_APDU_size++] = TAG_SERIAL_NUMBER;
res_APDU[res_APDU_size++] = 8;
memcpy(res_APDU + res_APDU_size, pico_serial_str, 8);
res_APDU_size += 8;
}
apdu.ne = res_APDU_size;
return PICOKEY_OK;
}
@@ -270,16 +282,27 @@ int cmd_list() {
if (validated == false) {
return SW_SECURITY_STATUS_NOT_SATISFIED();
}
bool ext = (apdu.nc == 1 && apdu.data[0] == 0x01);
for (int i = 0; i < MAX_OATH_CRED; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
if (file_has_data(ef)) {
asn1_ctx_t ctxi, key = { 0 }, name = { 0 };
asn1_ctx_t ctxi, key = { 0 }, name = { 0 }, pws = { 0 };
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == true && asn1_find_tag(&ctxi, TAG_KEY, &key) == true) {
res_APDU[res_APDU_size++] = TAG_NAME_LIST;
res_APDU[res_APDU_size++] = (uint8_t)(name.len + 1);
res_APDU[res_APDU_size++] = (uint8_t)(name.len + 1 + (ext ? 1 : 0));
res_APDU[res_APDU_size++] = key.data[0];
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
if (ext) {
uint8_t props = 0x0;
if (asn1_find_tag(&ctxi, TAG_PWS_LOGIN, &pws) == true || asn1_find_tag(&ctxi, TAG_PWS_PASSWORD, &pws) == true || asn1_find_tag(&ctxi, TAG_PWS_METADATA, &pws) == true) {
props |= 0x4;
}
if (asn1_find_tag(&ctxi, TAG_PROPERTY, &pws) == true && (pws.data[0] & PROP_TOUCH)) {
props |= 0x1;
}
res_APDU[res_APDU_size++] = props;
}
}
}
}
@@ -615,7 +638,7 @@ int cmd_rename() {
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
return SW_WRONG_DATA();
}
uint8_t *new_data = (uint8_t *) calloc(sizeof(uint8_t), fsize + new_name.len - name.len);
uint8_t *new_data = (uint8_t *) calloc(fsize + new_name.len - name.len, sizeof(uint8_t));
memcpy(new_data, fdata, name.data - fdata);
*(new_data + (name.data - fdata) - 1) = new_name.len;
memcpy(new_data + (name.data - fdata), new_name.data, new_name.len);
@@ -626,6 +649,53 @@ int cmd_rename() {
return SW_OK();
}
int cmd_get_credential() {
asn1_ctx_t ctxi, name = { 0 };
if (apdu.nc < 3) {
return SW_INCORRECT_PARAMS();
}
if (apdu.data[0] != TAG_NAME) {
return SW_WRONG_DATA();
}
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
return SW_WRONG_DATA();
}
file_t *ef = find_oath_cred(name.data, name.len);
if (file_has_data(ef) == false) {
return SW_DATA_INVALID();
}
asn1_ctx_t login = { 0 }, pw = { 0 }, meta = { 0 }, prop = { 0 };
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == true) {
res_APDU[res_APDU_size++] = TAG_NAME;
res_APDU[res_APDU_size++] = (uint8_t)(name.len);
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
}
if (asn1_find_tag(&ctxi, TAG_PWS_LOGIN, &login) == true) {
res_APDU[res_APDU_size++] = TAG_PWS_LOGIN;
res_APDU[res_APDU_size++] = (uint8_t)(login.len);
memcpy(res_APDU + res_APDU_size, login.data, login.len); res_APDU_size += login.len;
}
if (asn1_find_tag(&ctxi, TAG_PWS_PASSWORD, &pw) == true) {
res_APDU[res_APDU_size++] = TAG_PWS_PASSWORD;
res_APDU[res_APDU_size++] = (uint8_t)(pw.len);
memcpy(res_APDU + res_APDU_size, pw.data, pw.len); res_APDU_size += pw.len;
}
if (asn1_find_tag(&ctxi, TAG_PWS_METADATA, &meta) == true) {
res_APDU[res_APDU_size++] = TAG_PWS_METADATA;
res_APDU[res_APDU_size++] = (uint8_t)(meta.len);
memcpy(res_APDU + res_APDU_size, meta.data, meta.len); res_APDU_size += meta.len;
}
if (asn1_find_tag(&ctxi, TAG_PROPERTY, &prop) == true) {
res_APDU[res_APDU_size++] = TAG_PROPERTY;
res_APDU[res_APDU_size++] = (uint8_t)(prop.len);
memcpy(res_APDU + res_APDU_size, prop.data, prop.len); res_APDU_size += prop.len;
}
apdu.ne = res_APDU_size;
return SW_OK();
}
#define INS_PUT 0x01
#define INS_DELETE 0x02
#define INS_SET_CODE 0x03
@@ -640,6 +710,7 @@ int cmd_rename() {
#define INS_VERIFY_PIN 0xb2
#define INS_CHANGE_PIN 0xb3
#define INS_SET_PIN 0xb4
#define INS_GET_CREDENTIAL 0xb5
static const cmd_t cmds[] = {
{ INS_PUT, cmd_put },
@@ -656,6 +727,7 @@ static const cmd_t cmds[] = {
{ INS_CHANGE_PIN, cmd_change_otp_pin },
{ INS_VERIFY_PIN, cmd_verify_otp_pin },
{ INS_VERIFY_CODE, cmd_verify_hotp },
{ INS_GET_CREDENTIAL, cmd_get_credential },
{ 0x00, 0x0 }
};

View File

@@ -15,8 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "fido.h"
#include "pico_keys.h"
#include "fido.h"
#include "apdu.h"
#include "files.h"
#include "random.h"
@@ -24,14 +24,17 @@
#include "asn1.h"
#include "hid/ctap_hid.h"
#include "usb.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#ifdef ENABLE_EMULATION
void add_keyboard_buffer(const uint8_t *buf, size_t len, bool press_enter) {}
void append_keyboard_buffer(const uint8_t *buf, size_t len) {}
#else
#include "tusb.h"
#endif
#include "mbedtls/aes.h"
#include "management.h"
#ifndef ENABLE_EMULATION
#include "tusb.h"
#endif
#define FIXED_SIZE 16
#define KEY_SIZE 16
@@ -116,12 +119,10 @@ uint16_t otp_status(bool is_otp);
int otp_process_apdu();
int otp_unload();
#ifndef ENABLE_EMULATION
extern int (*hid_set_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
extern uint16_t (*hid_get_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);
int otp_hid_set_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
uint16_t otp_hid_get_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);
#endif
const uint8_t otp_aid[] = {
7,
@@ -200,15 +201,12 @@ uint16_t calculate_crc(const uint8_t *data, size_t data_len) {
return crc & 0xFFFF;
}
#ifndef ENABLE_EMULATION
static uint8_t session_counter[2] = { 0 };
#endif
int otp_button_pressed(uint8_t slot) {
init_otp();
if (!cap_supported(CAP_OTP)) {
return 3;
}
#ifndef ENABLE_EMULATION
file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
const uint8_t *data = file_get_data(ef);
otp_config_t *otp_config = (otp_config_t *) data;
@@ -218,6 +216,7 @@ int otp_button_pressed(uint8_t slot) {
if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) {
return 2;
}
#ifdef ENABLE_OATH_APP
if (otp_config->tkt_flags & OATH_HOTP) {
uint8_t tmp_key[KEY_SIZE + 2];
tmp_key[0] = 0x01;
@@ -259,6 +258,7 @@ int otp_button_pressed(uint8_t slot) {
append_keyboard_buffer((const uint8_t *) "\r", 1);
}
}
#endif
else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) {
uint8_t fixed_size = FIXED_SIZE + UID_SIZE + KEY_SIZE;
if (otp_config->cfg_flags & SHORT_TICKET) { // Not clear which is the purpose of SHORT_TICKET
@@ -317,19 +317,15 @@ int otp_button_pressed(uint8_t slot) {
low_flash_available();
}
}
#else
(void) slot;
#endif
return 0;
}
INITIALIZER( otp_ctor ) {
register_app(otp_select, otp_aid);
button_pressed_cb = otp_button_pressed;
#ifndef ENABLE_EMULATION
hid_set_report_cb = otp_hid_set_report_cb;
hid_get_report_cb = otp_hid_get_report_cb;
#endif
}
int otp_unload() {
@@ -490,20 +486,20 @@ int cmd_otp() {
return SW_WRONG_DATA();
}
int ret = 0;
#ifndef ENABLE_EMULATION
uint8_t *rdata_bk = apdu.rdata;
if (otp_config->cfg_flags & CHAL_BTN_TRIG) {
status_byte = 0x20;
otp_status(_is_otp);
#ifndef ENABLE_EMULATION
if (wait_button() == true) {
status_byte = 0x00;
otp_status(_is_otp);
return SW_CONDITIONS_NOT_SATISFIED();
}
#endif
status_byte = 0x10;
apdu.rdata = rdata_bk;
}
#endif
if (p1 == 0x30 || p1 == 0x38) {
if (!(otp_config->cfg_flags & CHAL_HMAC)) {
return SW_WRONG_DATA();
@@ -568,8 +564,6 @@ int otp_process_apdu() {
return SW_INS_NOT_SUPPORTED();
}
#ifndef ENABLE_EMULATION
uint8_t otp_frame_rx[70] = {0};
uint8_t otp_frame_tx[70] = {0};
uint8_t otp_exp_seq = 0, otp_curr_seq = 0;
@@ -671,5 +665,3 @@ uint16_t otp_hid_get_report_cb(uint8_t itf,
return reqlen;
}
#endif

View File

@@ -20,12 +20,13 @@
from http import client
from fido2.hid import CtapHidDevice
from fido2.client import Fido2Client, UserInteraction, ClientError, _Ctap1ClientBackend
from fido2.client import Fido2Client, UserInteraction, ClientError, _Ctap1ClientBackend, DefaultClientDataCollector
from fido2.attestation import FidoU2FAttestation
from fido2.ctap2.pin import ClientPin
from fido2.server import Fido2Server
from fido2.ctap import CtapError
from fido2.webauthn import CollectedClientData, PublicKeyCredentialParameters, PublicKeyCredentialType
from fido2.webauthn import PublicKeyCredentialParameters, PublicKeyCredentialType, PublicKeyCredentialCreationOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, AuthenticatorSelectionCriteria, UserVerificationRequirement, PublicKeyCredentialRequestOptions
from fido2.ctap2.extensions import HmacSecretExtension, LargeBlobExtension, CredBlobExtension, CredProtectExtension, MinPinLengthExtension, CredPropsExtension, ThirdPartyPaymentExtension
from utils import *
from fido2.cose import ES256
import sys
@@ -70,11 +71,13 @@ class DeviceSelectCredential:
pass
class Device():
def __init__(self, origin="https://example.com", user_interaction=CliInteraction(),uv="discouraged",rp={"id": "example.com", "name": "Example RP"}, attestation="direct"):
def __init__(self, origin="https://example.com", user_interaction=CliInteraction(), uv="discouraged", rp={"id": "example.com", "name": "Example RP"}, attestation="direct"):
self.__user = None
self.__set_client(origin=origin, user_interaction=user_interaction, uv=uv)
self.__set_server(rp=rp, attestation=attestation)
def __verify_rp(rp_id, origin):
return True
def __set_client(self, origin, user_interaction, uv):
self.__uv = uv
@@ -101,14 +104,23 @@ class Device():
sys.exit(1)
# Set up a FIDO 2 client using the origin https://example.com
self.__client = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction)
extensions = [
HmacSecretExtension(allow_hmac_secret=True),
LargeBlobExtension(),
CredBlobExtension(),
CredProtectExtension(),
MinPinLengthExtension(),
CredPropsExtension(),
ThirdPartyPaymentExtension()
]
self.__client = Fido2Client(self.__dev, client_data_collector=DefaultClientDataCollector(self.__origin, verify=Device.__verify_rp), user_interaction=self.__user_interaction, extensions=extensions)
# Prefer UV if supported and configured
if self.__client.info.options.get("uv") or self.__client.info.options.get("pinUvAuthToken"):
self.__uv = "preferred"
print("Authenticator supports User Verification")
self.__client1 = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction)
self.__client1 = Fido2Client(self.__dev, client_data_collector=DefaultClientDataCollector(self.__origin, verify=Device.__verify_rp), user_interaction=self.__user_interaction)
self.__client1._backend = _Ctap1ClientBackend(self.__dev, user_interaction=self.__user_interaction)
self.ctap1 = self.__client1._backend.ctap1
@@ -117,7 +129,7 @@ class Device():
self.__attestation = attestation
self.__server = Fido2Server(self.__rp, attestation=self.__attestation)
self.__server.allowed_algorithms = [
PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, p['alg'])
PublicKeyCredentialParameters(type=PublicKeyCredentialType.PUBLIC_KEY, alg=p['alg'])
for p in self.__client._backend.info.algorithms
]
@@ -216,9 +228,7 @@ class Device():
'key_params':key_params}}
def doMC(self, client_data=Ellipsis, rp=Ellipsis, user=Ellipsis, key_params=Ellipsis, exclude_list=None, extensions=None, rk=None, user_verification=None, enterprise_attestation=None, event=None, ctap1=False):
client_data = client_data if client_data is not Ellipsis else CollectedClientData.create(
type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32)
)
client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
rp = rp if rp is not Ellipsis else self.__rp
user = user if user is not Ellipsis else self.user()
key_params = key_params if key_params is not Ellipsis else self.__server.allowed_algorithms
@@ -226,22 +236,31 @@ class Device():
client = self.__client1
else:
client = self.__client
result = client._backend.do_make_credential(
client_data=client_data,
rp=rp,
user=user,
key_params=key_params,
exclude_list=exclude_list,
options=PublicKeyCredentialCreationOptions(
rp=PublicKeyCredentialRpEntity.from_dict(rp),
user=PublicKeyCredentialUserEntity.from_dict(user),
pub_key_cred_params=key_params,
exclude_credentials=exclude_list,
extensions=extensions,
rk=rk,
user_verification=user_verification,
enterprise_attestation=enterprise_attestation,
challenge=os.urandom(32),
authenticator_selection=AuthenticatorSelectionCriteria(
require_resident_key=rk,
user_verification=UserVerificationRequirement.REQUIRED if user_verification else UserVerificationRequirement.DISCOURAGED
),
attestation=enterprise_attestation
)
client_data, rp_id = client_data.collect_client_data(options=options)
result = client._backend.do_make_credential(
options=options,
client_data=client_data,
rp_id=rp_id,
enterprise_rpid_list=None,
event=event
)
return {'res':result,'req':{'client_data':client_data,
return {'res':result.response,'req':{'client_data':client_data,
'rp':rp,
'user':user,
'key_params':key_params}}
'key_params':key_params},'client_extension_results':result.client_extension_results}
def try_make_credential(self, options=None):
if (options is None):
@@ -267,14 +286,14 @@ class Device():
# Complete registration
auth_data = self.__server.register_complete(
state, result.client_data, result.attestation_object
state=state, response=result
)
credentials = [auth_data.credential_data]
print("New credential created!")
print("CLIENT DATA:", result.client_data)
print("ATTESTATION OBJECT:", result.attestation_object)
print("CLIENT DATA:", result.response.client_data)
print("ATTESTATION OBJECT:", result.response.attestation_object)
print()
print("CREDENTIAL DATA:", auth_data.credential_data)
@@ -294,17 +313,14 @@ class Device():
self.__server.authenticate_complete(
state,
credentials,
result.credential_id,
result.client_data,
result.authenticator_data,
result.signature,
result
)
print("Credential authenticated!")
print("CLIENT DATA:", result.client_data)
print("CLIENT DATA:", result.response.client_data)
print()
print("AUTH DATA:", result.authenticator_data)
print("AUTH DATA:", result.response.authenticator_data)
def GA(self, rp_id=Ellipsis, client_data_hash=Ellipsis, allow_list=None, extensions=None, options=None, pin_uv_param=None, pin_uv_protocol=None):
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
@@ -325,21 +341,31 @@ class Device():
return self.__client._backend.ctap2.get_next_assertion()
def doGA(self, client_data=Ellipsis, rp_id=Ellipsis, allow_list=None, extensions=None, user_verification=None, event=None, ctap1=False, check_only=False):
client_data = client_data if client_data is not Ellipsis else CollectedClientData.create(
type=CollectedClientData.TYPE.GET, origin=self.__origin, challenge=os.urandom(32)
)
client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
if (ctap1 is True):
client = self.__client1
else:
client = self.__client
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
options=PublicKeyCredentialRequestOptions(
challenge=os.urandom(32),
rp_id=rp_id,
allow_credentials=allow_list,
user_verification=UserVerificationRequirement.REQUIRED if user_verification else UserVerificationRequirement.DISCOURAGED,
extensions=extensions
)
client_data, rp_id = client_data.collect_client_data(options=options)
if (ctap1 is True):
client = self.__client1
else:
client = self.__client
try:
result = client._backend.do_get_assertion(
options=options,
client_data=client_data,
rp_id=rp_id,
allow_list=allow_list,
extensions=extensions,
user_verification=user_verification,
event=event
)
except ClientError as e:
@@ -347,11 +373,9 @@ class Device():
client_pin = ClientPin(self.__client._backend.ctap2)
client_pin.set_pin(DEFAULT_PIN)
result = client._backend.do_get_assertion(
options=options,
client_data=client_data,
rp_id=rp_id,
allow_list=allow_list,
extensions=extensions,
user_verification=user_verification,
event=event
)
else:
@@ -416,8 +440,8 @@ def AuthRes(device, RegRes, *args):
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
], *args)
aut_data = res['res'].get_response(0)
m = aut_data.authenticator_data.rp_id_hash + aut_data.authenticator_data.flags.to_bytes(1, 'big') + aut_data.authenticator_data.counter.to_bytes(4, 'big') + aut_data.client_data.hash
ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.signature)
m = aut_data.response.authenticator_data.rp_id_hash + aut_data.response.authenticator_data.flags.to_bytes(1, 'big') + aut_data.response.authenticator_data.counter.to_bytes(4, 'big') + aut_data.response.client_data.hash
ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.response.signature)
return aut_data
@pytest.fixture(scope="class")

View File

@@ -0,0 +1,32 @@
FROM debian:bookworm
ARG DEBIAN_FRONTEND=noninteractive
RUN apt update && apt upgrade -y
RUN apt install -y apt-utils
RUN apt install -y libccid \
libpcsclite-dev \
git \
autoconf \
pkg-config \
libtool \
help2man \
automake \
gcc \
make \
build-essential \
opensc \
python3 \
python3-pip \
swig \
cmake \
libfuse-dev \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install pytest pycvc cryptography pyscard inputimeout fido2==2.0.0 --break-system-packages
WORKDIR /
RUN git clone https://github.com/frankmorgner/vsmartcard.git
WORKDIR /vsmartcard/virtualsmartcard
RUN autoreconf --verbose --install
RUN ./configure --sysconfdir=/etc
RUN make && make install
WORKDIR /

View File

@@ -22,11 +22,7 @@ RUN apt install -y libccid \
cmake \
libfuse-dev \
&& rm -rf /var/lib/apt/lists/*
RUN pip3 install pytest pycvc cryptography pyscard inputimeout
RUN git clone https://github.com/polhenarejos/python-fido2.git
WORKDIR /python-fido2
RUN git checkout development
RUN pip3 install .
RUN pip3 install pytest pycvc cryptography pyscard inputimeout fido2==2.0.0
WORKDIR /
RUN git clone https://github.com/frankmorgner/vsmartcard.git
WORKDIR /vsmartcard/virtualsmartcard

View File

@@ -27,16 +27,17 @@
from __future__ import annotations
from .base import HidDescriptor
from ..ctap import CtapDevice, CtapError, STATUS
from ..utils import LOG_LEVEL_TRAFFIC
from threading import Event
from enum import IntEnum, IntFlag, unique
from typing import Tuple, Optional, Callable, Iterator
import logging
import os
import struct
import sys
import os
import logging
from enum import IntEnum, IntFlag, unique
from threading import Event
from typing import Callable, Iterator
from ..ctap import STATUS, CtapDevice, CtapError
from ..utils import LOG_LEVEL_TRAFFIC
from .base import HidDescriptor
logger = logging.getLogger(__name__)
@@ -55,6 +56,7 @@ elif sys.platform.startswith("openbsd"):
from . import openbsd as backend
else:
raise Exception("Unsupported platform")
from . import emulation as backend
list_descriptors = backend.list_descriptors
@@ -62,6 +64,10 @@ get_descriptor = backend.get_descriptor
open_connection = backend.open_connection
class ConnectionFailure(Exception):
"""The CTAP connection failed or returned an invalid response."""
@unique
class CTAPHID(IntEnum):
PING = 0x01
@@ -109,7 +115,7 @@ class CtapHidDevice(CtapDevice):
response = self.call(CTAPHID.INIT, nonce)
r_nonce, response = response[:8], response[8:]
if r_nonce != nonce:
raise Exception("Wrong nonce")
raise ConnectionFailure("Wrong nonce")
(
self._channel_id,
self._u2fhid_version,
@@ -129,7 +135,7 @@ class CtapHidDevice(CtapDevice):
return self._u2fhid_version
@property
def device_version(self) -> Tuple[int, int, int]:
def device_version(self) -> tuple[int, int, int]:
"""Device version number."""
return self._device_version
@@ -139,12 +145,12 @@ class CtapHidDevice(CtapDevice):
return self._capabilities
@property
def product_name(self) -> Optional[str]:
def product_name(self) -> str | None:
"""Product name of device."""
return self.descriptor.product_name
@property
def serial_number(self) -> Optional[str]:
def serial_number(self) -> str | None:
"""Serial number of device."""
return self.descriptor.serial_number
@@ -159,10 +165,22 @@ class CtapHidDevice(CtapDevice):
self,
cmd: int,
data: bytes = b"",
event: Optional[Event] = None,
on_keepalive: Optional[Callable[[int], None]] = None,
event: Event | None = None,
on_keepalive: Callable[[STATUS], None] | None = None,
) -> bytes:
event = event or Event()
while True:
try:
return self._do_call(cmd, data, event, on_keepalive)
except CtapError as e:
if e.code == CtapError.ERR.CHANNEL_BUSY:
if not event.wait(0.1):
logger.warning("CTAP channel busy, trying again...")
continue # Keep retrying on BUSY while not cancelled
raise
def _do_call(self, cmd, data, event, on_keepalive):
remaining = data
seq = 0
@@ -194,7 +212,7 @@ class CtapHidDevice(CtapDevice):
r_channel = struct.unpack_from(">I", recv)[0]
recv = recv[4:]
if r_channel != self._channel_id:
raise Exception("Wrong channel")
raise ConnectionFailure("Wrong channel")
if not response: # Initialization packet
r_cmd, r_len = struct.unpack_from(">BH", recv)
@@ -202,13 +220,12 @@ class CtapHidDevice(CtapDevice):
if r_cmd == TYPE_INIT | cmd:
pass # first data packet
elif r_cmd == TYPE_INIT | CTAPHID.KEEPALIVE:
ka_status = struct.unpack_from(">B", recv)[0]
logger.debug(f"Got keepalive status: {ka_status:02x}")
try:
ka_status = STATUS(struct.unpack_from(">B", recv)[0])
logger.debug(f"Got keepalive status: {ka_status:02x}")
except ValueError:
raise ConnectionFailure("Invalid keepalive status")
if on_keepalive and ka_status != last_ka:
try:
ka_status = STATUS(ka_status)
except ValueError:
pass # Unknown status value
last_ka = ka_status
on_keepalive(ka_status)
continue
@@ -220,7 +237,7 @@ class CtapHidDevice(CtapDevice):
r_seq = struct.unpack_from(">B", recv)[0]
recv = recv[1:]
if r_seq != seq:
raise Exception("Wrong sequence number")
raise ConnectionFailure("Wrong sequence number")
seq += 1
response += recv

View File

@@ -46,7 +46,7 @@
# default values, can be overridden by the environment
: ${MBEDTLS_DOCKER_GUEST:=bullseye}
: ${MBEDTLS_DOCKER_GUEST:=bookworm}
DOCKER_IMAGE_TAG="pico-hsm-test:${MBEDTLS_DOCKER_GUEST}"

View File

@@ -20,8 +20,6 @@
from fido2.client import CtapError
from fido2.cose import ES256, ES384, ES512, EdDSA
import fido2.features
fido2.features.webauthn_json_mapping.enabled = False
from utils import ES256K
import pytest
@@ -51,13 +49,13 @@ def test_bad_type_cdh(device):
def test_missing_user(device):
with pytest.raises(CtapError) as e:
device.doMC(user=None)
device.MC(user=None)
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_bad_type_user_user(device):
with pytest.raises(CtapError) as e:
device.doMC(user=b"12345678")
device.MC(user=b"12345678")
def test_missing_rp(device):
with pytest.raises(CtapError) as e:
@@ -71,7 +69,7 @@ def test_bad_type_rp(device):
def test_missing_pubKeyCredParams(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=None)
device.MC(key_params=None)
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
@@ -93,35 +91,23 @@ def test_bad_type_options(device):
def test_bad_type_rp_name(device):
with pytest.raises(CtapError) as e:
device.doMC(rp={"id": "test.org", "name": 8, "icon": "icon"})
device.MC(rp={"id": "test.org", "name": 8, "icon": "icon"})
def test_bad_type_rp_id(device):
with pytest.raises(CtapError) as e:
device.doMC(rp={"id": 8, "name": "name", "icon": "icon"})
def test_bad_type_rp_icon(device):
with pytest.raises(CtapError) as e:
device.doMC(rp={"id": "test.org", "name": "name", "icon": 8})
device.MC(rp={"id": 8, "name": "name", "icon": "icon"})
def test_bad_type_user_name(device):
with pytest.raises(CtapError) as e:
device.doMC(user={"id": b"user_id", "name": 8})
device.MC(user={"id": b"user_id", "name": 8})
def test_bad_type_user_id(device):
with pytest.raises(CtapError) as e:
device.doMC(user={"id": "user_id", "name": "name"})
device.MC(user={"id": "user_id", "name": "name"})
def test_bad_type_user_displayName(device):
with pytest.raises(CtapError) as e:
device.doMC(user={"id": "user_id", "name": "name", "displayName": 8})
def test_bad_type_user_icon(device):
with pytest.raises(CtapError) as e:
device.doMC(user={"id": "user_id", "name": "name", "icon": 8})
def test_bad_type_pubKeyCredParams(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=["wrong"])
device.MC(user={"id": "user_id", "name": "name", "displayName": 8})
@pytest.mark.parametrize(
"alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM, EdDSA.ALGORITHM]
@@ -132,13 +118,13 @@ def test_algorithms(device, info, alg):
def test_missing_pubKeyCredParams_type(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=[{"alg": ES256.ALGORITHM}])
device.MC(key_params=[{"alg": ES256.ALGORITHM}])
assert e.value.code == CtapError.ERR.INVALID_CBOR
def test_missing_pubKeyCredParams_alg(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=[{"type": "public-key"}])
device.MC(key_params=[{"type": "public-key"}])
assert e.value.code in [
CtapError.ERR.INVALID_CBOR,
@@ -147,7 +133,7 @@ def test_missing_pubKeyCredParams_alg(device):
def test_bad_type_pubKeyCredParams_alg(device):
with pytest.raises(CtapError) as e:
device.doMC(key_params=[{"alg": "7", "type": "public-key"}])
device.MC(key_params=[{"alg": "7", "type": "public-key"}])
assert e.value.code == CtapError.ERR.CBOR_UNEXPECTED_TYPE
@@ -158,26 +144,26 @@ def test_unsupported_algorithm(device):
assert e.value.code == CtapError.ERR.UNSUPPORTED_ALGORITHM
def test_exclude_list(resetdevice):
resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "rot13"}])
resetdevice.MC(exclude_list=[{"id": b"1234", "type": "rot13"}])
def test_exclude_list2(resetdevice):
resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}])
resetdevice.MC(exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}])
def test_bad_type_exclude_list(device):
with pytest.raises(CtapError) as e:
device.doMC(exclude_list=["1234"])
device.MC(exclude_list=["1234"])
def test_missing_exclude_list_type(device):
with pytest.raises(CtapError) as e:
device.doMC(exclude_list=[{"id": b"1234"}])
device.MC(exclude_list=[{"id": b"1234"}])
def test_missing_exclude_list_id(device):
with pytest.raises(CtapError) as e:
device.doMC(exclude_list=[{"type": "public-key"}])
device.MC(exclude_list=[{"type": "public-key"}])
def test_bad_type_exclude_list_id(device):
with pytest.raises(CtapError) as e:
device.doMC(exclude_list=[{"type": "public-key", "id": "1234"}])
device.MC(exclude_list=[{"type": "public-key", "id": "1234"}])
def test_bad_type_exclude_list_type(device):
with pytest.raises(CtapError) as e:

View File

@@ -31,10 +31,10 @@ def test_authenticate(device):
AUTRes = device.authenticate(credentials)
def test_assertion_auth_data(GARes):
assert len(GARes['res'].get_response(0).authenticator_data) == 37
assert len(GARes['res'].get_response(0).response.authenticator_data) == 37
def test_Check_that_AT_flag_is_not_set(GARes):
assert (GARes['res'].get_response(0).authenticator_data.flags & 0xF8) == 0
assert (GARes['res'].get_response(0).response.authenticator_data.flags & 0xF8) == 0
def test_that_user_credential_and_numberOfCredentials_are_not_present(device, MCRes):
res = device.GA(allow_list=[
@@ -63,8 +63,8 @@ def test_get_assertion_allow_list_filtering_and_buffering(device):
""" Check that authenticator filters and stores items in allow list correctly """
allow_list = []
rp1 = {"id": "rp1.com", "name": "rp1.com"}
rp2 = {"id": "rp2.com", "name": "rp2.com"}
rp1 = {"id": "example.com", "name": "rp1.com"}
rp2 = {"id": "example.com", "name": "rp2.com"}
rp1_registrations = []
rp2_registrations = []
@@ -127,7 +127,7 @@ def test_mismatched_rp(device, GARes):
rp_id += ".com"
with pytest.raises(CtapError) as e:
device.doGA(rp_id=rp_id)
device.GA(rp_id=rp_id)
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_missing_rp(device):
@@ -137,7 +137,7 @@ def test_missing_rp(device):
def test_bad_rp(device):
with pytest.raises(CtapError) as e:
device.doGA(rp_id={"id": {"type": "wrong"}})
device.GA(rp_id={"id": {"type": "wrong"}})
def test_missing_cdh(device):
with pytest.raises(CtapError) as e:
@@ -150,11 +150,11 @@ def test_bad_cdh(device):
def test_bad_allow_list(device):
with pytest.raises(CtapError) as e:
device.doGA(allow_list={"type": "wrong"})
device.GA(allow_list={"type": "wrong"})
def test_bad_allow_list_item(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=["wrong"] + [
device.GA(allow_list=["wrong"] + [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
@@ -177,7 +177,7 @@ def test_option_up(device, info, GARes):
assert res.auth_data.flags & (1 << 0)
def test_allow_list_fake_item(device, MCRes):
device.doGA(allow_list=[{"type": "rot13", "id": b"1234"}]
device.GA(allow_list=[{"type": "rot13", "id": b"1234"}]
+ [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
],
@@ -185,7 +185,7 @@ def test_allow_list_fake_item(device, MCRes):
def test_allow_list_missing_field(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[{"id": b"1234"}] + [
device.GA(allow_list=[{"id": b"1234"}] + [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
@@ -200,7 +200,7 @@ def test_allow_list_field_wrong_type(device, MCRes):
def test_allow_list_id_wrong_type(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[{"type": "public-key", "id": 42}]
device.GA(allow_list=[{"type": "public-key", "id": 42}]
+ [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
@@ -208,7 +208,7 @@ def test_allow_list_id_wrong_type(device, MCRes):
def test_allow_list_missing_id(device, MCRes):
with pytest.raises(CtapError) as e:
device.doGA(allow_list=[{"type": "public-key"}] + [
device.GA(allow_list=[{"type": "public-key"}] + [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)

View File

@@ -58,8 +58,9 @@ def test_with_allow_list_after_reset(device, MCRes_DC, GARes_DC):
device.reset()
# It returns a silent authentication
ga_res = device.doGA(allow_list=allow_list)
with pytest.raises(CtapError) as e:
ga_res = device.doGA(allow_list=allow_list)
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_resident_key(MCRes_DC, info):
@@ -85,7 +86,7 @@ def test_multiple_rk_nodisplay(device, MCRes_DC):
auths = []
regs = []
# Use unique RP to not collide with other credentials
rp = {"id": f"unique-{random.random()}.com", "name": "Example"}
rp = {"id": "example.com", "name": "Example"}
for i in range(0, 3):
res = device.doMC(rp=rp, rk=True, user=generate_random_user())
regs.append(res)
@@ -116,7 +117,7 @@ def test_rk_maximum_size_nodisplay(device):
auths = resGA.get_assertions()
user_max_GA = auths[0]
print(auths)
for y in ("name", "displayName", "id"):
if (y in user_max_GA):
assert user_max_GA.user[y] == user_max[y]
@@ -126,7 +127,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
"""
Test maximum returned capacity of the RK for the given RP
"""
device.reset()
# Try to determine from get_info, or default to 19.
RK_CAPACITY_PER_RP = info.max_creds_in_list
if not RK_CAPACITY_PER_RP:
@@ -140,7 +141,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
return user
# Use unique RP to not collide with other credentials from other tests.
rp = {"id": f"unique-{random.random()}.com", "name": "Example"}
rp = {"id": "example.com", "name": "Example"}
# req = FidoRequest(MCRes_DC, options=None, user=get_user(), rp = rp)
# res = device.sendGA(*req.toGA())
@@ -183,10 +184,10 @@ def test_rk_with_allowlist_of_different_rp(resetdevice):
"""
rk_rp = {"id": "rk-cred.org", "name": "Example"}
rk_res = resetdevice.doMC(rp = rk_rp, rk=True)['res'].attestation_object
rk_res = resetdevice.MC(rp = rk_rp, options={"rk":True})['res']
server_rp = {"id": "server-cred.com", "name": "Example"}
server_res = resetdevice.doMC(rp = server_rp, rk=True)['res'].attestation_object
server_res = resetdevice.MC(rp = server_rp, options={"rk":True})['res']
allow_list_with_different_rp_cred = [
{
@@ -197,7 +198,7 @@ def test_rk_with_allowlist_of_different_rp(resetdevice):
with pytest.raises(CtapError) as e:
res = resetdevice.doGA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred)
res = resetdevice.GA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred)
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
@@ -208,10 +209,10 @@ def test_same_userId_overwrites_rk(resetdevice):
rp = {"id": "overwrite.org", "name": "Example"}
user = generate_random_user()
mc_res1 = resetdevice.doMC(rp = rp, rk=True, user = user)
mc_res1 = resetdevice.MC(rp = rp, options={"rk":True}, user = user)
# Should overwrite the first credential.
mc_res2 = resetdevice.doMC(rp = rp, rk=True, user = user)
mc_res2 = resetdevice.MC(rp = rp, options={"rk":True}, user = user)
ga_res = resetdevice.GA(rp_id=rp['id'])['res']
@@ -227,7 +228,7 @@ def test_larger_icon_than_128(device):
user = generate_random_user()
user['icon'] = 'https://www.w3.org/TR/webauthn/?icon=' + ("A" * 128)
device.doMC(rp = rp, rk=True, user = user)
device.MC(rp = rp, options={"rk":True}, user = user)
def test_returned_credential(device):

View File

@@ -21,6 +21,7 @@
import pytest
from fido2.ctap import CtapError
from fido2.ctap2.pin import PinProtocolV2, ClientPin
from fido2.utils import websafe_decode
from utils import verify
import os
@@ -46,22 +47,24 @@ def GACredBlob(device, MCCredBlob):
@pytest.fixture(scope="function")
def MCLBK(device):
res = device.doMC(
mc = device.doMC(
rk=True,
extensions={'largeBlob':{'support':'required'}}
)['res']
return res
)
res = mc['res']
ext = mc['client_extension_results']
return {'res': res, 'ext': ext}
@pytest.fixture(scope="function")
def GALBRead(device, MCLBK):
res = device.doGA(
allow_list=[
{"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
{"id": MCLBK['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
],extensions={'largeBlob':{'read': True}}
)
assertions = res['res'].get_assertions()
for a in assertions:
verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
verify(MCLBK['res'].attestation_object, a, res['req']['client_data'].hash)
return res['res']
@pytest.fixture(scope="function")
@@ -70,18 +73,19 @@ def GALBReadLBK(GALBRead):
@pytest.fixture(scope="function")
def GALBReadLB(GALBRead):
print(GALBRead.get_response(0))
return GALBRead.get_response(0)
@pytest.fixture(scope="function")
def GALBWrite(device, MCLBK):
res = device.doGA(
allow_list=[
{"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
{"id": MCLBK['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
],extensions={'largeBlob':{'write': LARGE_BLOB}}
)
assertions = res['res'].get_assertions()
for a in assertions:
verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
verify(MCLBK['res'].attestation_object, a, res['req']['client_data'].hash)
return res['res'].get_response(0)
def test_supports_credblob(info):
@@ -136,15 +140,17 @@ def test_supports_largeblobs(info):
assert info.max_large_blob is None or (info.max_large_blob > 1024)
def test_get_largeblobkey_mc(MCLBK):
assert 'supported' in MCLBK.extension_results
assert MCLBK.extension_results['supported'] is True
assert 'largeBlob' in MCLBK['ext']
assert 'supported' in MCLBK['ext']['largeBlob']
assert MCLBK['ext']['largeBlob']['supported'] is True
def test_get_largeblobkey_ga(GALBReadLBK):
assert GALBReadLBK.large_blob_key is not None
def test_get_largeblob_rw(GALBWrite, GALBReadLB):
assert 'written' in GALBWrite.extension_results
assert GALBWrite.extension_results['written'] is True
assert 'largeBlob' in GALBWrite.client_extension_results
assert 'written' in GALBWrite.client_extension_results['largeBlob']
assert GALBWrite.client_extension_results['largeBlob']['written'] is True
assert 'blob' in GALBReadLB.extension_results
assert GALBReadLB.extension_results['blob'] == LARGE_BLOB
assert 'blob' in GALBReadLB.client_extension_results['largeBlob']
assert websafe_decode(GALBReadLB.client_extension_results['largeBlob']['blob']) == LARGE_BLOB

View File

@@ -22,6 +22,7 @@ import pytest
from fido2.ctap2.extensions import CredProtectExtension
from fido2.webauthn import UserVerificationRequirement
from fido2.ctap import CtapError
from utils import generate_random_user
class CredProtect:
UserVerificationOptional = 1
@@ -30,140 +31,139 @@ class CredProtect:
@pytest.fixture(scope="class")
def MCCredProtectOptional(resetdevice):
res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL})['res'].attestation_object
res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL})['res'].attestation_object
return res
@pytest.fixture(scope="class")
def MCCredProtectOptionalList(resetdevice):
res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST})['res'].attestation_object
res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST})['res'].attestation_object
return res
@pytest.fixture(scope="class")
def MCCredProtectRequired(resetdevice):
res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object
res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object
return res
class TestCredProtect(object):
def test_credprotect_make_credential_1(self, MCCredProtectOptional):
assert MCCredProtectOptional.auth_data.extensions
assert "credProtect" in MCCredProtectOptional.auth_data.extensions
assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1
def test_credprotect_make_credential_1(MCCredProtectOptional):
assert MCCredProtectOptional.auth_data.extensions
assert "credProtect" in MCCredProtectOptional.auth_data.extensions
assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1
def test_credprotect_make_credential_2(self, MCCredProtectOptionalList):
assert MCCredProtectOptionalList.auth_data.extensions
assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions
assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2
def test_credprotect_make_credential_2(MCCredProtectOptionalList):
assert MCCredProtectOptionalList.auth_data.extensions
assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions
assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2
def test_credprotect_make_credential_3(self, MCCredProtectRequired):
assert MCCredProtectRequired.auth_data.extensions
assert "credProtect" in MCCredProtectRequired.auth_data.extensions
assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3
def test_credprotect_make_credential_3(MCCredProtectRequired):
assert MCCredProtectRequired.auth_data.extensions
assert "credProtect" in MCCredProtectRequired.auth_data.extensions
assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3
def test_credprotect_optional_excluded(self, device, MCCredProtectOptional):
""" CredProtectOptional Cred should be visible to be excluded with no UV """
exclude_list = [
{
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
def test_credprotect_optional_excluded(device, MCCredProtectOptional):
""" CredProtectOptional Cred should be visible to be excluded with no UV """
exclude_list = [
{
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
with pytest.raises(CtapError) as e:
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)
with pytest.raises(CtapError) as e:
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
def test_credprotect_optional_list_excluded(self, device, MCCredProtectOptionalList):
""" CredProtectOptionalList Cred should be visible to be excluded with no UV """
exclude_list = [
{
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
def test_credprotect_optional_list_excluded(device, MCCredProtectOptionalList):
""" CredProtectOptionalList Cred should be visible to be excluded with no UV """
exclude_list = [
{
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
with pytest.raises(CtapError) as e:
device.MC(options={'rk': True}, extensions={'credProtect': CredProtect.UserVerificationOptionalWithCredentialId}, exclude_list=exclude_list)
with pytest.raises(CtapError) as e:
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST}, exclude_list=exclude_list)
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
def test_credprotect_required_not_excluded_with_no_uv(self, device, MCCredProtectRequired):
""" CredProtectRequired Cred should NOT be visible to be excluded with no UV """
exclude_list = [
{
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
def test_credprotect_required_not_excluded_with_no_uv(device, MCCredProtectRequired):
""" CredProtectRequired Cred should NOT be visible to be excluded with no UV """
exclude_list = [
{
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
# works
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)
# works
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)
def test_credprotect_optional_works_with_no_allowList_no_uv(self, device, MCCredProtectOptional):
def test_credprotect_optional_works_with_no_allowList_no_uv(device, MCCredProtectOptional):
# works
res = device.doGA()['res'].get_assertions()[0]
# works
res = device.doGA()['res'].get_assertions()[0]
# If there's only one credential, this is None
assert res.number_of_credentials == None
# If there's only one credential, this is None
assert res.number_of_credentials == None
def test_credprotect_optional_and_list_works_no_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired):
allow_list = [
{
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
]
# works
res1 = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()[0]
assert res1.number_of_credentials in (None, 2)
def test_credprotect_optional_and_list_works_no_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired):
allow_list = [
{
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
]
# works
res1 = device.doGA(allow_list=allow_list)['res'].get_assertions()[0]
assert res1.number_of_credentials in (None, 2)
results = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()
results = device.doGA(allow_list=allow_list)['res'].get_assertions()
# the required credProtect is not returned.
for res in results:
assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:]
# the required credProtect is not returned.
for res in results:
assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:]
def test_hmac_secret_and_credProtect_make_credential(self, resetdevice, MCCredProtectOptional):
def test_hmac_secret_and_credProtect_make_credential(resetdevice, MCCredProtectOptional
):
res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object
res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object
for ext in ["credProtect", "hmac-secret"]:
assert res.auth_data.extensions
assert ext in res.auth_data.extensions
assert res.auth_data.extensions[ext] == True
for ext in ["credProtect", "hmac-secret"]:
assert res.auth_data.extensions
assert ext in res.auth_data.extensions
assert res.auth_data.extensions[ext] == True
class TestCredProtectUv:
def test_credprotect_all_with_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin):
allow_list = [
{
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
]
pin = "12345678"
def test_credprotect_all_with_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin):
allow_list = [
{
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
{
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
"type": "public-key",
},
]
client_pin.set_pin(pin)
pin = "12345678"
res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]
client_pin.set_pin(pin)
res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]
assert res1.number_of_credentials in (None, 3)
assert res1.number_of_credentials in (None, 3)

View File

@@ -24,6 +24,7 @@ from fido2.ctap2.extensions import HmacSecretExtension
from fido2.utils import hmac_sha256
from fido2.ctap2.pin import PinProtocolV2
from fido2.webauthn import UserVerificationRequirement
from fido2.client import ClientError
from utils import *
salt1 = b"\xa5" * 32
@@ -38,10 +39,6 @@ def MCHmacSecret(resetdevice):
res = resetdevice.doMC(extensions={"hmacCreateSecret": True},rk=True)
return res['res'].attestation_object
@pytest.fixture(scope="class")
def hmac(resetdevice):
return HmacSecretExtension(resetdevice.client()._backend.ctap2, pin_protocol=PinProtocolV2())
def test_hmac_secret_make_credential(MCHmacSecret):
assert MCHmacSecret.auth_data.extensions
assert "hmac-secret" in MCHmacSecret.auth_data.extensions
@@ -55,51 +52,51 @@ def test_fake_extension(device):
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
def test_hmac_secret_entropy(device, MCHmacSecret, hmac, salts
def test_hmac_secret_entropy(device, MCHmacSecret, salts
):
hout = {'salt1':salts[0]}
if (len(salts) > 1):
hout['salt2'] = salts[1]
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
ext = auth.extension_results
ext = auth.client_extension_results
assert ext
assert "hmacGetSecret" in ext
assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
assert len(auth.response.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
#print(shannon_entropy(auth.authenticator_data.extensions['hmac-secret']))
#print(shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']))
if len(salts) == 1:
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 4.5
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5
assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 4.5
assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
if len(salts) == 2:
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 5.4
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5
assert shannon_entropy(ext["hmacGetSecret"]['output2']) > 4.5
assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 5.4
assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
assert shannon_entropy(ext.hmac_get_secret.output2) > 4.5
def get_output(device, MCHmacSecret, hmac, salts):
def get_output(device, MCHmacSecret, salts):
hout = {'salt1':salts[0]}
if (len(salts) > 1):
hout['salt2'] = salts[1]
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
ext = auth.extension_results
ext = auth.client_extension_results
assert ext
assert "hmacGetSecret" in ext
assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
assert len(auth.response.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
if len(salts) == 2:
return ext["hmacGetSecret"]['output1'], ext["hmacGetSecret"]['output2']
return ext.hmac_get_secret.output1, ext.hmac_get_secret.output2
else:
return ext["hmacGetSecret"]['output1']
return ext.hmac_get_secret.output1
def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
output1 = get_output(device, MCHmacSecret, hmac, (salt1,))
def test_hmac_secret_sanity(device, MCHmacSecret):
output1 = get_output(device, MCHmacSecret, (salt1,))
output12 = get_output(
device, MCHmacSecret, hmac, (salt1, salt2)
device, MCHmacSecret, (salt1, salt2)
)
output21 = get_output(
device, MCHmacSecret, hmac, (salt2, salt1)
device, MCHmacSecret, (salt2, salt1)
)
assert output12[0] == output1
@@ -107,60 +104,60 @@ def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
assert output21[0] == output12[1]
assert output12[0] != output12[1]
def test_missing_keyAgreement(device, hmac):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
def test_missing_keyAgreement(device):
with pytest.raises(CtapError):
device.GA(extensions={"hmac-secret": {2: hout[2], 3: hout[3]}})
device.GA(extensions={"hmac-secret": {2: b'1234', 3: b'1234'}})
def test_missing_saltAuth(device, hmac):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
def test_missing_saltAuth(device):
with pytest.raises(CtapError) as e:
device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2]}})
device.GA(extensions={"hmac-secret": {2: b'1234'}})
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_missing_saltEnc(device, hmac):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
def test_missing_saltEnc(device,):
with pytest.raises(CtapError) as e:
device.GA(extensions={"hmac-secret": {1: hout[1], 3: hout[3]}})
device.GA(extensions={"hmac-secret": { 3: b'1234'}})
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_bad_auth(device, hmac, MCHmacSecret):
def test_bad_auth(device, MCHmacSecret):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
bad_auth = list(hout[3][:])
bad_auth[len(bad_auth) // 2] = bad_auth[len(bad_auth) // 2] ^ 1
bad_auth = bytes(bad_auth)
key_agreement = {
1: 2,
3: -25, # Per the spec, "although this is NOT the algorithm actually used"
-1: 1,
-2: b'\x00'*32,
-3: b'\x00'*32,
}
with pytest.raises(CtapError) as e:
device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2], 3: bad_auth, 4: 2}})
device.GA(extensions={"hmac-secret": {1: key_agreement, 2: b'\x00'*80, 3: b'\x00'*32, 4: 2}})
assert e.value.code == CtapError.ERR.EXTENSION_FIRST
@pytest.mark.parametrize("salts", [(salt4,), (salt4, salt5)])
def test_invalid_salt_length(device, hmac, salts):
with pytest.raises(ValueError) as e:
def test_invalid_salt_length(device, salts):
with pytest.raises((CtapError,ClientError)) as e:
if (len(salts) == 2):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
hout = {"salt1":salts[0],"salt2":salts[1]}
else:
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
hout = {"salt1":salts[0]}
device.doGA(extensions={"hmacGetSecret": hout})
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
def test_get_next_assertion_has_extension(
device, hmac, salts
device, salts
):
""" Check that get_next_assertion properly returns extension information for multiple accounts. """
if (len(salts) == 2):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
hout = {"salt1":salts[0],"salt2":salts[1]}
else:
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
hout = {"salt1":salts[0]}
accounts = 3
regs = []
auths = []
rp = {"id": f"example_salts_{len(salts)}.org", "name": "ExampleRP_2"}
rp = {"id": f"example.com", "name": "ExampleRP_2"}
fixed_users = [generate_random_user() for _ in range(accounts)]
for i in range(accounts):
res = device.doMC(extensions={"hmacCreateSecret": True},
@@ -183,21 +180,19 @@ def test_get_next_assertion_has_extension(
assert "hmac-secret" in ext
assert isinstance(ext["hmac-secret"], bytes)
assert len(ext["hmac-secret"]) == len(salts) * 32 + 16
key = hmac.process_get_output(x)
def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac):
def test_hmac_secret_different_with_uv(device, MCHmacSecret):
salts = [salt1]
if (len(salts) == 2):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
hout = {"salt1":salts[0],"salt2":salts[1]}
else:
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
hout = {"salt1":salts[0]}
auth_no_uv = device.GA(extensions={"hmac-secret": hout})['res']
assert (auth_no_uv.auth_data.flags & (1 << 2)) == 0
auth_no_uv = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
assert (auth_no_uv.response.authenticator_data.flags & (1 << 2)) == 0
ext_no_uv = auth_no_uv.auth_data.extensions
ext_no_uv = auth_no_uv.response.authenticator_data.extensions
assert ext_no_uv
assert "hmac-secret" in ext_no_uv
assert isinstance(ext_no_uv["hmac-secret"], bytes)
@@ -209,11 +204,11 @@ def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac):
hout['salt2'] = salts[1]
auth_uv = device.doGA(extensions={"hmacGetSecret": hout}, user_verification=UserVerificationRequirement.REQUIRED)['res'].get_response(0)
assert auth_uv.authenticator_data.flags & (1 << 2)
ext_uv = auth_uv.extension_results
assert auth_uv.response.authenticator_data.flags & (1 << 2)
ext_uv = auth_uv.client_extension_results
assert ext_uv
assert "hmacGetSecret" in ext_uv
assert len(ext_uv["hmacGetSecret"]) == len(salts)
assert len([p for p in ext_uv["hmacGetSecret"] if len(ext_uv["hmacGetSecret"][p]) > 0]) == len(salts)
# Now see if the hmac-secrets are different
assert ext_no_uv["hmac-secret"][:32] != ext_uv["hmacGetSecret"]['output1']

View File

@@ -29,7 +29,7 @@ def test_authenticate_ctap1_through_ctap2(device, RegRes):
res = device.doGA(ctap1=False, allow_list=[
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
])
assert res['res'].get_response(0).credential_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id
assert res['res'].get_response(0).raw_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id
# Test FIDO2 register works with U2F auth

View File

@@ -3,6 +3,6 @@
/usr/sbin/pcscd &
sleep 2
rm -f memory.flash
cp -R tests/docker/fido2/* /usr/local/lib/python3.9/dist-packages/fido2/hid
cp -R tests/docker/fido2/* /usr/local/lib/python3.11/dist-packages/fido2/hid
./build_in_docker/pico_fido > /dev/null &
pytest tests

View File

@@ -24,7 +24,7 @@ import argparse
import platform
from binascii import hexlify
from threading import Event
from typing import Mapping, Any, Optional, Callable
from typing import List, Mapping, Any, Optional, Callable
import struct
import urllib.request
import json
@@ -73,21 +73,21 @@ def get_pki_data(url, data=None, method='GET'):
return j
class VendorConfig(Config):
class PARAM(IntEnum):
VENDOR_COMMAND_ID = 0x01
VENDOR_AUT_CT = 0x02
VENDOR_PARAM = 0x02
VENDOR_PARAM_BYTESTRING = 0x02
VENDOR_PARAM_INT = 0x03
VENDOR_PARAM_TEXTSTRING = 0x04
class CMD(IntEnum):
CONFIG_AUT_ENABLE = 0x03e43f56b34285e2
CONFIG_AUT_DISABLE = 0x1831a40f04a25ed9
CONFIG_VENDOR_PROTOTYPE = 0x7f
CONFIG_VENDOR_PHY = 0x1b
CONFIG_EA_UPLOAD = 0x66f2a674c29a8dcf
CONFIG_PHY_VIDPID = 0x6fcb19b0cbe3acfa
CONFIG_PHY_OPTS = 0x969f3b09eceb805f
CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948
CONFIG_PHY_LED_BTNESS = 0x76a85945985d02fd
CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948
CONFIG_PHY_OPTS = 0x269f3b09eceb805f
CONFIG_PIN_POLICY = 0x6c07d70fe96c3897
class RESP(IntEnum):
KEY_AGREEMENT = 0x01
@@ -97,16 +97,16 @@ class VendorConfig(Config):
def enable_device_aut(self, ct):
self._call(
VendorConfig.CMD.CONFIG_VENDOR_PROTOTYPE,
Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_ENABLE,
VendorConfig.PARAM.VENDOR_AUT_CT: ct
VendorConfig.PARAM.VENDOR_PARAM_BYTESTRING: ct
},
)
def disable_device_aut(self):
self._call(
VendorConfig.CMD.CONFIG_VENDOR_PROTOTYPE,
Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_DISABLE
},
@@ -114,40 +114,63 @@ class VendorConfig(Config):
def vidpid(self, vid, pid):
self._call(
VendorConfig.CMD.CONFIG_VENDOR_PHY,
Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_VIDPID,
VendorConfig.PARAM.VENDOR_PARAM: (vid & 0xFFFF) << 16 | pid
VendorConfig.PARAM.VENDOR_PARAM_INT: (vid & 0xFFFF) << 16 | pid
},
)
def led_gpio(self, gpio):
self._call(
VendorConfig.CMD.CONFIG_VENDOR_PHY,
Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_GPIO,
VendorConfig.PARAM.VENDOR_PARAM: gpio
VendorConfig.PARAM.VENDOR_PARAM_INT: gpio
},
)
def led_brightness(self, brightness):
self._call(
VendorConfig.CMD.CONFIG_VENDOR_PHY,
Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_BTNESS,
VendorConfig.PARAM.VENDOR_PARAM: brightness
VendorConfig.PARAM.VENDOR_PARAM_INT: brightness
},
)
def phy_opts(self, opts):
self._call(
VendorConfig.CMD.CONFIG_VENDOR_PHY,
Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_OPTS,
VendorConfig.PARAM.VENDOR_PARAM: opts
VendorConfig.PARAM.VENDOR_PARAM_INT: opts
},
)
def upload_ea(self, der):
self._call(
Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_EA_UPLOAD,
VendorConfig.PARAM.VENDOR_PARAM_BYTESTRING: der
},
)
def pin_policy(self, url: bytes|str = None, policy: int = None):
if (url is not None or policy is not None):
params = { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PIN_POLICY }
if (url is not None):
if (isinstance(url, str)):
url = url.encode()
params[VendorConfig.PARAM.VENDOR_PARAM_BYTESTRING] = url
if (policy is not None):
params[VendorConfig.PARAM.VENDOR_PARAM_INT] = policy
self._call(
Config.CMD.VENDOR_PROTOTYPE,
params,
)
class Ctap2Vendor(Ctap2):
def __init__(self, device: CtapDevice, strict_cbor: bool = True):
super().__init__(device=device, strict_cbor=strict_cbor)
@@ -245,7 +268,6 @@ class Vendor:
DISABLE = 0x02
KEY_AGREEMENT = 0x01
EA_CSR = 0x01
EA_UPLOAD = 0x02
class RESP(IntEnum):
PARAM = 0x01
@@ -433,13 +455,7 @@ class Vendor:
)[Vendor.RESP.PARAM]
def upload_ea(self, der):
self._call(
Vendor.CMD.VENDOR_EA,
Vendor.SUBCMD.EA_UPLOAD,
{
Vendor.PARAM.PARAM: der
}
)
self.vcfg.upload_ea(der)
def vidpid(self, vid, pid):
return self.vcfg.vidpid(vid, pid)
@@ -483,6 +499,21 @@ class Vendor:
)
return { 'free': resp[1], 'used': resp[2], 'total': resp[3], 'files': resp[4], 'size': resp[5] }
def enable_enterprise_attestation(self):
self.vcfg.enable_enterprise_attestation()
def set_min_pin_length(self, length, rpids: list[str] = None, url=None):
params = {
Config.PARAM.NEW_MIN_PIN_LENGTH: length,
Config.PARAM.MIN_PIN_LENGTH_RPIDS: rpids if rpids else None,
}
self.vcfg.set_min_pin_length(
min_pin_length=length,
rp_ids=rpids if rpids else None,
)
self.vcfg.pin_policy(url=url.encode() if url else None)
def parse_args():
parser = argparse.ArgumentParser()
subparser = parser.add_subparsers(title="commands", dest="command")
@@ -495,7 +526,7 @@ def parse_args():
parser_backup.add_argument('filename', help='File to save or load the backup.')
parser_attestation = subparser.add_parser('attestation', help='Manages Enterprise Attestation')
parser_attestation.add_argument('subcommand', choices=['csr'])
parser_attestation.add_argument('subcommand', choices=['csr','enable'])
parser_attestation.add_argument('--filename', help='Uploads the certificate filename to the device as enterprise attestation certificate. If not provided, it will generate an enterprise attestation certificate automatically.')
parser_phy = subparser.add_parser('phy', help='Set PHY options.')
@@ -513,10 +544,15 @@ def parse_args():
parser_mem = subparser.add_parser('memory', help='Get current memory usage.')
parser_pin_policy = subparser.add_parser('pin_policy', help='Manage PIN policy.')
parser_pin_policy.add_argument('length', type=int, help='Minimum PIN length (4-63).')
parser_pin_policy.add_argument('--rpids', help='Comma separated list of Relying Party IDs that have authorization to receive minimum PIN length.')
parser_pin_policy.add_argument('--url', help='URL where the user can consult PIN policy.')
args = parser.parse_args()
return args
def secure(vdr, args):
def secure(vdr: Vendor, args):
if (args.subcommand == 'enable'):
vdr.enable_device_aut()
elif (args.subcommand == 'unlock'):
@@ -524,13 +560,13 @@ def secure(vdr, args):
elif (args.subcommand == 'disable'):
vdr.disable_device_aut()
def backup(vdr, args):
def backup(vdr: Vendor, args):
if (args.subcommand == 'save'):
vdr.backup_save(args.filename)
elif (args.subcommand == 'load'):
vdr.backup_load(args.filename)
def attestation(vdr, args):
def attestation(vdr: Vendor, args):
if (args.subcommand == 'csr'):
if (args.filename is None):
csr = x509.load_der_x509_csr(vdr.csr())
@@ -545,8 +581,10 @@ def attestation(vdr, args):
except ValueError:
cert = x509.load_pem_x509_certificate(dataf)
vdr.upload_ea(cert.public_bytes(Encoding.DER))
elif (args.subcommand == 'enable'):
vdr.enable_enterprise_attestation()
def phy(vdr, args):
def phy(vdr: Vendor, args):
val = args.value if 'value' in args else None
if (val):
if (args.subcommand == 'vidpid'):
@@ -570,7 +608,7 @@ def phy(vdr, args):
else:
print('Command executed successfully. Please, restart your Pico Key.')
def memory(vdr, args):
def memory(vdr: Vendor, args):
mem = vdr.memory()
print(f'Memory usage:')
print(f'\tFree: {mem["free"]/1024:.2f} kilobytes ({mem["free"]*100/mem["total"]:.2f}%)')
@@ -579,6 +617,14 @@ def memory(vdr, args):
print(f'\tFlash size: {mem["size"]/1024:.2f} kilobytes')
print(f'\tFiles: {mem["files"]}')
def pin_policy(vdr: Vendor, args):
rpids = None
if (args.rpids):
rpids = args.rpids.split(',')
vdr.set_min_pin_length(args.length, rpids, args.url)
def main(args):
print('Pico Fido Tool v1.10')
print('Author: Pol Henarejos')
@@ -602,6 +648,9 @@ def main(args):
phy(vdr, args)
elif (args.command == 'memory'):
memory(vdr, args)
elif (args.command == 'pin_policy'):
pin_policy(vdr, args)
def run():
args = parse_args()