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_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD 17)
if(ENABLE_EMULATION)
else()
pico_sdk_init()
endif()
add_executable(pico_fido) add_executable(pico_fido)
endif() endif()
@@ -156,6 +151,12 @@ if(ENABLE_EMULATION)
target_link_options(pico_fido PUBLIC target_link_options(pico_fido PUBLIC
-Wl,-dead_strip -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() else()
target_link_options(pico_fido PUBLIC target_link_options(pico_fido PUBLIC
-Wl,--gc-sections -Wl,--gc-sections

View File

@@ -16,7 +16,7 @@
*/ */
#include "pico_keys.h" #include "pico_keys.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) #if defined(PICO_PLATFORM)
#include "pico/stdlib.h" #include "pico/stdlib.h"
#endif #endif
#include "hid/ctap_hid.h" #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; return CTAP1_ERR_INVALID_CMD;
} }
void cbor_thread(void) { void *cbor_thread(void *arg) {
(void)arg;
card_init_core1(); card_init_core1();
while (1) { while (1) {
uint32_t m; uint32_t m;
@@ -115,17 +116,17 @@ void cbor_thread(void) {
if (m == EV_EXIT) { if (m == EV_EXIT) {
break; 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) { if (apdu.sw == 0) {
DEBUG_DATA(res_APDU, res_APDU_size); DEBUG_DATA(res_APDU, res_APDU_size);
} }
else { else {
if (apdu.sw >= CTAP1_ERR_INVALID_CHANNEL) { if (apdu.sw >= CTAP1_ERR_INVALID_CHANNEL) {
res_APDU[-1] = apdu.sw; res_APDU[-1] = (uint8_t)apdu.sw;
apdu.sw = 0; apdu.sw = 0;
} }
else { else {
res_APDU[0] = apdu.sw; res_APDU[0] = (uint8_t)apdu.sw;
} }
} }
@@ -137,6 +138,7 @@ void cbor_thread(void) {
#ifdef ESP_PLATFORM #ifdef ESP_PLATFORM
vTaskDelete(NULL); vTaskDelete(NULL);
#endif #endif
return NULL;
} }
int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) { 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) { else if (key->grp.id == MBEDTLS_ECP_DP_ED25519) {
alg = FIDO2_ALG_EDDSA; alg = FIDO2_ALG_EDDSA;
} }
else if (key->grp.id == MBEDTLS_ECP_DP_ED448) {
alg = FIDO2_ALG_ED448;
}
#endif #endif
return COSE_key_params(crv, alg, &key->grp, &key->Q, mapEncoderParent, mapEncoder); 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/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "pico_keys.h"
#ifndef ESP_PLATFORM #ifndef ESP_PLATFORM
#include "common.h" #include "common.h"
#else #else
@@ -27,7 +28,7 @@
#include "cbor.h" #include "cbor.h"
#include "ctap.h" #include "ctap.h"
#include "ctap2_cbor.h" #include "ctap2_cbor.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) #if defined(PICO_PLATFORM)
#include "bsp/board.h" #include "bsp/board.h"
#endif #endif
#include "hid/ctap_hid.h" #include "hid/ctap_hid.h"
@@ -35,7 +36,6 @@
#include "files.h" #include "files.h"
#include "random.h" #include "random.h"
#include "crypto_utils.h" #include "crypto_utils.h"
#include "pico_keys.h"
#include "apdu.h" #include "apdu.h"
#include "kek.h" #include "kek.h"
@@ -44,6 +44,7 @@ uint32_t max_usage_time_period = 600 * 1000;
bool needs_power_cycle = false; bool needs_power_cycle = false;
static mbedtls_ecdh_context hkey; static mbedtls_ecdh_context hkey;
static bool hkey_init = false; static bool hkey_init = false;
extern int encrypt_keydev_f1(const uint8_t keydev[32]);
int beginUsingPinUvAuthToken(bool userIsPresent) { int beginUsingPinUvAuthToken(bool userIsPresent) {
paut.user_present = userIsPresent; paut.user_present = userIsPresent;
@@ -105,11 +106,7 @@ int regenerate() {
mbedtls_ecdh_init(&hkey); mbedtls_ecdh_init(&hkey);
hkey_init = true; hkey_init = true;
mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1); mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1);
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_gen, NULL);
&hkey.ctx.mbed_ecdh.d,
&hkey.ctx.mbed_ecdh.Q,
random_gen,
NULL);
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1); mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
if (ret != 0) { if (ret != 0) {
return ret; return ret;
@@ -125,34 +122,15 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
return ret; return ret;
} }
if (protocol == 1) { if (protocol == 1) {
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), buf, sizeof(buf), sharedSecret);
buf,
sizeof(buf),
sharedSecret);
} }
else if (protocol == 2) { else if (protocol == 2) {
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
ret = mbedtls_hkdf(md_info, ret = mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 HMAC key", 14, sharedSecret, 32);
NULL,
0,
buf,
sizeof(buf),
(uint8_t *) "CTAP2 HMAC key",
14,
sharedSecret,
32);
if (ret != 0) { if (ret != 0) {
return ret; return ret;
} }
return mbedtls_hkdf(md_info, return mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 AES key", 13, sharedSecret + 32, 32);
NULL,
0,
buf,
sizeof(buf),
(uint8_t *) "CTAP2 AES key",
13,
sharedSecret + 32,
32);
} }
return -1; 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) { int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret) {
mbedtls_mpi z; mbedtls_mpi z;
mbedtls_mpi_init(&z); mbedtls_mpi_init(&z);
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, &z, Q, &hkey.ctx.mbed_ecdh.d, random_gen, NULL);
&z,
Q,
&hkey.ctx.mbed_ecdh.d,
random_gen,
NULL);
ret = kdf(protocol, &z, sharedSecret); ret = kdf(protocol, &z, sharedSecret);
mbedtls_mpi_free(&z); mbedtls_mpi_free(&z);
return ret; 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]; uint8_t t[32];
random_gen(NULL, t, sizeof(t)); 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.permissions = 0;
paut.data = file_get_data(ef_authtoken); paut.data = file_get_data(ef_authtoken);
paut.len = file_get_size(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; return 0;
} }
@@ -210,11 +200,7 @@ int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in
return -1; return -1;
} }
int authenticate(uint8_t protocol, int authenticate(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign) {
const uint8_t *key,
const uint8_t *data,
size_t len,
uint8_t *sign) {
uint8_t hmac[32]; uint8_t hmac[32];
int ret = int ret =
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac); 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; return ret;
} }
if (protocol == 1) { if (protocol == 1) {
return memcmp(sign, hmac, 16); return ct_memcmp(sign, hmac, 16);
} }
else if (protocol == 2) { else if (protocol == 2) {
return memcmp(sign, hmac, 32); return ct_memcmp(sign, hmac, 32);
} }
return -1; return -1;
} }
@@ -280,17 +266,15 @@ int pinUvAuthTokenUsageTimerObserver() {
return 0; return 0;
} }
int check_mkek_encrypted(const uint8_t *dhash) { int check_keydev_encrypted(const uint8_t pin_token[32]) {
if (file_get_size(ef_mkek) == MKEK_IV_SIZE + MKEK_KEY_SIZE) { if (file_get_data(ef_keydev) && *file_get_data(ef_keydev) == 0x01) {
hash_multi(dhash, 16, session_pin); // Only for storing MKEK uint8_t tmp_keydev[61];
uint8_t mkek[MKEK_SIZE] = {0}; tmp_keydev[0] = 0x02; // Change format to encrypted
memcpy(mkek, file_get_data(ef_mkek), MKEK_IV_SIZE + MKEK_KEY_SIZE); encrypt_with_aad(pin_token, file_get_data(ef_keydev) + 1, 32, tmp_keydev + 1);
int ret = store_mkek(mkek); DEBUG_DATA(tmp_keydev, sizeof(tmp_keydev));
mbedtls_platform_zeroize(mkek, sizeof(mkek)); file_put_data(ef_keydev, tmp_keydev, sizeof(tmp_keydev));
mbedtls_platform_zeroize(session_pin, sizeof(session_pin)); mbedtls_platform_zeroize(tmp_keydev, sizeof(tmp_keydev));
if (ret != PICOKEY_OK) { low_flash_available();
return CTAP2_ERR_PIN_AUTH_INVALID;
}
} }
return PICOKEY_OK; return PICOKEY_OK;
} }
@@ -305,11 +289,11 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CborEncoder encoder, mapEncoder; CborEncoder encoder, mapEncoder;
CborValue map; CborValue map;
CborError error = CborNoError; CborError error = CborNoError;
CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 }, CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 }, kay = { 0 };
kay = { 0 };
CborCharString rpId = { 0 }; CborCharString rpId = { 0 };
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map)); CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
uint64_t val_c = 1; uint64_t val_c = 1;
uint8_t keydev[32] = {0};
if (hkey_init == false) { if (hkey_init == false) {
initialize(); initialize();
} }
@@ -431,15 +415,17 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if (pin_len < minPin) { if (pin_len < minPin) {
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION); 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[0] = MAX_PIN_RETRIES;
hsh[1] = pin_len; hsh[1] = pin_len;
hsh[2] = 1; // New format indicator
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash); mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
double_hash_pin(dhash, 16, hsh + 2); pin_derive_verifier(dhash, 16, hsh + 3);
file_put_data(ef_pin, hsh, 2 + 32); file_put_data(ef_pin, hsh, sizeof(hsh));
low_flash_available(); 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) { if (ret != PICOKEY_OK) {
CBOR_ERROR(ret); CBOR_ERROR(ret);
} }
@@ -486,10 +472,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
} }
uint8_t pin_data[34]; uint8_t pin_data[35];
memcpy(pin_data, file_get_data(ef_pin), 34); memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
pin_data[0] -= 1; 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(); low_flash_available();
uint8_t retries = pin_data[0]; uint8_t retries = pin_data[0];
uint8_t paddedNewPin[64]; 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)); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
} }
uint8_t dhash[32]; uint8_t dhash[32], off = 3;
double_hash_pin(paddedNewPin, 16, dhash); if (file_get_size(ef_pin) == 34) {
if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) { 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(); regenerate();
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
if (retries == 0) { if (retries == 0) {
@@ -514,10 +507,28 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID); 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; pin_data[0] = MAX_PIN_RETRIES;
file_put_data(ef_pin, pin_data, sizeof(pin_data)); file_put_data(ef_pin, pin_data, sizeof(pin_data));
low_flash_available(); low_flash_available();
ret = check_keydev_encrypted(session_pin);
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
new_pin_mismatches = 0; new_pin_mismatches = 0;
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin); ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin);
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); 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) { if (pin_len < minPin) {
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION); CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
} }
uint8_t hsh[34];
hsh[0] = MAX_PIN_RETRIES; // New PIN is valid and verified
hsh[1] = pin_len; 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); mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
double_hash_pin(dhash, 16, hsh + 2); pin_derive_session(dhash, 16, session_pin);
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 && ret = check_keydev_encrypted(session_pin);
memcmp(hsh + 2, file_get_data(ef_pin) + 2, 32) == 0) { 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); CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
} }
file_put_data(ef_pin, pin_data, sizeof(pin_data));
uint8_t mkek[MKEK_SIZE] = {0}; mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
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(dhash, sizeof(dhash)); mbedtls_platform_zeroize(dhash, sizeof(dhash));
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) { 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)); 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(); low_flash_available();
resetPinUvAuthToken(); resetPinUvAuthToken();
resetPersistentPinUvAuthToken();
goto err; // No return goto err; // No return
} }
else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions 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 if ((permissions & CTAP_PERMISSION_BE)) { // Not supported yet
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION); 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)) { if (!file_has_data(ef_pin)) {
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET); 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)); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
} }
uint8_t pin_data[34]; uint8_t pin_data[35];
memcpy(pin_data, file_get_data(ef_pin), 34); memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
pin_data[0] -= 1; 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(); low_flash_available();
uint8_t retries = pin_data[0]; uint8_t retries = pin_data[0];
uint8_t paddedNewPin[64], poff = ((uint8_t)pinUvAuthProtocol - 1) * IV_SIZE; 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)); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
} }
uint8_t dhash[32]; uint8_t dhash[32], off = 3;
double_hash_pin(paddedNewPin, 16, dhash); if (file_get_size(ef_pin) == 34) {
if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) { 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(); regenerate();
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret)); mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
mbedtls_platform_zeroize(dhash, sizeof(dhash));
if (retries == 0) { if (retries == 0) {
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED); 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); 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) { if (ret != PICOKEY_OK) {
CBOR_ERROR(ret); CBOR_ERROR(ret);
} }
hash_multi(paddedNewPin, 16, session_pin);
pin_data[0] = MAX_PIN_RETRIES; pin_data[0] = MAX_PIN_RETRIES;
new_pin_mismatches = 0; new_pin_mismatches = 0;
file_put_data(ef_pin, pin_data, sizeof(pin_data)); file_put_data(ef_pin, pin_data, sizeof(pin_data));
mbedtls_platform_zeroize(pin_data, sizeof(pin_data)); mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
mbedtls_platform_zeroize(dhash, sizeof(dhash));
low_flash_available(); low_flash_available();
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF); 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) { if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID); CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
} }
resetPinUvAuthToken(); uint8_t pinUvAuthToken_enc[32 + IV_SIZE], *pdata = NULL;
beginUsingPinUvAuthToken(false); if (permissions & CTAP_PERMISSION_PCMR) {
if (subcommand == 0x05) { ppaut.permissions = CTAP_PERMISSION_PCMR;
permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA; pdata = ppaut.data;
}
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 { 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, pdata, 32, pinUvAuthToken_enc);
encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1)); CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32 + poff)); 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(kax);
CBOR_FREE_BYTE_STRING(kay); CBOR_FREE_BYTE_STRING(kay);
CBOR_FREE_BYTE_STRING(rpId); CBOR_FREE_BYTE_STRING(rpId);
mbedtls_platform_zeroize(keydev, sizeof(keydev));
if (error != CborNoError) { if (error != CborNoError) {
if (error == CborErrorImproperValue) { if (error == CborErrorImproperValue) {
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;

View File

@@ -15,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "pico_keys.h"
#include "ctap2_cbor.h" #include "ctap2_cbor.h"
#include "fido.h" #include "fido.h"
#include "ctap.h" #include "ctap.h"
@@ -22,7 +23,6 @@
#include "files.h" #include "files.h"
#include "apdu.h" #include "apdu.h"
#include "credential.h" #include "credential.h"
#include "pico_keys.h"
#include "random.h" #include "random.h"
#include "mbedtls/ecdh.h" #include "mbedtls/ecdh.h"
#include "mbedtls/chachapoly.h" #include "mbedtls/chachapoly.h"
@@ -31,14 +31,16 @@
extern uint8_t keydev_dec[32]; extern uint8_t keydev_dec[32];
extern bool has_keydev_dec; extern bool has_keydev_dec;
extern void resetPersistentPinUvAuthToken();
extern void resetPinUvAuthToken();
int cbor_config(const uint8_t *data, size_t len) { int cbor_config(const uint8_t *data, size_t len) {
CborParser parser; CborParser parser;
CborValue map; CborValue map;
CborError error = CborNoError; CborError error = CborNoError;
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParam = 0; uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0;
CborByteString pinUvAuthParam = { 0 }, vendorAutCt = { 0 }; CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 };
CborCharString minPinLengthRPIDs[32] = { 0 }; CborCharString minPinLengthRPIDs[32] = { 0 }, vendorParamTextString = { 0 };
size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0; size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0;
CborEncoder encoder; CborEncoder encoder;
//CborEncoder mapEncoder; //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); raw_subpara = (uint8_t *) cbor_value_get_next_byte(&_f1);
CBOR_PARSE_MAP_START(_f1, 2) CBOR_PARSE_MAP_START(_f1, 2)
{ {
if (subcommand == 0x7f) { // Config Aut if (subcommand == 0xFF) { // Vendor
CBOR_FIELD_GET_UINT(subpara, 2); CBOR_FIELD_GET_UINT(subpara, 2);
if (subpara == 0x01) { if (subpara == 0x01) {
CBOR_FIELD_GET_UINT(vendorCommandId, 2); CBOR_FIELD_GET_UINT(vendorCommandId, 2);
} }
else if (subpara == 0x02) { 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 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); 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); CBOR_PARSE_MAP_END(_f1, 2);
raw_subpara_len = cbor_value_get_next_byte(&_f1) - raw_subpara; 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) { if (pinUvAuthParam.present == false) {
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED); CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
} }
if (pinUvAuthProtocol == 0) { if (pinUvAuthProtocol == 0) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); 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); uint8_t *verify_payload = (uint8_t *) calloc(1, 32 + 1 + 1 + raw_subpara_len);
memset(verify_payload, 0xff, 32); 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); CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
} }
if (subcommand == 0x7f) { if (subcommand == 0xFF) {
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) { #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)) { if (!file_has_data(ef_keydev_enc)) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
} }
@@ -163,7 +171,7 @@ int cbor_config(const uint8_t *data, size_t len) {
} }
mbedtls_chachapoly_context chatx; 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) { if (ret != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); 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]; uint8_t key_dev_enc[12 + 32 + 16];
random_gen(NULL, key_dev_enc, 12); random_gen(NULL, key_dev_enc, 12);
mbedtls_chachapoly_init(&chatx); 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)); 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); mbedtls_chachapoly_free(&chatx);
if (ret != 0) { 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 file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
low_flash_available(); 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 { else {
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND); 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) { if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength) {
forceChangePin = ptrue; forceChangePin = ptrue;
} }
if (forceChangePin) {
resetPersistentPinUvAuthToken();
resetPinUvAuthToken();
}
uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32); uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32);
dataf[0] = (uint8_t)newMinPinLength; dataf[0] = (uint8_t)newMinPinLength;
dataf[1] = forceChangePin == ptrue ? 1 : 0; 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); set_opts(get_opts() | FIDO2_OPT_EA);
goto err; 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 { else {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION); CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
} }
@@ -259,7 +292,8 @@ int cbor_config(const uint8_t *data, size_t len) {
err: err:
CBOR_FREE_BYTE_STRING(pinUvAuthParam); 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++) { for (size_t i = 0; i < minPinLengthRPIDs_len; i++) {
CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[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/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "pico_keys.h"
#include "fido.h" #include "fido.h"
#include "ctap.h" #include "ctap.h"
#include "hid/ctap_hid.h" #include "hid/ctap_hid.h"
@@ -22,7 +23,6 @@
#include "files.h" #include "files.h"
#include "apdu.h" #include "apdu.h"
#include "credential.h" #include "credential.h"
#include "pico_keys.h"
uint8_t rp_counter = 1; uint8_t rp_counter = 1;
uint8_t rp_total = 0; 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) { if (strcmp(_fd3, "transports") == 0) {
CBOR_PARSE_ARRAY_START(_f3, 4) CBOR_PARSE_ARRAY_START(_f3, 4)
{ {
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId. CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.transports_len], 4);
transports_len], 4);
credentialId.transports_len++; credentialId.transports_len++;
} }
CBOR_PARSE_ARRAY_END(_f3, 4); 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); cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
if (subcommand == 0x01) { if (subcommand == 0x01) {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) { if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) == CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
} }
if (is_preview == false && else {
(!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) { if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); 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; uint8_t existing = 0;
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) { 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) { else if (subcommand == 0x02 || subcommand == 0x03) {
file_t *rp_ef = NULL; file_t *rp_ef = NULL;
if (subcommand == 0x02) { if (subcommand == 0x02) {
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) { if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) == CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); 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)) { else {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); 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_counter = 1;
rp_total = 0; rp_total = 0;
@@ -199,13 +212,20 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
} }
if (subcommand == 0x04) { if (subcommand == 0x04) {
*(raw_subpara - 1) = 0x04; *(raw_subpara - 1) = 0x04;
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) { if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) == CborNoError) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
} }
if (is_preview == false && else {
(!(paut.permissions & CTAP_PERMISSION_CM) || if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
(paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) { CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
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_counter = 1;
cred_total = 0; cred_total = 0;
@@ -239,7 +259,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
} }
Credential cred = { 0 }; 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); 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_encode_uint(&mapEncoder, 0x07));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2)); CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id")); 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, "type"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key")); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2)); 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++) { for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + 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); uint8_t *rp_id_hash = file_get_data(ef);
if (delete_file(ef) != 0) { if (delete_file(ef) != 0) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); 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++) { for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + 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 }; Credential cred = { 0 };
uint8_t *rp_id_hash = file_get_data(ef); 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); CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
} }
if (memcmp(user.id.data, cred.userId.data, MIN(user.id.len, cred.userId.len)) != 0) { 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); CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
} }
uint8_t newcred[MAX_CRED_ID_LENGTH]; 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, if (credential_create(&cred.rpId, &cred.userId, &user.parent.name,
&user.displayName, &cred.opts, &cred.extensions, &user.displayName, &cred.opts, &cred.extensions,
cred.use_sign_count, (int)cred.alg, cred.use_sign_count, (int)cred.alg,
(int)cred.curve, newcred, &newcred_len) != 0) { (int)cred.curve, newcred, &newcred_len) != 0) {
credential_free(&cred); credential_free(&cred);
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED); CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
} }

View File

@@ -15,16 +15,16 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "pico_keys.h"
#include "cbor.h" #include "cbor.h"
#include "ctap.h" #include "ctap.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) #if defined(PICO_PLATFORM)
#include "bsp/board.h" #include "bsp/board.h"
#endif #endif
#include "hid/ctap_hid.h" #include "hid/ctap_hid.h"
#include "fido.h" #include "fido.h"
#include "files.h" #include "files.h"
#include "crypto_utils.h" #include "crypto_utils.h"
#include "pico_keys.h"
#include "apdu.h" #include "apdu.h"
#include "cbor_make_credential.h" #include "cbor_make_credential.h"
#include "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) { if (strcmp(allowList[e].type.data, "public-key") != 0) {
continue; continue;
} }
if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) { if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
credential_free(&creds[creds_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));
else { if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
creds_len++; continue;
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 (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) {
if (!resident) { if (credential_load_resident(ef, rp_id_hash, &creds[creds_len]) != 0) {
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) { // Should never happen
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i)); credential_free(&creds[creds_len]);
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue; 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; 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) { if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue; 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) { if (ret != 0) {
credential_free(&creds[creds_len]); 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)) { if (creds[i].extensions.credProtect == CRED_PROT_UV_REQUIRED && !(flags & FIDO2_AUT_FLAG_UV)) {
credential_free(&creds[i]); credential_free(&creds[i]);
} }
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && allowList_len == 0 && !(flags & FIDO2_AUT_FLAG_UV)) {
resident == true && !(flags & FIDO2_AUT_FLAG_UV)) {
credential_free(&creds[i]); credential_free(&creds[i]);
} }
else { 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) { if (strcmp(allowList[e].type.data, "public-key") != 0) {
continue; continue;
} }
if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) { if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
numberOfCredentials++; 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 { else {
selcred = &creds[0]; selcred = &creds[0];
if (resident && allowList_len > 1) {
numberOfCredentials = 1;
}
if (numberOfCredentials > 1) { if (numberOfCredentials > 1) {
asserted = true; asserted = true;
residentx = resident; residentx = resident;
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) { 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]; credsx[i] = creds[i];
} }
numberOfCredentialsx = numberOfCredentials; 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_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id")); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
if (selcred) { 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 { 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)); 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_encoder_create_map(&mapEncoder, &mapEncoder2, lu));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id")); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data, CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data, selcred->userId.len));
selcred->userId.len));
if (numberOfCredentials > 1 && allowList_len == 0) { if (numberOfCredentials > 1 && allowList_len == 0) {
if (selcred->userName.present == true) { if (selcred->userName.present == true) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name")); 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/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "pico_keys.h"
#include "ctap2_cbor.h" #include "ctap2_cbor.h"
#include "hid/ctap_hid.h" #include "hid/ctap_hid.h"
#include "fido.h" #include "fido.h"
@@ -27,22 +28,36 @@ int cbor_get_info() {
CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2; CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2;
CborError error = CborNoError; CborError error = CborNoError;
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0); 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_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, "U2F_V2"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_0")); 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_1"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_2"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02)); 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, "credBlob"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobKey")); CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobKey"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "minPinLength")); 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_encode_text_stringz(&arrayEncoder, "thirdPartyPayment"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder)); 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, 0x0F));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CREDBLOB_LENGTH)); // maxCredBlobLength 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)); #endif
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
err: err:
if (error != CborNoError) { if (error != CborNoError) {

View File

@@ -15,13 +15,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "pico_keys.h"
#include "ctap2_cbor.h" #include "ctap2_cbor.h"
#include "fido.h" #include "fido.h"
#include "ctap.h" #include "ctap.h"
#include "hid/ctap_hid.h" #include "hid/ctap_hid.h"
#include "files.h" #include "files.h"
#include "apdu.h" #include "apdu.h"
#include "pico_keys.h"
#include "mbedtls/sha256.h" #include "mbedtls/sha256.h"
static uint64_t expectedLength = 0, expectedNextOffset = 0; 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 }; uint8_t verify_data[70] = { 0 };
memset(verify_data, 0xff, 32); memset(verify_data, 0xff, 32);
verify_data[32] = 0x0C; 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); 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) { if (verify((uint8_t)pinUvAuthProtocol, paut.data, verify_data, (uint16_t)sizeof(verify_data), pinUvAuthParam.data) != 0) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID); 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/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "pico_keys.h"
#include "cbor_make_credential.h" #include "cbor_make_credential.h"
#include "ctap2_cbor.h" #include "ctap2_cbor.h"
#include "hid/ctap_hid.h" #include "hid/ctap_hid.h"
@@ -25,7 +26,7 @@
#include "credential.h" #include "credential.h"
#include "mbedtls/sha256.h" #include "mbedtls/sha256.h"
#include "random.h" #include "random.h"
#include "pico_keys.h" #include "crypto_utils.h"
int cbor_make_credential(const uint8_t *data, size_t len) { int cbor_make_credential(const uint8_t *data, size_t len) {
CborParser parser; 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 }; PublicKeyCredentialDescriptor excludeList[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
size_t excludeList_len = 0; size_t excludeList_len = 0;
CredOptions options = { 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; uint8_t *aut_data = NULL;
size_t resp_size = 0; size_t resp_size = 0;
CredExtensions extensions = { 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_PARSE_MAP_START(_f1, 2)
{ {
CBOR_FIELD_GET_KEY_TEXT(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_BOOL(2, "hmac-secret", extensions.hmac_secret);
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect); 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_BOOL(2, "minPinLength", extensions.minPinLength);
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob); 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, "largeBlobKey", extensions.largeBlobKey);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment); 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_ADVANCE(2);
} }
CBOR_PARSE_MAP_END(_f1, 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) { if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0) {
CBOR_ERROR(CTAP2_ERR_CBOR_UNEXPECTED_TYPE); 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) { if (curve <= 0) {
curve = FIDO2_CURVE_P256; 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) { if (curve <= 0) {
curve = FIDO2_CURVE_P384; 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) { if (curve <= 0) {
curve = FIDO2_CURVE_P521; 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 else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256K
#ifndef ENABLE_EMULATION #ifndef ENABLE_EMULATION
&& (phy_data.enabled_curves & PHY_CURVE_SECP256K1) && (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 #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) { if (curve <= 0) {
curve = FIDO2_CURVE_ED25519; curve = FIDO2_CURVE_ED25519;
} }
} }
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ED448) {
if (curve <= 0) {
curve = FIDO2_CURVE_ED448;
}
}
#endif #endif
else if (pubKeyCredParams[i].alg <= FIDO2_ALG_RS256 && pubKeyCredParams[i].alg >= FIDO2_ALG_RS512) { else if (pubKeyCredParams[i].alg <= FIDO2_ALG_RS256 && pubKeyCredParams[i].alg >= FIDO2_ALG_RS512) {
// pass // pass
@@ -298,12 +350,26 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
continue; continue;
} }
Credential ecred = {0}; Credential ecred = {0};
if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, if (credential_is_resident(excludeList[e].id.data, excludeList[e].id.len)) {
&ecred) == 0 && for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
(ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || file_t *ef_cred = search_dynamic_file((uint16_t)(EF_CRED + i));
(flags & FIDO2_AUT_FLAG_UV))) { 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); credential_free(&ecred);
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED); CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
}
} }
credential_free(&ecred); credential_free(&ecred);
} }
@@ -313,6 +379,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION); 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 (options.up == ptrue || options.up == NULL) { //14.1
if (pinUvAuthParam.present == true) { if (pinUvAuthParam.present == true) {
if (getUserPresentFlagValue() == false) { 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); const known_app_t *ka = find_app_by_rp_id_hash(rp_id_hash);
uint8_t cred_id[MAX_CRED_ID_LENGTH] = {0}; 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, 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));
&extensions, (!ka || ka->use_sign_count == ptrue), alg, curve,
cred_id, &cred_id_len));
if (getUserVerifiedFlagValue()) { if (getUserVerifiedFlagValue()) {
flags |= FIDO2_AUT_FLAG_UV; 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) { if (extensions.credBlob.present == true) {
l++; l++;
} }
if (hmac_secret_mc) {
l++;
}
if (pin_complexity_policy == ptrue) {
l++;
}
if (l > 0) { if (l > 0) {
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l)); CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
if (extensions.credBlob.present == true) { 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)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
} }
if (extensions.hmac_secret == ptrue) { if (extensions.hmac_secret == ptrue) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret")); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true)); CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
} }
if (minPinLen > 0) { if (minPinLen > 0) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength")); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen)); 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)); CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
ext_len = cbor_encoder_get_buffer_size(&encoder, ext); 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)); CBOR_CHECK(COSE_key(&ekey, &encoder, &mapEncoder));
size_t rs = cbor_encoder_get_buffer_size(&encoder, cbor_buf); 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); aut_data = (uint8_t *) calloc(1, aut_data_len + clientDataHash.len);
uint8_t *pa = aut_data; uint8_t *pa = aut_data;
memcpy(pa, rp_id_hash, 32); pa += 32; memcpy(pa, rp_id_hash, 32); pa += 32;
*pa++ = flags; *pa++ = flags;
pa += put_uint32_t_be(ctr, pa); pa += put_uint32_t_be(ctr, pa);
memcpy(pa, aaguid, 16); pa += 16; memcpy(pa, aaguid, 16); pa += 16;
pa += put_uint16_t_be(cred_id_len, pa); if (options.rk == ptrue) {
memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len; 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, cbor_buf, rs); pa += (uint16_t)rs;
memcpy(pa, ext, ext_len); pa += (uint16_t)ext_len; memcpy(pa, ext, ext_len); pa += (uint16_t)ext_len;
if ((size_t)(pa - aut_data) != aut_data_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); memcpy(pa, clientDataHash.data, clientDataHash.len);
uint8_t hash[64] = {0}, sig[MBEDTLS_ECDSA_MAX_LEN] = {0}; 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); 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); 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); md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
} }
#ifdef MBEDTLS_EDDSA_C #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; md = NULL;
} }
#endif #endif
@@ -483,7 +619,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
#ifndef ENABLE_EMULATION #ifndef ENABLE_EMULATION
uint8_t *p = (uint8_t *)user.parent.name.data + 5; uint8_t *p = (uint8_t *)user.parent.name.data + 5;
if (memcmp(p, "CommissionProfile", 17) == 0) { 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) { if (ret == PICOKEY_OK) {
ret = phy_save(); 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_byte_string(&mapEncoder, aut_data, aut_data_len));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03)); 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_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_text_stringz(&mapEncoder2, "sig"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, sig, olen)); CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, sig, olen));
if (self_attestation == false || is_nitrokey) { if (self_attestation == false || is_nk) {
CborEncoder arrEncoder; CborEncoder arrEncoder;
file_t *ef_cert = NULL; file_t *ef_cert = NULL;
if (enterpriseAttestation == 2) { if (enterpriseAttestation == 2) {
@@ -569,6 +705,10 @@ err:
CBOR_FREE_BYTE_STRING(user.id); CBOR_FREE_BYTE_STRING(user.id);
CBOR_FREE_BYTE_STRING(user.displayName); CBOR_FREE_BYTE_STRING(user.displayName);
CBOR_FREE_BYTE_STRING(user.parent.name); 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) { if (extensions.present == true) {
CBOR_FREE_BYTE_STRING(extensions.credBlob); CBOR_FREE_BYTE_STRING(extensions.credBlob);
} }

View File

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

View File

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

View File

@@ -15,13 +15,13 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "pico_keys.h"
#include "ctap2_cbor.h" #include "ctap2_cbor.h"
#include "fido.h" #include "fido.h"
#include "ctap.h" #include "ctap.h"
#include "hid/ctap_hid.h" #include "hid/ctap_hid.h"
#include "files.h" #include "files.h"
#include "apdu.h" #include "apdu.h"
#include "pico_keys.h"
#include "random.h" #include "random.h"
#include "mbedtls/ecdh.h" #include "mbedtls/ecdh.h"
#include "mbedtls/chachapoly.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_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, buffer + sizeof(buffer) - ret, ret)); 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 #ifndef ENABLE_EMULATION
else if (cmd == CTAP_VENDOR_PHY_OPTS) { else if (cmd == CTAP_VENDOR_PHY_OPTS) {
if (vendorCmd == 0x01) { if (vendorCmd == 0x01) {
uint16_t opts = 0; uint16_t opts = 0;
if (file_has_data(ef_phy)) { if (file_has_data(ef_phy)) {
uint8_t *data = file_get_data(ef_phy); uint8_t *pdata = file_get_data(ef_phy);
opts = get_uint16_t_be(data + PHY_OPTS); opts = get_uint16_t_be(pdata + PHY_OPTS);
} }
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1)); CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); 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/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "fido.h"
#include "pico_keys.h" #include "pico_keys.h"
#include "fido.h"
#include "apdu.h" #include "apdu.h"
#include "ctap.h" #include "ctap.h"
#include "random.h" #include "random.h"

View File

@@ -15,8 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "fido.h"
#include "pico_keys.h" #include "pico_keys.h"
#include "fido.h"
#include "apdu.h" #include "apdu.h"
#include "ctap.h" #include "ctap.h"
#include "random.h" #include "random.h"
@@ -69,11 +69,7 @@ int cmd_register() {
} }
if (memcmp(req->appId, bogus_firefox, if (memcmp(req->appId, bogus_firefox,
CTAP_APPID_SIZE) == 0 || memcmp(req->appId, bogus_chrome, CTAP_APPID_SIZE) == 0) CTAP_APPID_SIZE) == 0 || memcmp(req->appId, bogus_chrome, CTAP_APPID_SIZE) == 0)
#ifndef ENABLE_EMULATION
{ return ctap_error(CTAP1_ERR_CHANNEL_BUSY); } { return ctap_error(CTAP1_ERR_CHANNEL_BUSY); }
#else
{ return SW_DATA_INVALID(); }
#endif
mbedtls_ecdsa_context key; mbedtls_ecdsa_context key;
mbedtls_ecdsa_init(&key); mbedtls_ecdsa_init(&key);
int ret = derive_key(req->appId, true, resp->keyHandleCertSig, MBEDTLS_ECP_DP_SECP256R1, &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/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "pico_keys.h"
#include "mbedtls/chachapoly.h" #include "mbedtls/chachapoly.h"
#include "mbedtls/sha256.h" #include "mbedtls/sha256.h"
#include "credential.h" #include "credential.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) #if defined(PICO_PLATFORM)
#include "bsp/board.h" #include "bsp/board.h"
#endif #endif
#include "hid/ctap_hid.h" #include "hid/ctap_hid.h"
@@ -26,7 +27,6 @@
#include "ctap.h" #include "ctap.h"
#include "random.h" #include "random.h"
#include "files.h" #include "files.h"
#include "pico_keys.h"
#include "otp.h" #include "otp.h"
int credential_derive_chacha_key(uint8_t *outk, const uint8_t *); int credential_derive_chacha_key(uint8_t *outk, const uint8_t *);
@@ -93,7 +93,7 @@ int credential_create(CborCharString *rpId,
int alg, int alg,
int curve, int curve,
uint8_t *cred_id, uint8_t *cred_id,
size_t *cred_id_len) { uint16_t *cred_id_len) {
CborEncoder encoder, mapEncoder, mapEncoder2; CborEncoder encoder, mapEncoder, mapEncoder2;
CborError error = CborNoError; CborError error = CborNoError;
uint8_t rp_id_hash[32]; uint8_t rp_id_hash[32];
@@ -150,7 +150,7 @@ int credential_create(CborCharString *rpId,
} }
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
size_t rs = cbor_encoder_get_buffer_size(&encoder, cred_id); 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}; uint8_t key[32] = {0};
credential_derive_chacha_key(key, (const uint8_t *)CRED_PROTO); credential_derive_chacha_key(key, (const uint8_t *)CRED_PROTO);
uint8_t iv[CRED_IV_LEN] = {0}; 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) { if (memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue; 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) { if (ret != 0) {
credential_free(&rcred); credential_free(&rcred);
continue; continue;
@@ -330,11 +330,14 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
if (sloti == -1) { if (sloti == -1) {
return -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, 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_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); free(data);
if (new_record == true) { //increase rps 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); mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk);
return 0; 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_ #define _CREDENTIAL_H_
#include "ctap2_cbor.h" #include "ctap2_cbor.h"
#include "file.h"
typedef struct CredOptions { typedef struct CredOptions {
const bool *rk; const bool *rk;
@@ -58,6 +59,7 @@ typedef struct Credential {
#define CRED_PROTO_21_S "\xf1\xd0\x02\x01" #define CRED_PROTO_21_S "\xf1\xd0\x02\x01"
#define CRED_PROTO_22_S "\xf1\xd0\x02\x02" #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 #define CRED_PROTO CRED_PROTO_22_S
@@ -66,6 +68,11 @@ typedef struct Credential {
#define CRED_TAG_LEN 16 #define CRED_TAG_LEN 16
#define CRED_SILENT_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 typedef enum
{ {
CRED_PROTO_21 = 0x01, CRED_PROTO_21 = 0x01,
@@ -83,7 +90,7 @@ extern int credential_create(CborCharString *rpId,
int alg, int alg,
int curve, int curve,
uint8_t *cred_id, uint8_t *cred_id,
size_t *cred_id_len); uint16_t *cred_id_len);
extern void credential_free(Credential *cred); 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_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, 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, extern int credential_derive_large_blob_key(const uint8_t *cred_id,
size_t cred_id_len, size_t cred_id_len,
uint8_t *outk); 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_ #endif // _CREDENTIAL_H_

View File

@@ -114,10 +114,14 @@ typedef struct {
#define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2 #define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2
#define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9 #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_VIDPID 0x6fcb19b0cbe3acfa
#define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948
#define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd #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) #define CTAP_VENDOR_CBOR (CTAPHID_VENDOR_FIRST + 1)
@@ -134,6 +138,7 @@ typedef struct {
#define CTAP_PERMISSION_BE 0x08 // BioEnrollment #define CTAP_PERMISSION_BE 0x08 // BioEnrollment
#define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite #define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite
#define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration #define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration
#define CTAP_PERMISSION_PCMR 0x40 // PerCredentialManagementReadOnly
typedef struct mse { typedef struct mse {
uint8_t Qpt[65]; uint8_t Qpt[65];

View File

@@ -15,6 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * 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 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/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "pico_keys.h"
#include "fido.h" #include "fido.h"
#include "kek.h" #include "kek.h"
#include "pico_keys.h"
#include "apdu.h" #include "apdu.h"
#include "ctap.h" #include "ctap.h"
#include "files.h" #include "files.h"
@@ -25,10 +25,10 @@
#include "random.h" #include "random.h"
#include "mbedtls/x509_crt.h" #include "mbedtls/x509_crt.h"
#include "mbedtls/hkdf.h" #include "mbedtls/hkdf.h"
#if defined(USB_ITF_CCID) || defined(ENABLE_EMULATION) #if defined(USB_ITF_CCID)
#include "ccid/ccid.h" #include "ccid/ccid.h"
#endif #endif
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) #if defined(PICO_PLATFORM)
#include "bsp/board.h" #include "bsp/board.h"
#endif #endif
#include <math.h> #include <math.h>
@@ -42,6 +42,7 @@ int fido_process_apdu();
int fido_unload(); int fido_unload();
pinUvAuthToken_t paut = { 0 }; pinUvAuthToken_t paut = { 0 };
persistentPinUvAuthToken_t ppaut = { 0 };
uint8_t keydev_dec[32]; uint8_t keydev_dec[32];
bool has_keydev_dec = false; bool has_keydev_dec = false;
@@ -118,6 +119,15 @@ mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) {
return MBEDTLS_ECP_DP_ED448; return MBEDTLS_ECP_DP_ED448;
} }
#endif #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; return MBEDTLS_ECP_DP_NONE;
} }
int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) { 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]; uint8_t key_path[KEY_PATH_LEN];
memcpy(key_path, cred_id, KEY_PATH_LEN); memcpy(key_path, cred_id, KEY_PATH_LEN);
*(uint32_t *) key_path = 0x80000000 | 10022; *(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; *(uint32_t *) (key_path + i * sizeof(uint32_t)) |= 0x80000000;
} }
return derive_key(NULL, false, key_path, mbedtls_curve, key); 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; 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)) { if (has_keydev_dec == false && !file_has_data(ef_keydev)) {
return PICOKEY_ERR_MEMORY_FATAL; return PICOKEY_ERR_MEMORY_FATAL;
} }
@@ -203,13 +213,39 @@ int load_keydev(uint8_t *key) {
memcpy(key, keydev_dec, sizeof(keydev_dec)); memcpy(key, keydev_dec, sizeof(keydev_dec));
} }
else { else {
memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev)); uint16_t fid_size = file_get_size(ef_keydev);
if (fid_size == 32) {
if (mkek_decrypt(key, 32) != PICOKEY_OK) { memcpy(key, file_get_data(ef_keydev), 32);
return PICOKEY_EXEC_ERROR; 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) { else if (fid_size == 33 || fid_size == 61) {
return PICOKEY_EXEC_ERROR; 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) { 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)]; uint32_t k = *(uint32_t *) &keyHandle[i * sizeof(uint32_t)];
if (!(k & 0x80000000)) { if (!(k & 0x80000000)) {
return -1; 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; return r;
} }
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); 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) { if (new_key == true) {
uint32_t val = 0; uint32_t val = 0;
random_gen(NULL, (uint8_t *) &val, sizeof(val)); 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; 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() { int scan_files_fido() {
ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF); ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF);
ef_keydev_enc = search_by_fid(EF_KEY_DEV_ENC, 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); mbedtls_ecdsa_free(&ecdsa);
return ret; return ret;
} }
uint8_t kdata[64]; uint8_t keydev[32] = {0};
size_t key_size = 0; size_t key_size = 0;
ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, kdata, sizeof(kdata)); ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, keydev, sizeof(keydev));
if (ret != PICOKEY_OK) { if (ret != 0 || key_size != 32) {
return ret; mbedtls_platform_zeroize(keydev, sizeof(keydev));
mbedtls_ecdsa_free(&ecdsa);
return ret != 0 ? ret : PICOKEY_EXEC_ERROR;
} }
if (otp_key_1) { encrypt_keydev_f1(keydev);
ret = aes_encrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata, 32); mbedtls_platform_zeroize(keydev, sizeof(keydev));
}
ret = file_put_data(ef_keydev, kdata, (uint16_t)key_size);
mbedtls_platform_zeroize(kdata, sizeof(kdata));
mbedtls_ecdsa_free(&ecdsa); mbedtls_ecdsa_free(&ecdsa);
if (ret != PICOKEY_OK) { if (ret != PICOKEY_OK) {
return ret; return ret;
@@ -341,21 +393,6 @@ int scan_files_fido() {
else { else {
printf("FATAL ERROR: KEY DEV not found in memory!\r\n"); 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); ef_certdev = search_by_fid(EF_EE_DEV, NULL, SPECIFY_EF);
if (ef_certdev) { if (ef_certdev) {
if (!file_has_data(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"); printf("FATAL ERROR: Global counter not found in memory!\r\n");
} }
ef_pin = search_by_fid(EF_PIN, NULL, SPECIFY_EF); 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); ef_authtoken = search_by_fid(EF_AUTHTOKEN, NULL, SPECIFY_EF);
if (ef_authtoken) { if (ef_authtoken) {
if (!file_has_data(ef_authtoken)) { if (!file_has_data(ef_authtoken)) {
@@ -419,6 +449,19 @@ int scan_files_fido() {
else { else {
printf("FATAL ERROR: Auth Token not found in memory!\r\n"); 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); ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF);
if (!file_has_data(ef_largeblob)) { 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); 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(); extern void init_otp();
void init_fido() { void init_fido() {
scan_all(); scan_all();
#ifdef ENABLE_OTP_APP
init_otp(); init_otp();
#endif
} }
bool wait_button_pressed() { bool wait_button_pressed() {
uint32_t val = EV_PRESS_BUTTON; uint32_t val = EV_PRESS_BUTTON;
#ifndef ENABLE_EMULATION #if defined(PICO_PLATFORM) || defined(ESP_PLATFORM)
queue_try_add(&card_to_usb_q, &val); queue_try_add(&card_to_usb_q, &val);
do { do {
queue_remove_blocking(&usb_to_card_q, &val); queue_remove_blocking(&usb_to_card_q, &val);
@@ -485,11 +530,13 @@ extern int cmd_register();
extern int cmd_authenticate(); extern int cmd_authenticate();
extern int cmd_version(); extern int cmd_version();
extern int cbor_parse(int, uint8_t *, size_t); extern int cbor_parse(int, uint8_t *, size_t);
extern void driver_init_hid();
#define CTAP_CBOR 0x10 #define CTAP_CBOR 0x10
int cmd_cbor() { int cmd_cbor() {
uint8_t *old_buf = res_APDU; uint8_t *old_buf = res_APDU;
driver_init_hid();
int ret = cbor_parse(0x90, apdu.data, apdu.nc); int ret = cbor_parse(0x90, apdu.data, apdu.nc);
if (ret != 0) { if (ret != 0) {
return SW_EXEC_ERROR(); return SW_EXEC_ERROR();

View File

@@ -18,7 +18,7 @@
#ifndef _FIDO_H_ #ifndef _FIDO_H_
#define _FIDO_H_ #define _FIDO_H_
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) #if defined(PICO_PLATFORM)
#include "pico/stdlib.h" #include "pico/stdlib.h"
#endif #endif
#ifndef ESP_PLATFORM #ifndef ESP_PLATFORM
@@ -31,11 +31,7 @@
#ifdef MBEDTLS_EDDSA_C #ifdef MBEDTLS_EDDSA_C
#include "mbedtls/eddsa.h" #include "mbedtls/eddsa.h"
#endif #endif
#ifndef ENABLE_EMULATION
#include "hid/ctap_hid.h" #include "hid/ctap_hid.h"
#else
#include <stdbool.h>
#endif
#define CTAP_PUBKEY_LEN (65) #define CTAP_PUBKEY_LEN (65)
#define KEY_PATH_LEN (32) #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 mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve);
extern int mbedtls_curve_to_fido(mbedtls_ecp_group_id id); 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 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 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 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); 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_EDDSA -8 //EdDSA
#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384 P384 #define FIDO2_ALG_ESP256 -9 //ECDSA-SHA256 P256
#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512 P521 #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_ECDH_ES_HKDF_256 -25 //ECDH-ES + HKDF-256
#define FIDO2_ALG_ES256K -47 #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_RS256 -257
#define FIDO2_ALG_RS384 -258 #define FIDO2_ALG_RS384 -258
#define FIDO2_ALG_RS512 -259 #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_P256 1
#define FIDO2_CURVE_P384 2 #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_ED25519 6
#define FIDO2_CURVE_ED448 7 #define FIDO2_CURVE_ED448 7
#define FIDO2_CURVE_P256K1 8 #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_UP 0x1
#define FIDO2_AUT_FLAG_UV 0x4 #define FIDO2_AUT_FLAG_UV 0x4
@@ -129,9 +136,17 @@ typedef struct pinUvAuthToken {
bool user_verified; bool user_verified;
} pinUvAuthToken_t; } pinUvAuthToken_t;
typedef struct persistentPinUvAuthToken {
uint8_t *data;
size_t len;
uint8_t permissions;
} persistentPinUvAuthToken_t;
extern uint32_t user_present_time_limit; extern uint32_t user_present_time_limit;
extern pinUvAuthToken_t paut; 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 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]; 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_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_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_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_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_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 { .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_OPTS 0xC001
#define EF_PIN 0x1080 #define EF_PIN 0x1080
#define EF_AUTHTOKEN 0x1090 #define EF_AUTHTOKEN 0x1090
#define EF_PAUTHTOKEN 0x1091
#define EF_MINPINLEN 0x1100 #define EF_MINPINLEN 0x1100
#define EF_PIN_COMPLEXITY_POLICY 0x1102
#define EF_DEV_CONF 0x1122 #define EF_DEV_CONF 0x1122
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF #define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF
#define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF #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/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "fido.h"
#include "pico_keys.h" #include "pico_keys.h"
#include "fido.h"
#include "stdlib.h" #include "stdlib.h"
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM) #if defined(PICO_PLATFORM)
#include "pico/stdlib.h" #include "pico/stdlib.h"
#endif #endif
#include "kek.h" #include "kek.h"
@@ -85,46 +85,6 @@ void release_mkek(uint8_t *mkek) {
mbedtls_platform_zeroize(mkek, MKEK_SIZE); 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 mkek_decrypt(uint8_t *data, uint16_t len) {
int r; int r;
uint8_t mkek[MKEK_SIZE + 4]; uint8_t mkek[MKEK_SIZE + 4];

View File

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

View File

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

View File

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

View File

@@ -15,8 +15,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include "fido.h"
#include "pico_keys.h" #include "pico_keys.h"
#include "fido.h"
#include "apdu.h" #include "apdu.h"
#include "files.h" #include "files.h"
#include "random.h" #include "random.h"
@@ -24,6 +24,7 @@
#include "asn1.h" #include "asn1.h"
#include "crypto_utils.h" #include "crypto_utils.h"
#include "management.h" #include "management.h"
extern bool is_nk;
#define MAX_OATH_CRED 255 #define MAX_OATH_CRED 255
#define CHALLENGE_LEN 8 #define CHALLENGE_LEN 8
@@ -44,6 +45,10 @@
#define TAG_PASSWORD 0x80 #define TAG_PASSWORD 0x80
#define TAG_NEW_PASSWORD 0x81 #define TAG_NEW_PASSWORD 0x81
#define TAG_PIN_COUNTER 0x82 #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_SHA1 0x01
#define ALG_HMAC_SHA256 0x02 #define ALG_HMAC_SHA256 0x02
@@ -56,6 +61,7 @@
#define PROP_INC 0x01 #define PROP_INC 0x01
#define PROP_TOUCH 0x02 #define PROP_TOUCH 0x02
#define PROP_PIN 0x03
int oath_process_apdu(); int oath_process_apdu();
int oath_unload(); 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++] = TAG_ALGO;
res_APDU[res_APDU_size++] = 1; res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = ALG_HMAC_SHA1; 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; apdu.ne = res_APDU_size;
return PICOKEY_OK; return PICOKEY_OK;
} }
@@ -270,16 +282,27 @@ int cmd_list() {
if (validated == false) { if (validated == false) {
return SW_SECURITY_STATUS_NOT_SATISFIED(); return SW_SECURITY_STATUS_NOT_SATISFIED();
} }
bool ext = (apdu.nc == 1 && apdu.data[0] == 0x01);
for (int i = 0; i < MAX_OATH_CRED; i++) { for (int i = 0; i < MAX_OATH_CRED; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i)); file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
if (file_has_data(ef)) { 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); 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) { 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++] = 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]; res_APDU[res_APDU_size++] = key.data[0];
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len; 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) { if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
return SW_WRONG_DATA(); 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); memcpy(new_data, fdata, name.data - fdata);
*(new_data + (name.data - fdata) - 1) = new_name.len; *(new_data + (name.data - fdata) - 1) = new_name.len;
memcpy(new_data + (name.data - fdata), new_name.data, 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(); 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_PUT 0x01
#define INS_DELETE 0x02 #define INS_DELETE 0x02
#define INS_SET_CODE 0x03 #define INS_SET_CODE 0x03
@@ -640,6 +710,7 @@ int cmd_rename() {
#define INS_VERIFY_PIN 0xb2 #define INS_VERIFY_PIN 0xb2
#define INS_CHANGE_PIN 0xb3 #define INS_CHANGE_PIN 0xb3
#define INS_SET_PIN 0xb4 #define INS_SET_PIN 0xb4
#define INS_GET_CREDENTIAL 0xb5
static const cmd_t cmds[] = { static const cmd_t cmds[] = {
{ INS_PUT, cmd_put }, { INS_PUT, cmd_put },
@@ -656,6 +727,7 @@ static const cmd_t cmds[] = {
{ INS_CHANGE_PIN, cmd_change_otp_pin }, { INS_CHANGE_PIN, cmd_change_otp_pin },
{ INS_VERIFY_PIN, cmd_verify_otp_pin }, { INS_VERIFY_PIN, cmd_verify_otp_pin },
{ INS_VERIFY_CODE, cmd_verify_hotp }, { INS_VERIFY_CODE, cmd_verify_hotp },
{ INS_GET_CREDENTIAL, cmd_get_credential },
{ 0x00, 0x0 } { 0x00, 0x0 }
}; };

View File

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

View File

@@ -20,12 +20,13 @@
from http import client from http import client
from fido2.hid import CtapHidDevice 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.attestation import FidoU2FAttestation
from fido2.ctap2.pin import ClientPin from fido2.ctap2.pin import ClientPin
from fido2.server import Fido2Server from fido2.server import Fido2Server
from fido2.ctap import CtapError 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 utils import *
from fido2.cose import ES256 from fido2.cose import ES256
import sys import sys
@@ -70,11 +71,13 @@ class DeviceSelectCredential:
pass pass
class Device(): 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.__user = None
self.__set_client(origin=origin, user_interaction=user_interaction, uv=uv) self.__set_client(origin=origin, user_interaction=user_interaction, uv=uv)
self.__set_server(rp=rp, attestation=attestation) self.__set_server(rp=rp, attestation=attestation)
def __verify_rp(rp_id, origin):
return True
def __set_client(self, origin, user_interaction, uv): def __set_client(self, origin, user_interaction, uv):
self.__uv = uv self.__uv = uv
@@ -101,14 +104,23 @@ class Device():
sys.exit(1) sys.exit(1)
# Set up a FIDO 2 client using the origin https://example.com # 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 # Prefer UV if supported and configured
if self.__client.info.options.get("uv") or self.__client.info.options.get("pinUvAuthToken"): if self.__client.info.options.get("uv") or self.__client.info.options.get("pinUvAuthToken"):
self.__uv = "preferred" self.__uv = "preferred"
print("Authenticator supports User Verification") 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.__client1._backend = _Ctap1ClientBackend(self.__dev, user_interaction=self.__user_interaction)
self.ctap1 = self.__client1._backend.ctap1 self.ctap1 = self.__client1._backend.ctap1
@@ -117,7 +129,7 @@ class Device():
self.__attestation = attestation self.__attestation = attestation
self.__server = Fido2Server(self.__rp, attestation=self.__attestation) self.__server = Fido2Server(self.__rp, attestation=self.__attestation)
self.__server.allowed_algorithms = [ 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 for p in self.__client._backend.info.algorithms
] ]
@@ -216,9 +228,7 @@ class Device():
'key_params':key_params}} '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): 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( client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32)
)
rp = rp if rp is not Ellipsis else self.__rp rp = rp if rp is not Ellipsis else self.__rp
user = user if user is not Ellipsis else self.user() 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 key_params = key_params if key_params is not Ellipsis else self.__server.allowed_algorithms
@@ -226,22 +236,31 @@ class Device():
client = self.__client1 client = self.__client1
else: else:
client = self.__client client = self.__client
result = client._backend.do_make_credential( options=PublicKeyCredentialCreationOptions(
client_data=client_data, rp=PublicKeyCredentialRpEntity.from_dict(rp),
rp=rp, user=PublicKeyCredentialUserEntity.from_dict(user),
user=user, pub_key_cred_params=key_params,
key_params=key_params, exclude_credentials=exclude_list,
exclude_list=exclude_list,
extensions=extensions, extensions=extensions,
rk=rk, challenge=os.urandom(32),
user_verification=user_verification, authenticator_selection=AuthenticatorSelectionCriteria(
enterprise_attestation=enterprise_attestation, 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 event=event
) )
return {'res':result,'req':{'client_data':client_data, return {'res':result.response,'req':{'client_data':client_data,
'rp':rp, 'rp':rp,
'user':user, 'user':user,
'key_params':key_params}} 'key_params':key_params},'client_extension_results':result.client_extension_results}
def try_make_credential(self, options=None): def try_make_credential(self, options=None):
if (options is None): if (options is None):
@@ -267,14 +286,14 @@ class Device():
# Complete registration # Complete registration
auth_data = self.__server.register_complete( auth_data = self.__server.register_complete(
state, result.client_data, result.attestation_object state=state, response=result
) )
credentials = [auth_data.credential_data] credentials = [auth_data.credential_data]
print("New credential created!") print("New credential created!")
print("CLIENT DATA:", result.client_data) print("CLIENT DATA:", result.response.client_data)
print("ATTESTATION OBJECT:", result.attestation_object) print("ATTESTATION OBJECT:", result.response.attestation_object)
print() print()
print("CREDENTIAL DATA:", auth_data.credential_data) print("CREDENTIAL DATA:", auth_data.credential_data)
@@ -294,17 +313,14 @@ class Device():
self.__server.authenticate_complete( self.__server.authenticate_complete(
state, state,
credentials, credentials,
result.credential_id, result
result.client_data,
result.authenticator_data,
result.signature,
) )
print("Credential authenticated!") print("Credential authenticated!")
print("CLIENT DATA:", result.client_data) print("CLIENT DATA:", result.response.client_data)
print() 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): 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'] 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() 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): 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( client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
type=CollectedClientData.TYPE.GET, origin=self.__origin, challenge=os.urandom(32) 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'] 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): if (ctap1 is True):
client = self.__client1 client = self.__client1
else: else:
client = self.__client client = self.__client
try: try:
result = client._backend.do_get_assertion( result = client._backend.do_get_assertion(
options=options,
client_data=client_data, client_data=client_data,
rp_id=rp_id, rp_id=rp_id,
allow_list=allow_list,
extensions=extensions,
user_verification=user_verification,
event=event event=event
) )
except ClientError as e: except ClientError as e:
@@ -347,11 +373,9 @@ class Device():
client_pin = ClientPin(self.__client._backend.ctap2) client_pin = ClientPin(self.__client._backend.ctap2)
client_pin.set_pin(DEFAULT_PIN) client_pin.set_pin(DEFAULT_PIN)
result = client._backend.do_get_assertion( result = client._backend.do_get_assertion(
options=options,
client_data=client_data, client_data=client_data,
rp_id=rp_id, rp_id=rp_id,
allow_list=allow_list,
extensions=extensions,
user_verification=user_verification,
event=event event=event
) )
else: else:
@@ -416,8 +440,8 @@ def AuthRes(device, RegRes, *args):
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} {"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
], *args) ], *args)
aut_data = res['res'].get_response(0) 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 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.signature) ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.response.signature)
return aut_data return aut_data
@pytest.fixture(scope="class") @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 \ cmake \
libfuse-dev \ libfuse-dev \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
RUN pip3 install pytest pycvc cryptography pyscard inputimeout RUN pip3 install pytest pycvc cryptography pyscard inputimeout fido2==2.0.0
RUN git clone https://github.com/polhenarejos/python-fido2.git
WORKDIR /python-fido2
RUN git checkout development
RUN pip3 install .
WORKDIR / WORKDIR /
RUN git clone https://github.com/frankmorgner/vsmartcard.git RUN git clone https://github.com/frankmorgner/vsmartcard.git
WORKDIR /vsmartcard/virtualsmartcard WORKDIR /vsmartcard/virtualsmartcard

View File

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

View File

@@ -46,7 +46,7 @@
# default values, can be overridden by the environment # 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}" DOCKER_IMAGE_TAG="pico-hsm-test:${MBEDTLS_DOCKER_GUEST}"

View File

@@ -20,8 +20,6 @@
from fido2.client import CtapError from fido2.client import CtapError
from fido2.cose import ES256, ES384, ES512, EdDSA from fido2.cose import ES256, ES384, ES512, EdDSA
import fido2.features
fido2.features.webauthn_json_mapping.enabled = False
from utils import ES256K from utils import ES256K
import pytest import pytest
@@ -51,13 +49,13 @@ def test_bad_type_cdh(device):
def test_missing_user(device): def test_missing_user(device):
with pytest.raises(CtapError) as e: with pytest.raises(CtapError) as e:
device.doMC(user=None) device.MC(user=None)
assert e.value.code == CtapError.ERR.MISSING_PARAMETER assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_bad_type_user_user(device): def test_bad_type_user_user(device):
with pytest.raises(CtapError) as e: with pytest.raises(CtapError) as e:
device.doMC(user=b"12345678") device.MC(user=b"12345678")
def test_missing_rp(device): def test_missing_rp(device):
with pytest.raises(CtapError) as e: with pytest.raises(CtapError) as e:
@@ -71,7 +69,7 @@ def test_bad_type_rp(device):
def test_missing_pubKeyCredParams(device): def test_missing_pubKeyCredParams(device):
with pytest.raises(CtapError) as e: with pytest.raises(CtapError) as e:
device.doMC(key_params=None) device.MC(key_params=None)
assert e.value.code == CtapError.ERR.MISSING_PARAMETER 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): def test_bad_type_rp_name(device):
with pytest.raises(CtapError) as e: 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): def test_bad_type_rp_id(device):
with pytest.raises(CtapError) as e: with pytest.raises(CtapError) as e:
device.doMC(rp={"id": 8, "name": "name", "icon": "icon"}) device.MC(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})
def test_bad_type_user_name(device): def test_bad_type_user_name(device):
with pytest.raises(CtapError) as e: 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): def test_bad_type_user_id(device):
with pytest.raises(CtapError) as e: 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): def test_bad_type_user_displayName(device):
with pytest.raises(CtapError) as e: with pytest.raises(CtapError) as e:
device.doMC(user={"id": "user_id", "name": "name", "displayName": 8}) device.MC(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"])
@pytest.mark.parametrize( @pytest.mark.parametrize(
"alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM, EdDSA.ALGORITHM] "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): def test_missing_pubKeyCredParams_type(device):
with pytest.raises(CtapError) as e: 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 assert e.value.code == CtapError.ERR.INVALID_CBOR
def test_missing_pubKeyCredParams_alg(device): def test_missing_pubKeyCredParams_alg(device):
with pytest.raises(CtapError) as e: 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 [ assert e.value.code in [
CtapError.ERR.INVALID_CBOR, CtapError.ERR.INVALID_CBOR,
@@ -147,7 +133,7 @@ def test_missing_pubKeyCredParams_alg(device):
def test_bad_type_pubKeyCredParams_alg(device): def test_bad_type_pubKeyCredParams_alg(device):
with pytest.raises(CtapError) as e: 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 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 assert e.value.code == CtapError.ERR.UNSUPPORTED_ALGORITHM
def test_exclude_list(resetdevice): 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): 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): def test_bad_type_exclude_list(device):
with pytest.raises(CtapError) as e: with pytest.raises(CtapError) as e:
device.doMC(exclude_list=["1234"]) device.MC(exclude_list=["1234"])
def test_missing_exclude_list_type(device): def test_missing_exclude_list_type(device):
with pytest.raises(CtapError) as e: 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): def test_missing_exclude_list_id(device):
with pytest.raises(CtapError) as e: 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): def test_bad_type_exclude_list_id(device):
with pytest.raises(CtapError) as e: 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): def test_bad_type_exclude_list_type(device):
with pytest.raises(CtapError) as e: with pytest.raises(CtapError) as e:

View File

@@ -31,10 +31,10 @@ def test_authenticate(device):
AUTRes = device.authenticate(credentials) AUTRes = device.authenticate(credentials)
def test_assertion_auth_data(GARes): 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): 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): def test_that_user_credential_and_numberOfCredentials_are_not_present(device, MCRes):
res = device.GA(allow_list=[ 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 """ """ Check that authenticator filters and stores items in allow list correctly """
allow_list = [] allow_list = []
rp1 = {"id": "rp1.com", "name": "rp1.com"} rp1 = {"id": "example.com", "name": "rp1.com"}
rp2 = {"id": "rp2.com", "name": "rp2.com"} rp2 = {"id": "example.com", "name": "rp2.com"}
rp1_registrations = [] rp1_registrations = []
rp2_registrations = [] rp2_registrations = []
@@ -127,7 +127,7 @@ def test_mismatched_rp(device, GARes):
rp_id += ".com" rp_id += ".com"
with pytest.raises(CtapError) as e: 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 assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_missing_rp(device): def test_missing_rp(device):
@@ -137,7 +137,7 @@ def test_missing_rp(device):
def test_bad_rp(device): def test_bad_rp(device):
with pytest.raises(CtapError) as e: 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): def test_missing_cdh(device):
with pytest.raises(CtapError) as e: with pytest.raises(CtapError) as e:
@@ -150,11 +150,11 @@ def test_bad_cdh(device):
def test_bad_allow_list(device): def test_bad_allow_list(device):
with pytest.raises(CtapError) as e: 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): def test_bad_allow_list_item(device, MCRes):
with pytest.raises(CtapError) as e: 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"} {"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) assert res.auth_data.flags & (1 << 0)
def test_allow_list_fake_item(device, MCRes): 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"} {"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): def test_allow_list_missing_field(device, MCRes):
with pytest.raises(CtapError) as e: 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"} {"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): def test_allow_list_id_wrong_type(device, MCRes):
with pytest.raises(CtapError) as e: 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"} {"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): def test_allow_list_missing_id(device, MCRes):
with pytest.raises(CtapError) as e: 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"} {"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() device.reset()
# It returns a silent authentication # 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): def test_resident_key(MCRes_DC, info):
@@ -85,7 +86,7 @@ def test_multiple_rk_nodisplay(device, MCRes_DC):
auths = [] auths = []
regs = [] regs = []
# Use unique RP to not collide with other credentials # 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): for i in range(0, 3):
res = device.doMC(rp=rp, rk=True, user=generate_random_user()) res = device.doMC(rp=rp, rk=True, user=generate_random_user())
regs.append(res) regs.append(res)
@@ -116,7 +117,7 @@ def test_rk_maximum_size_nodisplay(device):
auths = resGA.get_assertions() auths = resGA.get_assertions()
user_max_GA = auths[0] user_max_GA = auths[0]
print(auths)
for y in ("name", "displayName", "id"): for y in ("name", "displayName", "id"):
if (y in user_max_GA): if (y in user_max_GA):
assert user_max_GA.user[y] == user_max[y] 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 Test maximum returned capacity of the RK for the given RP
""" """
device.reset()
# Try to determine from get_info, or default to 19. # Try to determine from get_info, or default to 19.
RK_CAPACITY_PER_RP = info.max_creds_in_list RK_CAPACITY_PER_RP = info.max_creds_in_list
if not RK_CAPACITY_PER_RP: 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 return user
# Use unique RP to not collide with other credentials from other tests. # 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) # req = FidoRequest(MCRes_DC, options=None, user=get_user(), rp = rp)
# res = device.sendGA(*req.toGA()) # 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_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_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 = [ 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: 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 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"} rp = {"id": "overwrite.org", "name": "Example"}
user = generate_random_user() 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. # 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'] 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 = generate_random_user()
user['icon'] = 'https://www.w3.org/TR/webauthn/?icon=' + ("A" * 128) 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): def test_returned_credential(device):

View File

@@ -21,6 +21,7 @@
import pytest import pytest
from fido2.ctap import CtapError from fido2.ctap import CtapError
from fido2.ctap2.pin import PinProtocolV2, ClientPin from fido2.ctap2.pin import PinProtocolV2, ClientPin
from fido2.utils import websafe_decode
from utils import verify from utils import verify
import os import os
@@ -46,22 +47,24 @@ def GACredBlob(device, MCCredBlob):
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def MCLBK(device): def MCLBK(device):
res = device.doMC( mc = device.doMC(
rk=True, rk=True,
extensions={'largeBlob':{'support':'required'}} extensions={'largeBlob':{'support':'required'}}
)['res'] )
return res res = mc['res']
ext = mc['client_extension_results']
return {'res': res, 'ext': ext}
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def GALBRead(device, MCLBK): def GALBRead(device, MCLBK):
res = device.doGA( res = device.doGA(
allow_list=[ 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}} ],extensions={'largeBlob':{'read': True}}
) )
assertions = res['res'].get_assertions() assertions = res['res'].get_assertions()
for a in 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'] return res['res']
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
@@ -70,18 +73,19 @@ def GALBReadLBK(GALBRead):
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def GALBReadLB(GALBRead): def GALBReadLB(GALBRead):
print(GALBRead.get_response(0))
return GALBRead.get_response(0) return GALBRead.get_response(0)
@pytest.fixture(scope="function") @pytest.fixture(scope="function")
def GALBWrite(device, MCLBK): def GALBWrite(device, MCLBK):
res = device.doGA( res = device.doGA(
allow_list=[ 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}} ],extensions={'largeBlob':{'write': LARGE_BLOB}}
) )
assertions = res['res'].get_assertions() assertions = res['res'].get_assertions()
for a in 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) return res['res'].get_response(0)
def test_supports_credblob(info): 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) assert info.max_large_blob is None or (info.max_large_blob > 1024)
def test_get_largeblobkey_mc(MCLBK): def test_get_largeblobkey_mc(MCLBK):
assert 'supported' in MCLBK.extension_results assert 'largeBlob' in MCLBK['ext']
assert MCLBK.extension_results['supported'] is True assert 'supported' in MCLBK['ext']['largeBlob']
assert MCLBK['ext']['largeBlob']['supported'] is True
def test_get_largeblobkey_ga(GALBReadLBK): def test_get_largeblobkey_ga(GALBReadLBK):
assert GALBReadLBK.large_blob_key is not None assert GALBReadLBK.large_blob_key is not None
def test_get_largeblob_rw(GALBWrite, GALBReadLB): def test_get_largeblob_rw(GALBWrite, GALBReadLB):
assert 'written' in GALBWrite.extension_results assert 'largeBlob' in GALBWrite.client_extension_results
assert GALBWrite.extension_results['written'] is True assert 'written' in GALBWrite.client_extension_results['largeBlob']
assert GALBWrite.client_extension_results['largeBlob']['written'] is True
assert 'blob' in GALBReadLB.extension_results assert 'blob' in GALBReadLB.client_extension_results['largeBlob']
assert GALBReadLB.extension_results['blob'] == LARGE_BLOB 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.ctap2.extensions import CredProtectExtension
from fido2.webauthn import UserVerificationRequirement from fido2.webauthn import UserVerificationRequirement
from fido2.ctap import CtapError from fido2.ctap import CtapError
from utils import generate_random_user
class CredProtect: class CredProtect:
UserVerificationOptional = 1 UserVerificationOptional = 1
@@ -30,140 +31,139 @@ class CredProtect:
@pytest.fixture(scope="class") @pytest.fixture(scope="class")
def MCCredProtectOptional(resetdevice): 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 return res
@pytest.fixture(scope="class") @pytest.fixture(scope="class")
def MCCredProtectOptionalList(resetdevice): 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 return res
@pytest.fixture(scope="class") @pytest.fixture(scope="class")
def MCCredProtectRequired(resetdevice): 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 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): def test_credprotect_make_credential_2(self, MCCredProtectOptionalList):
assert MCCredProtectOptional.auth_data.extensions assert MCCredProtectOptionalList.auth_data.extensions
assert "credProtect" in MCCredProtectOptional.auth_data.extensions assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions
assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1 assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2
def test_credprotect_make_credential_2(MCCredProtectOptionalList): def test_credprotect_make_credential_3(self, MCCredProtectRequired):
assert MCCredProtectOptionalList.auth_data.extensions assert MCCredProtectRequired.auth_data.extensions
assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions assert "credProtect" in MCCredProtectRequired.auth_data.extensions
assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2 assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3
def test_credprotect_make_credential_3(MCCredProtectRequired): def test_credprotect_optional_excluded(self, device, MCCredProtectOptional):
assert MCCredProtectRequired.auth_data.extensions """ CredProtectOptional Cred should be visible to be excluded with no UV """
assert "credProtect" in MCCredProtectRequired.auth_data.extensions exclude_list = [
assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3 {
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
def test_credprotect_optional_excluded(device, MCCredProtectOptional): with pytest.raises(CtapError) as e:
""" CredProtectOptional Cred should be visible to be excluded with no UV """ device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)
exclude_list = [
{
"id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
with pytest.raises(CtapError) as e: assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)
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): with pytest.raises(CtapError) as e:
""" CredProtectOptionalList Cred should be visible to be excluded with no UV """ device.MC(options={'rk': True}, extensions={'credProtect': CredProtect.UserVerificationOptionalWithCredentialId}, exclude_list=exclude_list)
exclude_list = [
{
"id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
with pytest.raises(CtapError) as e: assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST}, exclude_list=exclude_list)
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): # works
""" CredProtectRequired Cred should NOT be visible to be excluded with no UV """ device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)
exclude_list = [
{
"id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
"type": "public-key",
}
]
# works def test_credprotect_optional_works_with_no_allowList_no_uv(self, device, MCCredProtectOptional):
device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)
def test_credprotect_optional_works_with_no_allowList_no_uv(device, MCCredProtectOptional): # works
res = device.doGA()['res'].get_assertions()[0]
# works # If there's only one credential, this is None
res = device.doGA()['res'].get_assertions()[0] assert res.number_of_credentials == None
# If there's only one credential, this is None def test_credprotect_optional_and_list_works_no_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired):
assert res.number_of_credentials == None 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): results = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()
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)['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. def test_hmac_secret_and_credProtect_make_credential(self, resetdevice, MCCredProtectOptional):
for res in results:
assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:]
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"]: class TestCredProtectUv:
assert res.auth_data.extensions def test_credprotect_all_with_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin):
assert ext in res.auth_data.extensions allow_list = [
assert res.auth_data.extensions[ext] == True {
"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): client_pin.set_pin(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" res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]
client_pin.set_pin(pin) assert res1.number_of_credentials in (None, 3)
res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]
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.utils import hmac_sha256
from fido2.ctap2.pin import PinProtocolV2 from fido2.ctap2.pin import PinProtocolV2
from fido2.webauthn import UserVerificationRequirement from fido2.webauthn import UserVerificationRequirement
from fido2.client import ClientError
from utils import * from utils import *
salt1 = b"\xa5" * 32 salt1 = b"\xa5" * 32
@@ -38,10 +39,6 @@ def MCHmacSecret(resetdevice):
res = resetdevice.doMC(extensions={"hmacCreateSecret": True},rk=True) res = resetdevice.doMC(extensions={"hmacCreateSecret": True},rk=True)
return res['res'].attestation_object 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): def test_hmac_secret_make_credential(MCHmacSecret):
assert MCHmacSecret.auth_data.extensions assert MCHmacSecret.auth_data.extensions
assert "hmac-secret" in 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)]) @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]} hout = {'salt1':salts[0]}
if (len(salts) > 1): if (len(salts) > 1):
hout['salt2'] = salts[1] hout['salt2'] = salts[1]
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0) auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
ext = auth.extension_results ext = auth.client_extension_results
assert ext assert ext
assert "hmacGetSecret" in 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: if len(salts) == 1:
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 4.5 assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 4.5
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5 assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
if len(salts) == 2: if len(salts) == 2:
assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 5.4 assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 5.4
assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5 assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
assert shannon_entropy(ext["hmacGetSecret"]['output2']) > 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]} hout = {'salt1':salts[0]}
if (len(salts) > 1): if (len(salts) > 1):
hout['salt2'] = salts[1] hout['salt2'] = salts[1]
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0) auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
ext = auth.extension_results ext = auth.client_extension_results
assert ext assert ext
assert "hmacGetSecret" in 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: if len(salts) == 2:
return ext["hmacGetSecret"]['output1'], ext["hmacGetSecret"]['output2'] return ext.hmac_get_secret.output1, ext.hmac_get_secret.output2
else: else:
return ext["hmacGetSecret"]['output1'] return ext.hmac_get_secret.output1
def test_hmac_secret_sanity(device, MCHmacSecret, hmac): def test_hmac_secret_sanity(device, MCHmacSecret):
output1 = get_output(device, MCHmacSecret, hmac, (salt1,)) output1 = get_output(device, MCHmacSecret, (salt1,))
output12 = get_output( output12 = get_output(
device, MCHmacSecret, hmac, (salt1, salt2) device, MCHmacSecret, (salt1, salt2)
) )
output21 = get_output( output21 = get_output(
device, MCHmacSecret, hmac, (salt2, salt1) device, MCHmacSecret, (salt2, salt1)
) )
assert output12[0] == output1 assert output12[0] == output1
@@ -107,60 +104,60 @@ def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
assert output21[0] == output12[1] assert output21[0] == output12[1]
assert output12[0] != output12[1] assert output12[0] != output12[1]
def test_missing_keyAgreement(device, hmac): def test_missing_keyAgreement(device):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
with pytest.raises(CtapError): 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): def test_missing_saltAuth(device):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
with pytest.raises(CtapError) as e: 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 assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_missing_saltEnc(device, hmac): def test_missing_saltEnc(device,):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
with pytest.raises(CtapError) as e: 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 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}}) key_agreement = {
bad_auth = list(hout[3][:]) 1: 2,
bad_auth[len(bad_auth) // 2] = bad_auth[len(bad_auth) // 2] ^ 1 3: -25, # Per the spec, "although this is NOT the algorithm actually used"
bad_auth = bytes(bad_auth) -1: 1,
-2: b'\x00'*32,
-3: b'\x00'*32,
}
with pytest.raises(CtapError) as e: 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 assert e.value.code == CtapError.ERR.EXTENSION_FIRST
@pytest.mark.parametrize("salts", [(salt4,), (salt4, salt5)]) @pytest.mark.parametrize("salts", [(salt4,), (salt4, salt5)])
def test_invalid_salt_length(device, hmac, salts): def test_invalid_salt_length(device, salts):
with pytest.raises(ValueError) as e: with pytest.raises((CtapError,ClientError)) as e:
if (len(salts) == 2): if (len(salts) == 2):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}}) hout = {"salt1":salts[0],"salt2":salts[1]}
else: else:
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}}) hout = {"salt1":salts[0]}
device.doGA(extensions={"hmacGetSecret": hout}) device.doGA(extensions={"hmacGetSecret": hout})
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)]) @pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
def test_get_next_assertion_has_extension( def test_get_next_assertion_has_extension(
device, hmac, salts device, salts
): ):
""" Check that get_next_assertion properly returns extension information for multiple accounts. """ """ Check that get_next_assertion properly returns extension information for multiple accounts. """
if (len(salts) == 2): if (len(salts) == 2):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}}) hout = {"salt1":salts[0],"salt2":salts[1]}
else: else:
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}}) hout = {"salt1":salts[0]}
accounts = 3 accounts = 3
regs = [] regs = []
auths = [] 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)] fixed_users = [generate_random_user() for _ in range(accounts)]
for i in range(accounts): for i in range(accounts):
res = device.doMC(extensions={"hmacCreateSecret": True}, res = device.doMC(extensions={"hmacCreateSecret": True},
@@ -183,21 +180,19 @@ def test_get_next_assertion_has_extension(
assert "hmac-secret" in ext assert "hmac-secret" in ext
assert isinstance(ext["hmac-secret"], bytes) assert isinstance(ext["hmac-secret"], bytes)
assert len(ext["hmac-secret"]) == len(salts) * 32 + 16 assert len(ext["hmac-secret"]) == len(salts) * 32 + 16
key = hmac.process_get_output(x)
def test_hmac_secret_different_with_uv(device, MCHmacSecret):
def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac):
salts = [salt1] salts = [salt1]
if (len(salts) == 2): if (len(salts) == 2):
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}}) hout = {"salt1":salts[0],"salt2":salts[1]}
else: else:
hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}}) hout = {"salt1":salts[0]}
auth_no_uv = device.GA(extensions={"hmac-secret": hout})['res'] auth_no_uv = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
assert (auth_no_uv.auth_data.flags & (1 << 2)) == 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 ext_no_uv
assert "hmac-secret" in ext_no_uv assert "hmac-secret" in ext_no_uv
assert isinstance(ext_no_uv["hmac-secret"], bytes) 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] hout['salt2'] = salts[1]
auth_uv = device.doGA(extensions={"hmacGetSecret": hout}, user_verification=UserVerificationRequirement.REQUIRED)['res'].get_response(0) auth_uv = device.doGA(extensions={"hmacGetSecret": hout}, user_verification=UserVerificationRequirement.REQUIRED)['res'].get_response(0)
assert auth_uv.authenticator_data.flags & (1 << 2) assert auth_uv.response.authenticator_data.flags & (1 << 2)
ext_uv = auth_uv.extension_results ext_uv = auth_uv.client_extension_results
assert ext_uv assert ext_uv
assert "hmacGetSecret" in 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 # Now see if the hmac-secrets are different
assert ext_no_uv["hmac-secret"][:32] != ext_uv["hmacGetSecret"]['output1'] 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=[ res = device.doGA(ctap1=False, allow_list=[
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"} {"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 # Test FIDO2 register works with U2F auth

View File

@@ -3,6 +3,6 @@
/usr/sbin/pcscd & /usr/sbin/pcscd &
sleep 2 sleep 2
rm -f memory.flash 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 & ./build_in_docker/pico_fido > /dev/null &
pytest tests pytest tests

View File

@@ -24,7 +24,7 @@ import argparse
import platform import platform
from binascii import hexlify from binascii import hexlify
from threading import Event from threading import Event
from typing import Mapping, Any, Optional, Callable from typing import List, Mapping, Any, Optional, Callable
import struct import struct
import urllib.request import urllib.request
import json import json
@@ -73,21 +73,21 @@ def get_pki_data(url, data=None, method='GET'):
return j return j
class VendorConfig(Config): class VendorConfig(Config):
class PARAM(IntEnum): class PARAM(IntEnum):
VENDOR_COMMAND_ID = 0x01 VENDOR_COMMAND_ID = 0x01
VENDOR_AUT_CT = 0x02 VENDOR_PARAM_BYTESTRING = 0x02
VENDOR_PARAM = 0x02 VENDOR_PARAM_INT = 0x03
VENDOR_PARAM_TEXTSTRING = 0x04
class CMD(IntEnum): class CMD(IntEnum):
CONFIG_AUT_ENABLE = 0x03e43f56b34285e2 CONFIG_AUT_ENABLE = 0x03e43f56b34285e2
CONFIG_AUT_DISABLE = 0x1831a40f04a25ed9 CONFIG_AUT_DISABLE = 0x1831a40f04a25ed9
CONFIG_VENDOR_PROTOTYPE = 0x7f CONFIG_EA_UPLOAD = 0x66f2a674c29a8dcf
CONFIG_VENDOR_PHY = 0x1b
CONFIG_PHY_VIDPID = 0x6fcb19b0cbe3acfa CONFIG_PHY_VIDPID = 0x6fcb19b0cbe3acfa
CONFIG_PHY_OPTS = 0x969f3b09eceb805f
CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948
CONFIG_PHY_LED_BTNESS = 0x76a85945985d02fd CONFIG_PHY_LED_BTNESS = 0x76a85945985d02fd
CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948
CONFIG_PHY_OPTS = 0x269f3b09eceb805f
CONFIG_PIN_POLICY = 0x6c07d70fe96c3897
class RESP(IntEnum): class RESP(IntEnum):
KEY_AGREEMENT = 0x01 KEY_AGREEMENT = 0x01
@@ -97,16 +97,16 @@ class VendorConfig(Config):
def enable_device_aut(self, ct): def enable_device_aut(self, ct):
self._call( self._call(
VendorConfig.CMD.CONFIG_VENDOR_PROTOTYPE, Config.CMD.VENDOR_PROTOTYPE,
{ {
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_ENABLE, 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): def disable_device_aut(self):
self._call( self._call(
VendorConfig.CMD.CONFIG_VENDOR_PROTOTYPE, Config.CMD.VENDOR_PROTOTYPE,
{ {
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_DISABLE VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_DISABLE
}, },
@@ -114,40 +114,63 @@ class VendorConfig(Config):
def vidpid(self, vid, pid): def vidpid(self, vid, pid):
self._call( self._call(
VendorConfig.CMD.CONFIG_VENDOR_PHY, Config.CMD.VENDOR_PROTOTYPE,
{ {
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_VIDPID, 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): def led_gpio(self, gpio):
self._call( 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_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_GPIO,
VendorConfig.PARAM.VENDOR_PARAM: gpio VendorConfig.PARAM.VENDOR_PARAM_INT: gpio
}, },
) )
def led_brightness(self, brightness): def led_brightness(self, brightness):
self._call( 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_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_BTNESS,
VendorConfig.PARAM.VENDOR_PARAM: brightness VendorConfig.PARAM.VENDOR_PARAM_INT: brightness
}, },
) )
def phy_opts(self, opts): def phy_opts(self, opts):
self._call( self._call(
VendorConfig.CMD.CONFIG_VENDOR_PHY, Config.CMD.VENDOR_PROTOTYPE,
{ {
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_OPTS, 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): class Ctap2Vendor(Ctap2):
def __init__(self, device: CtapDevice, strict_cbor: bool = True): def __init__(self, device: CtapDevice, strict_cbor: bool = True):
super().__init__(device=device, strict_cbor=strict_cbor) super().__init__(device=device, strict_cbor=strict_cbor)
@@ -245,7 +268,6 @@ class Vendor:
DISABLE = 0x02 DISABLE = 0x02
KEY_AGREEMENT = 0x01 KEY_AGREEMENT = 0x01
EA_CSR = 0x01 EA_CSR = 0x01
EA_UPLOAD = 0x02
class RESP(IntEnum): class RESP(IntEnum):
PARAM = 0x01 PARAM = 0x01
@@ -433,13 +455,7 @@ class Vendor:
)[Vendor.RESP.PARAM] )[Vendor.RESP.PARAM]
def upload_ea(self, der): def upload_ea(self, der):
self._call( self.vcfg.upload_ea(der)
Vendor.CMD.VENDOR_EA,
Vendor.SUBCMD.EA_UPLOAD,
{
Vendor.PARAM.PARAM: der
}
)
def vidpid(self, vid, pid): def vidpid(self, vid, pid):
return self.vcfg.vidpid(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] } 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(): def parse_args():
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
subparser = parser.add_subparsers(title="commands", dest="command") 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_backup.add_argument('filename', help='File to save or load the backup.')
parser_attestation = subparser.add_parser('attestation', help='Manages Enterprise Attestation') 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_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.') 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_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() args = parser.parse_args()
return args return args
def secure(vdr, args): def secure(vdr: Vendor, args):
if (args.subcommand == 'enable'): if (args.subcommand == 'enable'):
vdr.enable_device_aut() vdr.enable_device_aut()
elif (args.subcommand == 'unlock'): elif (args.subcommand == 'unlock'):
@@ -524,13 +560,13 @@ def secure(vdr, args):
elif (args.subcommand == 'disable'): elif (args.subcommand == 'disable'):
vdr.disable_device_aut() vdr.disable_device_aut()
def backup(vdr, args): def backup(vdr: Vendor, args):
if (args.subcommand == 'save'): if (args.subcommand == 'save'):
vdr.backup_save(args.filename) vdr.backup_save(args.filename)
elif (args.subcommand == 'load'): elif (args.subcommand == 'load'):
vdr.backup_load(args.filename) vdr.backup_load(args.filename)
def attestation(vdr, args): def attestation(vdr: Vendor, args):
if (args.subcommand == 'csr'): if (args.subcommand == 'csr'):
if (args.filename is None): if (args.filename is None):
csr = x509.load_der_x509_csr(vdr.csr()) csr = x509.load_der_x509_csr(vdr.csr())
@@ -545,8 +581,10 @@ def attestation(vdr, args):
except ValueError: except ValueError:
cert = x509.load_pem_x509_certificate(dataf) cert = x509.load_pem_x509_certificate(dataf)
vdr.upload_ea(cert.public_bytes(Encoding.DER)) 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 val = args.value if 'value' in args else None
if (val): if (val):
if (args.subcommand == 'vidpid'): if (args.subcommand == 'vidpid'):
@@ -570,7 +608,7 @@ def phy(vdr, args):
else: else:
print('Command executed successfully. Please, restart your Pico Key.') print('Command executed successfully. Please, restart your Pico Key.')
def memory(vdr, args): def memory(vdr: Vendor, args):
mem = vdr.memory() mem = vdr.memory()
print(f'Memory usage:') print(f'Memory usage:')
print(f'\tFree: {mem["free"]/1024:.2f} kilobytes ({mem["free"]*100/mem["total"]:.2f}%)') 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'\tFlash size: {mem["size"]/1024:.2f} kilobytes')
print(f'\tFiles: {mem["files"]}') 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): def main(args):
print('Pico Fido Tool v1.10') print('Pico Fido Tool v1.10')
print('Author: Pol Henarejos') print('Author: Pol Henarejos')
@@ -602,6 +648,9 @@ def main(args):
phy(vdr, args) phy(vdr, args)
elif (args.command == 'memory'): elif (args.command == 'memory'):
memory(vdr, args) memory(vdr, args)
elif (args.command == 'pin_policy'):
pin_policy(vdr, args)
def run(): def run():
args = parse_args() args = parse_args()