diff --git a/CMakeLists.txt b/CMakeLists.txt
index 52ca50b..93402f2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -35,11 +35,6 @@ project(pico_fido C CXX ASM)
set(CMAKE_C_STANDARD 11)
set(CMAKE_CXX_STANDARD 17)
-if(ENABLE_EMULATION)
-else()
-pico_sdk_init()
-endif()
-
add_executable(pico_fido)
endif()
@@ -156,6 +151,12 @@ if(ENABLE_EMULATION)
target_link_options(pico_fido PUBLIC
-Wl,-dead_strip
)
+ if(DEBUG_APDU)
+ target_compile_options(pico_fido PUBLIC
+ -fsanitize=address -g -O1 -fno-omit-frame-pointer)
+ target_link_options(pico_fido PUBLIC
+ -fsanitize=address -g -O1 -fno-omit-frame-pointer)
+ endif()
else()
target_link_options(pico_fido PUBLIC
-Wl,--gc-sections
diff --git a/src/fido/cbor.c b/src/fido/cbor.c
index c7ac7f6..29a1b98 100644
--- a/src/fido/cbor.c
+++ b/src/fido/cbor.c
@@ -16,7 +16,7 @@
*/
#include "pico_keys.h"
-#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
+#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif
#include "hid/ctap_hid.h"
@@ -104,7 +104,8 @@ int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
return CTAP1_ERR_INVALID_CMD;
}
-void cbor_thread(void) {
+void *cbor_thread(void *arg) {
+ (void)arg;
card_init_core1();
while (1) {
uint32_t m;
@@ -115,17 +116,17 @@ void cbor_thread(void) {
if (m == EV_EXIT) {
break;
}
- apdu.sw = cbor_parse(cbor_cmd, cbor_data, cbor_len);
+ apdu.sw = (uint16_t)cbor_parse(cbor_cmd, cbor_data, cbor_len);
if (apdu.sw == 0) {
DEBUG_DATA(res_APDU, res_APDU_size);
}
else {
if (apdu.sw >= CTAP1_ERR_INVALID_CHANNEL) {
- res_APDU[-1] = apdu.sw;
+ res_APDU[-1] = (uint8_t)apdu.sw;
apdu.sw = 0;
}
else {
- res_APDU[0] = apdu.sw;
+ res_APDU[0] = (uint8_t)apdu.sw;
}
}
@@ -137,6 +138,7 @@ void cbor_thread(void) {
#ifdef ESP_PLATFORM
vTaskDelete(NULL);
#endif
+ return NULL;
}
int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) {
@@ -214,6 +216,9 @@ CborError COSE_key(mbedtls_ecp_keypair *key, CborEncoder *mapEncoderParent,
else if (key->grp.id == MBEDTLS_ECP_DP_ED25519) {
alg = FIDO2_ALG_EDDSA;
}
+ else if (key->grp.id == MBEDTLS_ECP_DP_ED448) {
+ alg = FIDO2_ALG_ED448;
+ }
#endif
return COSE_key_params(crv, alg, &key->grp, &key->Q, mapEncoderParent, mapEncoder);
}
diff --git a/src/fido/cbor_client_pin.c b/src/fido/cbor_client_pin.c
index 512c34b..75fcd62 100644
--- a/src/fido/cbor_client_pin.c
+++ b/src/fido/cbor_client_pin.c
@@ -15,6 +15,7 @@
* along with this program. If not, see .
*/
+#include "pico_keys.h"
#ifndef ESP_PLATFORM
#include "common.h"
#else
@@ -27,7 +28,7 @@
#include "cbor.h"
#include "ctap.h"
#include "ctap2_cbor.h"
-#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
+#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include "hid/ctap_hid.h"
@@ -35,7 +36,6 @@
#include "files.h"
#include "random.h"
#include "crypto_utils.h"
-#include "pico_keys.h"
#include "apdu.h"
#include "kek.h"
@@ -44,6 +44,7 @@ uint32_t max_usage_time_period = 600 * 1000;
bool needs_power_cycle = false;
static mbedtls_ecdh_context hkey;
static bool hkey_init = false;
+extern int encrypt_keydev_f1(const uint8_t keydev[32]);
int beginUsingPinUvAuthToken(bool userIsPresent) {
paut.user_present = userIsPresent;
@@ -105,11 +106,7 @@ int regenerate() {
mbedtls_ecdh_init(&hkey);
hkey_init = true;
mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1);
- int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp,
- &hkey.ctx.mbed_ecdh.d,
- &hkey.ctx.mbed_ecdh.Q,
- random_gen,
- NULL);
+ int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_gen, NULL);
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
if (ret != 0) {
return ret;
@@ -125,34 +122,15 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
return ret;
}
if (protocol == 1) {
- return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
- buf,
- sizeof(buf),
- sharedSecret);
+ return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), buf, sizeof(buf), sharedSecret);
}
else if (protocol == 2) {
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
- ret = mbedtls_hkdf(md_info,
- NULL,
- 0,
- buf,
- sizeof(buf),
- (uint8_t *) "CTAP2 HMAC key",
- 14,
- sharedSecret,
- 32);
+ ret = mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 HMAC key", 14, sharedSecret, 32);
if (ret != 0) {
return ret;
}
- return mbedtls_hkdf(md_info,
- NULL,
- 0,
- buf,
- sizeof(buf),
- (uint8_t *) "CTAP2 AES key",
- 13,
- sharedSecret + 32,
- 32);
+ return mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 AES key", 13, sharedSecret + 32, 32);
}
return -1;
}
@@ -160,26 +138,38 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret) {
mbedtls_mpi z;
mbedtls_mpi_init(&z);
- int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp,
- &z,
- Q,
- &hkey.ctx.mbed_ecdh.d,
- random_gen,
- NULL);
+ int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, &z, Q, &hkey.ctx.mbed_ecdh.d, random_gen, NULL);
ret = kdf(protocol, &z, sharedSecret);
mbedtls_mpi_free(&z);
return ret;
}
-int resetPinUvAuthToken() {
+void resetAuthToken(bool persistent) {
+ uint16_t fid = EF_AUTHTOKEN;
+ if (persistent) {
+ fid = EF_PAUTHTOKEN;
+ }
+ file_t *ef = search_by_fid(fid, NULL, SPECIFY_EF);
uint8_t t[32];
random_gen(NULL, t, sizeof(t));
- file_put_data(ef_authtoken, t, sizeof(t));
+ file_put_data(ef, t, sizeof(t));
+ low_flash_available();
+}
+
+int resetPinUvAuthToken() {
+ resetAuthToken(false);
paut.permissions = 0;
paut.data = file_get_data(ef_authtoken);
paut.len = file_get_size(ef_authtoken);
+ return 0;
+}
- low_flash_available();
+int resetPersistentPinUvAuthToken() {
+ resetAuthToken(true);
+ file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF);
+ ppaut.permissions = 0;
+ ppaut.data = file_get_data(ef_pauthtoken);
+ ppaut.len = file_get_size(ef_pauthtoken);
return 0;
}
@@ -210,11 +200,7 @@ int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in
return -1;
}
-int authenticate(uint8_t protocol,
- const uint8_t *key,
- const uint8_t *data,
- size_t len,
- uint8_t *sign) {
+int authenticate(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign) {
uint8_t hmac[32];
int ret =
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac);
@@ -242,10 +228,10 @@ int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t l
return ret;
}
if (protocol == 1) {
- return memcmp(sign, hmac, 16);
+ return ct_memcmp(sign, hmac, 16);
}
else if (protocol == 2) {
- return memcmp(sign, hmac, 32);
+ return ct_memcmp(sign, hmac, 32);
}
return -1;
}
@@ -280,17 +266,15 @@ int pinUvAuthTokenUsageTimerObserver() {
return 0;
}
-int check_mkek_encrypted(const uint8_t *dhash) {
- if (file_get_size(ef_mkek) == MKEK_IV_SIZE + MKEK_KEY_SIZE) {
- hash_multi(dhash, 16, session_pin); // Only for storing MKEK
- uint8_t mkek[MKEK_SIZE] = {0};
- memcpy(mkek, file_get_data(ef_mkek), MKEK_IV_SIZE + MKEK_KEY_SIZE);
- int ret = store_mkek(mkek);
- mbedtls_platform_zeroize(mkek, sizeof(mkek));
- mbedtls_platform_zeroize(session_pin, sizeof(session_pin));
- if (ret != PICOKEY_OK) {
- return CTAP2_ERR_PIN_AUTH_INVALID;
- }
+int check_keydev_encrypted(const uint8_t pin_token[32]) {
+ if (file_get_data(ef_keydev) && *file_get_data(ef_keydev) == 0x01) {
+ uint8_t tmp_keydev[61];
+ tmp_keydev[0] = 0x02; // Change format to encrypted
+ encrypt_with_aad(pin_token, file_get_data(ef_keydev) + 1, 32, tmp_keydev + 1);
+ DEBUG_DATA(tmp_keydev, sizeof(tmp_keydev));
+ file_put_data(ef_keydev, tmp_keydev, sizeof(tmp_keydev));
+ mbedtls_platform_zeroize(tmp_keydev, sizeof(tmp_keydev));
+ low_flash_available();
}
return PICOKEY_OK;
}
@@ -305,11 +289,11 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CborEncoder encoder, mapEncoder;
CborValue map;
CborError error = CborNoError;
- CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 },
- kay = { 0 };
+ CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 }, kay = { 0 };
CborCharString rpId = { 0 };
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
uint64_t val_c = 1;
+ uint8_t keydev[32] = {0};
if (hkey_init == false) {
initialize();
}
@@ -431,15 +415,17 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if (pin_len < minPin) {
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
}
- uint8_t hsh[34], dhash[32];
+ uint8_t hsh[35], dhash[32];
hsh[0] = MAX_PIN_RETRIES;
hsh[1] = pin_len;
+ hsh[2] = 1; // New format indicator
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
- double_hash_pin(dhash, 16, hsh + 2);
- file_put_data(ef_pin, hsh, 2 + 32);
+ pin_derive_verifier(dhash, 16, hsh + 3);
+ file_put_data(ef_pin, hsh, sizeof(hsh));
low_flash_available();
- ret = check_mkek_encrypted(dhash);
+ pin_derive_session(dhash, 16, session_pin);
+ ret = check_keydev_encrypted(session_pin);
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
@@ -486,10 +472,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
- uint8_t pin_data[34];
- memcpy(pin_data, file_get_data(ef_pin), 34);
+ uint8_t pin_data[35];
+ memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
pin_data[0] -= 1;
- file_put_data(ef_pin, pin_data, sizeof(pin_data));
+ file_put_data(ef_pin, pin_data, file_get_size(ef_pin));
low_flash_available();
uint8_t retries = pin_data[0];
uint8_t paddedNewPin[64];
@@ -498,9 +484,16 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
- uint8_t dhash[32];
- double_hash_pin(paddedNewPin, 16, dhash);
- if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) {
+ uint8_t dhash[32], off = 3;
+ if (file_get_size(ef_pin) == 34) {
+ off = 2;
+ double_hash_pin(paddedNewPin, 16, dhash);
+ }
+ else {
+ pin_derive_verifier(paddedNewPin, 16, dhash);
+ }
+
+ if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) {
regenerate();
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
if (retries == 0) {
@@ -514,10 +507,28 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
}
- hash_multi(paddedNewPin, 16, session_pin);
+ if (off == 2) {
+ // Upgrade pin file to new format
+ pin_data[2] = 1; // New format indicator
+ pin_derive_verifier(paddedNewPin, 16, pin_data + 3);
+
+ hash_multi(paddedNewPin, 16, session_pin);
+ ret = load_keydev(keydev);
+ if (ret != PICOKEY_OK) {
+ CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
+ }
+ encrypt_keydev_f1(keydev);
+ }
+ pin_derive_session(paddedNewPin, 16, session_pin);
pin_data[0] = MAX_PIN_RETRIES;
file_put_data(ef_pin, pin_data, sizeof(pin_data));
low_flash_available();
+
+ ret = check_keydev_encrypted(session_pin);
+ if (ret != PICOKEY_OK) {
+ CBOR_ERROR(ret);
+ }
+
new_pin_mismatches = 0;
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin);
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
@@ -539,35 +550,33 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if (pin_len < minPin) {
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
}
- uint8_t hsh[34];
- hsh[0] = MAX_PIN_RETRIES;
- hsh[1] = pin_len;
+
+ // New PIN is valid and verified
+ ret = load_keydev(keydev);
+ if (ret != PICOKEY_OK) {
+ CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
+ }
+ encrypt_keydev_f1(keydev);
+
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
- double_hash_pin(dhash, 16, hsh + 2);
- if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 &&
- memcmp(hsh + 2, file_get_data(ef_pin) + 2, 32) == 0) {
+ pin_derive_session(dhash, 16, session_pin);
+ ret = check_keydev_encrypted(session_pin);
+ if (ret != PICOKEY_OK) {
+ CBOR_ERROR(ret);
+ }
+ low_flash_available();
+
+ pin_data[0] = MAX_PIN_RETRIES;
+ pin_data[1] = pin_len;
+ pin_data[2] = 1; // New format indicator
+ pin_derive_verifier(dhash, 16, pin_data + 3);
+
+ if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 && ct_memcmp(pin_data + 3, file_get_data(ef_pin) + 3, 32) == 0) {
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
}
+ file_put_data(ef_pin, pin_data, sizeof(pin_data));
- uint8_t mkek[MKEK_SIZE] = {0};
- ret = load_mkek(mkek);
- if (ret != PICOKEY_OK) {
- CBOR_ERROR(ret);
- }
- file_put_data(ef_pin, hsh, 2 + 32);
-
- ret = check_mkek_encrypted(dhash);
- if (ret != PICOKEY_OK) {
- CBOR_ERROR(ret);
- }
-
- hash_multi(dhash, 16, session_pin);
- ret = store_mkek(mkek);
- mbedtls_platform_zeroize(mkek, sizeof(mkek));
- if (ret != PICOKEY_OK) {
- CBOR_ERROR(ret);
- }
- mbedtls_platform_zeroize(hsh, sizeof(hsh));
+ mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
mbedtls_platform_zeroize(dhash, sizeof(dhash));
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
uint8_t *tmpf = (uint8_t *) calloc(1, file_get_size(ef_minpin));
@@ -578,6 +587,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
}
low_flash_available();
resetPinUvAuthToken();
+ resetPersistentPinUvAuthToken();
goto err; // No return
}
else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions
@@ -598,7 +608,9 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
if ((permissions & CTAP_PERMISSION_BE)) { // Not supported yet
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
}
-
+ if ((permissions & CTAP_PERMISSION_PCMR) && permissions != CTAP_PERMISSION_PCMR) {
+ CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
+ }
}
if (!file_has_data(ef_pin)) {
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
@@ -618,10 +630,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
- uint8_t pin_data[34];
- memcpy(pin_data, file_get_data(ef_pin), 34);
+ uint8_t pin_data[35];
+ memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
pin_data[0] -= 1;
- file_put_data(ef_pin, pin_data, sizeof(pin_data));
+ file_put_data(ef_pin, pin_data, file_get_size(ef_pin));
low_flash_available();
uint8_t retries = pin_data[0];
uint8_t paddedNewPin[64], poff = ((uint8_t)pinUvAuthProtocol - 1) * IV_SIZE;
@@ -630,11 +642,18 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
- uint8_t dhash[32];
- double_hash_pin(paddedNewPin, 16, dhash);
- if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) {
+ uint8_t dhash[32], off = 3;
+ if (file_get_size(ef_pin) == 34) {
+ off = 2;
+ double_hash_pin(paddedNewPin, 16, dhash);
+ }
+ else {
+ pin_derive_verifier(paddedNewPin, 16, dhash);
+ }
+ if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) {
regenerate();
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
+ mbedtls_platform_zeroize(dhash, sizeof(dhash));
if (retries == 0) {
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
}
@@ -646,39 +665,59 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
}
+ mbedtls_platform_zeroize(dhash, sizeof(dhash));
- ret = check_mkek_encrypted(paddedNewPin);
+ if (off == 2) {
+ // Upgrade pin file to new format
+ pin_data[2] = 1; // New format indicator
+ pin_derive_verifier(paddedNewPin, 16, pin_data + 3);
+ hash_multi(paddedNewPin, 16, session_pin);
+ ret = load_keydev(keydev);
+ if (ret != PICOKEY_OK) {
+ CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
+ }
+ encrypt_keydev_f1(keydev);
+ }
+
+ pin_derive_session(paddedNewPin, 16, session_pin);
+ ret = check_keydev_encrypted(session_pin);
if (ret != PICOKEY_OK) {
CBOR_ERROR(ret);
}
- hash_multi(paddedNewPin, 16, session_pin);
pin_data[0] = MAX_PIN_RETRIES;
new_pin_mismatches = 0;
+
file_put_data(ef_pin, pin_data, sizeof(pin_data));
mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
- mbedtls_platform_zeroize(dhash, sizeof(dhash));
low_flash_available();
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
}
- resetPinUvAuthToken();
- beginUsingPinUvAuthToken(false);
- if (subcommand == 0x05) {
- permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA;
- }
- paut.permissions = (uint8_t)permissions;
- if (rpId.present == true) {
- mbedtls_sha256((uint8_t *) rpId.data, rpId.len, paut.rp_id_hash, 0);
- paut.has_rp_id = true;
+ uint8_t pinUvAuthToken_enc[32 + IV_SIZE], *pdata = NULL;
+ if (permissions & CTAP_PERMISSION_PCMR) {
+ ppaut.permissions = CTAP_PERMISSION_PCMR;
+ pdata = ppaut.data;
}
else {
- paut.has_rp_id = false;
+ resetPinUvAuthToken();
+ beginUsingPinUvAuthToken(false);
+ if (subcommand == 0x05) {
+ permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA;
+ }
+ paut.permissions = (uint8_t)permissions;
+ if (rpId.present == true) {
+ mbedtls_sha256((uint8_t *) rpId.data, rpId.len, paut.rp_id_hash, 0);
+ paut.has_rp_id = true;
+ }
+ else {
+ paut.has_rp_id = false;
+ }
+ pdata = paut.data;
}
- uint8_t pinUvAuthToken_enc[32 + IV_SIZE];
- encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc);
+ encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pdata, 32, pinUvAuthToken_enc);
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32 + poff));
@@ -695,6 +734,7 @@ err:
CBOR_FREE_BYTE_STRING(kax);
CBOR_FREE_BYTE_STRING(kay);
CBOR_FREE_BYTE_STRING(rpId);
+ mbedtls_platform_zeroize(keydev, sizeof(keydev));
if (error != CborNoError) {
if (error == CborErrorImproperValue) {
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c
index 89abcdf..2ee716d 100644
--- a/src/fido/cbor_config.c
+++ b/src/fido/cbor_config.c
@@ -15,6 +15,7 @@
* along with this program. If not, see .
*/
+#include "pico_keys.h"
#include "ctap2_cbor.h"
#include "fido.h"
#include "ctap.h"
@@ -22,7 +23,6 @@
#include "files.h"
#include "apdu.h"
#include "credential.h"
-#include "pico_keys.h"
#include "random.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/chachapoly.h"
@@ -31,14 +31,16 @@
extern uint8_t keydev_dec[32];
extern bool has_keydev_dec;
+extern void resetPersistentPinUvAuthToken();
+extern void resetPinUvAuthToken();
int cbor_config(const uint8_t *data, size_t len) {
CborParser parser;
CborValue map;
CborError error = CborNoError;
- uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParam = 0;
- CborByteString pinUvAuthParam = { 0 }, vendorAutCt = { 0 };
- CborCharString minPinLengthRPIDs[32] = { 0 };
+ uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0;
+ CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 };
+ CborCharString minPinLengthRPIDs[32] = { 0 }, vendorParamTextString = { 0 };
size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0;
CborEncoder encoder;
//CborEncoder mapEncoder;
@@ -66,13 +68,19 @@ int cbor_config(const uint8_t *data, size_t len) {
raw_subpara = (uint8_t *) cbor_value_get_next_byte(&_f1);
CBOR_PARSE_MAP_START(_f1, 2)
{
- if (subcommand == 0x7f) { // Config Aut
+ if (subcommand == 0xFF) { // Vendor
CBOR_FIELD_GET_UINT(subpara, 2);
if (subpara == 0x01) {
CBOR_FIELD_GET_UINT(vendorCommandId, 2);
}
else if (subpara == 0x02) {
- CBOR_FIELD_GET_BYTES(vendorAutCt, 2);
+ CBOR_FIELD_GET_BYTES(vendorParamByteString, 2);
+ }
+ else if (subpara == 0x03) {
+ CBOR_FIELD_GET_UINT(vendorParamInt, 2);
+ }
+ else if (subpara == 0x04) {
+ CBOR_FIELD_GET_TEXT(vendorParamTextString, 2);
}
}
else if (subcommand == 0x03) { // Extensions
@@ -95,15 +103,6 @@ int cbor_config(const uint8_t *data, size_t len) {
CBOR_FIELD_GET_BOOL(forceChangePin, 2);
}
}
- else if (subcommand == 0x1B) { // PHY
- CBOR_FIELD_GET_UINT(subpara, 2);
- if (subpara == 0x01) {
- CBOR_FIELD_GET_UINT(vendorCommandId, 2);
- }
- else if (subpara == 0x02) {
- CBOR_FIELD_GET_UINT(vendorParam, 2);
- }
- }
}
CBOR_PARSE_MAP_END(_f1, 2);
raw_subpara_len = cbor_value_get_next_byte(&_f1) - raw_subpara;
@@ -122,9 +121,12 @@ int cbor_config(const uint8_t *data, size_t len) {
if (pinUvAuthParam.present == false) {
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
}
- if (pinUvAuthProtocol == 0) {
+ if (pinUvAuthProtocol == 0) {
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
}
+ if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2) {
+ CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
+ }
uint8_t *verify_payload = (uint8_t *) calloc(1, 32 + 1 + 1 + raw_subpara_len);
memset(verify_payload, 0xff, 32);
@@ -141,8 +143,14 @@ int cbor_config(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
}
- if (subcommand == 0x7f) {
- if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) {
+ if (subcommand == 0xFF) {
+#ifndef ENABLE_EMULATION
+ const bool is_phy = (vendorCommandId == CTAP_CONFIG_PHY_VIDPID ||
+ vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO ||
+ vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS ||
+ vendorCommandId == CTAP_CONFIG_PHY_OPTS);
+#endif
+ if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE){
if (!file_has_data(ef_keydev_enc)) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
@@ -163,7 +171,7 @@ int cbor_config(const uint8_t *data, size_t len) {
}
mbedtls_chachapoly_context chatx;
- int ret = mse_decrypt_ct(vendorAutCt.data, vendorAutCt.len);
+ int ret = mse_decrypt_ct(vendorParamByteString.data, vendorParamByteString.len);
if (ret != 0) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
@@ -171,7 +179,7 @@ int cbor_config(const uint8_t *data, size_t len) {
uint8_t key_dev_enc[12 + 32 + 16];
random_gen(NULL, key_dev_enc, 12);
mbedtls_chachapoly_init(&chatx);
- mbedtls_chachapoly_setkey(&chatx, vendorAutCt.data);
+ mbedtls_chachapoly_setkey(&chatx, vendorParamByteString.data);
ret = mbedtls_chachapoly_encrypt_and_tag(&chatx, file_get_size(ef_keydev), key_dev_enc, NULL, 0, file_get_data(ef_keydev), key_dev_enc + 12, key_dev_enc + 12 + file_get_size(ef_keydev));
mbedtls_chachapoly_free(&chatx);
if (ret != 0) {
@@ -184,6 +192,56 @@ int cbor_config(const uint8_t *data, size_t len) {
file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
low_flash_available();
}
+
+#ifndef ENABLE_EMULATION
+ else if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) {
+ phy_data.vid = (vendorParamInt >> 16) & 0xFFFF;
+ phy_data.pid = vendorParamInt & 0xFFFF;
+ phy_data.vidpid_present = true;
+ }
+ else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) {
+ phy_data.led_gpio = (uint8_t)vendorParamInt;
+ phy_data.led_gpio_present = true;
+ }
+ else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) {
+ phy_data.led_brightness = (uint8_t)vendorParamInt;
+ phy_data.led_brightness_present = true;
+ }
+ else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) {
+ phy_data.opts = (uint16_t)vendorParamInt;
+ }
+ else {
+ CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
+ }
+ if (is_phy && phy_save() != PICOKEY_OK) {
+ CBOR_ERROR(CTAP2_ERR_PROCESSING);
+ }
+#endif
+ else if (vendorCommandId == CTAP_CONFIG_EA_UPLOAD) {
+ if (vendorParamByteString.present == false) {
+ CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
+ }
+ file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
+ if (ef_ee_ea) {
+ file_put_data(ef_ee_ea, vendorParamByteString.data, (uint16_t)vendorParamByteString.len);
+ }
+ low_flash_available();
+ }
+ else if (vendorCommandId == CTAP_CONFIG_PIN_POLICY) {
+ file_t *ef_pin_policy = file_new(EF_PIN_COMPLEXITY_POLICY);
+ if (ef_pin_policy) {
+ uint8_t *val = calloc(1, 2 + vendorParamByteString.len);
+ if (val) {
+ // Not ready yet for integer param
+ // val[0] = (uint8_t)(vendorParamInt >> 8);
+ // val[1] = (uint8_t)(vendorParamInt & 0xFF);
+ memcpy(val + 2, vendorParamByteString.data, vendorParamByteString.len);
+ file_put_data(ef_pin_policy, val, 2 + (uint16_t)vendorParamByteString.len);
+ free(val);
+ }
+ }
+ low_flash_available();
+ }
else {
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
}
@@ -207,6 +265,10 @@ int cbor_config(const uint8_t *data, size_t len) {
if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength) {
forceChangePin = ptrue;
}
+ if (forceChangePin) {
+ resetPersistentPinUvAuthToken();
+ resetPinUvAuthToken();
+ }
uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32);
dataf[0] = (uint8_t)newMinPinLength;
dataf[1] = forceChangePin == ptrue ? 1 : 0;
@@ -222,35 +284,6 @@ int cbor_config(const uint8_t *data, size_t len) {
set_opts(get_opts() | FIDO2_OPT_EA);
goto err;
}
-#ifndef ENABLE_EMULATION
- else if (subcommand == 0x1B) {
- if (vendorParam == 0) {
- CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
- }
- if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) {
- phy_data.vid = (vendorParam >> 16) & 0xFFFF;
- phy_data.pid = vendorParam & 0xFFFF;
- phy_data.vidpid_present = true;
- }
- else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) {
- phy_data.led_gpio = (uint8_t)vendorParam;
- phy_data.led_gpio_present = true;
- }
- else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) {
- phy_data.led_brightness = (uint8_t)vendorParam;
- phy_data.led_brightness_present = true;
- }
- else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) {
- phy_data.opts = (uint16_t)vendorParam;
- }
- else {
- CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
- }
- if (phy_save() != PICOKEY_OK) {
- CBOR_ERROR(CTAP2_ERR_PROCESSING);
- }
- }
-#endif
else {
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
}
@@ -259,7 +292,8 @@ int cbor_config(const uint8_t *data, size_t len) {
err:
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
- CBOR_FREE_BYTE_STRING(vendorAutCt);
+ CBOR_FREE_BYTE_STRING(vendorParamByteString);
+ CBOR_FREE_BYTE_STRING(vendorParamTextString);
for (size_t i = 0; i < minPinLengthRPIDs_len; i++) {
CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]);
}
diff --git a/src/fido/cbor_cred_mgmt.c b/src/fido/cbor_cred_mgmt.c
index 95d4f42..5d53db8 100644
--- a/src/fido/cbor_cred_mgmt.c
+++ b/src/fido/cbor_cred_mgmt.c
@@ -15,6 +15,7 @@
* along with this program. If not, see .
*/
+#include "pico_keys.h"
#include "fido.h"
#include "ctap.h"
#include "hid/ctap_hid.h"
@@ -22,7 +23,6 @@
#include "files.h"
#include "apdu.h"
#include "credential.h"
-#include "pico_keys.h"
uint8_t rp_counter = 1;
uint8_t rp_total = 0;
@@ -79,8 +79,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
if (strcmp(_fd3, "transports") == 0) {
CBOR_PARSE_ARRAY_START(_f3, 4)
{
- CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.
- transports_len], 4);
+ CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.transports_len], 4);
credentialId.transports_len++;
}
CBOR_PARSE_ARRAY_END(_f3, 4);
@@ -122,12 +121,19 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
if (subcommand == 0x01) {
- if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
- CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) == CborNoError) {
+ if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
+ CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ }
}
- if (is_preview == false &&
- (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
- CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ else {
+ if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
+ CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ }
+ if (is_preview == false &&
+ (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
+ CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ }
}
uint8_t existing = 0;
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
@@ -144,11 +150,18 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
else if (subcommand == 0x02 || subcommand == 0x03) {
file_t *rp_ef = NULL;
if (subcommand == 0x02) {
- if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) {
- CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) == CborNoError) {
+ if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
+ CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ }
}
- if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
- CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ else {
+ if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) {
+ CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ }
+ if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
+ CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ }
}
rp_counter = 1;
rp_total = 0;
@@ -199,13 +212,20 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
if (subcommand == 0x04) {
*(raw_subpara - 1) = 0x04;
- if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
- CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) == CborNoError) {
+ if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
+ CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ }
}
- if (is_preview == false &&
- (!(paut.permissions & CTAP_PERMISSION_CM) ||
- (paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) {
- CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ else {
+ if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
+ CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ }
+ if (is_preview == false &&
+ (!(paut.permissions & CTAP_PERMISSION_CM) ||
+ (paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) {
+ CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
+ }
}
cred_counter = 1;
cred_total = 0;
@@ -239,7 +259,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
Credential cred = { 0 };
- if (credential_load(file_get_data(cred_ef) + 32, file_get_size(cred_ef) - 32, rpIdHash.data, &cred) != 0) {
+ if (credential_load_resident(cred_ef, rpIdHash.data, &cred) != 0) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
@@ -296,7 +316,9 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
- CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred.id.data, cred.id.len));
+ uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
+ credential_derive_resident(cred.id.data, cred.id.len, cred_idr);
+ CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred_idr, sizeof(cred_idr)));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
@@ -352,7 +374,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
- if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) {
+ if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, CRED_RESIDENT_LEN) == 0) {
uint8_t *rp_id_hash = file_get_data(ef);
if (delete_file(ef) != 0) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
@@ -394,10 +416,10 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
}
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
- if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) {
+ if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, CRED_RESIDENT_LEN) == 0) {
Credential cred = { 0 };
uint8_t *rp_id_hash = file_get_data(ef);
- if (credential_load(rp_id_hash + 32, file_get_size(ef) - 32, rp_id_hash, &cred) != 0) {
+ if (credential_load_resident(ef, rp_id_hash, &cred) != 0) {
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
if (memcmp(user.id.data, cred.userId.data, MIN(user.id.len, cred.userId.len)) != 0) {
@@ -405,11 +427,11 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
}
uint8_t newcred[MAX_CRED_ID_LENGTH];
- size_t newcred_len = 0;
+ uint16_t newcred_len = 0;
if (credential_create(&cred.rpId, &cred.userId, &user.parent.name,
- &user.displayName, &cred.opts, &cred.extensions,
- cred.use_sign_count, (int)cred.alg,
- (int)cred.curve, newcred, &newcred_len) != 0) {
+ &user.displayName, &cred.opts, &cred.extensions,
+ cred.use_sign_count, (int)cred.alg,
+ (int)cred.curve, newcred, &newcred_len) != 0) {
credential_free(&cred);
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
}
diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c
index a534a8d..ee25e79 100644
--- a/src/fido/cbor_get_assertion.c
+++ b/src/fido/cbor_get_assertion.c
@@ -15,16 +15,16 @@
* along with this program. If not, see .
*/
+#include "pico_keys.h"
#include "cbor.h"
#include "ctap.h"
-#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
+#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include "hid/ctap_hid.h"
#include "fido.h"
#include "files.h"
#include "crypto_utils.h"
-#include "pico_keys.h"
#include "apdu.h"
#include "cbor_make_credential.h"
#include "credential.h"
@@ -295,27 +295,48 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
if (strcmp(allowList[e].type.data, "public-key") != 0) {
continue;
}
- if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) {
- credential_free(&creds[creds_len]);
- }
- else {
- creds_len++;
- silent = false; // If we are able to load a credential, we are not silent
- // Even we provide allowList, we need to check if the credential is resident
- if (!resident) {
- for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
- file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
- if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
+ if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
+ for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
+ file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
+ if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
+ continue;
+ }
+ if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) {
+ if (credential_load_resident(ef, rp_id_hash, &creds[creds_len]) != 0) {
+ // Should never happen
+ credential_free(&creds[creds_len]);
continue;
}
- if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, allowList[e].id.len) == 0) {
- resident = true;
+ resident = true;
+ creds_len++;
+ silent = false; // If we are able to load a credential, we are not silent
+ break;
+ }
+ }
+ }
+ else {
+ if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) {
+ credential_free(&creds[creds_len]);
+ }
+ else {
+ creds_len++;
+ silent = false; // If we are able to load a credential, we are not silent
+ // Even we provide allowList, we need to check if the credential is resident
+ if (!resident) {
+ for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
+ file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
+ if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
+ continue;
+ }
+ if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, allowList[e].id.len) == 0) {
+ resident = true;
+ break;
+ }
+ }
+ if (resident) {
break;
}
}
- if (resident) {
- break;
- }
}
}
}
@@ -326,7 +347,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue;
}
- int ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &creds[creds_len]);
+ int ret = credential_load_resident(ef, rp_id_hash, &creds[creds_len]);
if (ret != 0) {
credential_free(&creds[creds_len]);
}
@@ -343,8 +364,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
if (creds[i].extensions.credProtect == CRED_PROT_UV_REQUIRED && !(flags & FIDO2_AUT_FLAG_UV)) {
credential_free(&creds[i]);
}
- else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST &&
- resident == true && !(flags & FIDO2_AUT_FLAG_UV)) {
+ else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && allowList_len == 0 && !(flags & FIDO2_AUT_FLAG_UV)) {
credential_free(&creds[i]);
}
else {
@@ -375,8 +395,24 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
if (strcmp(allowList[e].type.data, "public-key") != 0) {
continue;
}
- if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) {
- numberOfCredentials++;
+ if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
+ for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
+ file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
+ if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
+ continue;
+ }
+ if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) {
+ if (credential_verify(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, true) == 0) {
+ numberOfCredentials++;
+ }
+ break;
+ }
+ }
+ }
+ else {
+ if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) {
+ numberOfCredentials++;
+ }
}
}
}
@@ -427,10 +463,16 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
}
else {
selcred = &creds[0];
+ if (resident && allowList_len > 1) {
+ numberOfCredentials = 1;
+ }
if (numberOfCredentials > 1) {
asserted = true;
residentx = resident;
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
+ credential_free(&credsx[i]);
+ }
+ for (int i = 0; i < numberOfCredentials; i++) {
credsx[i] = creds[i];
}
numberOfCredentialsx = numberOfCredentials;
@@ -633,7 +675,14 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
if (selcred) {
- CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len));
+ if (resident) {
+ uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
+ credential_derive_resident(selcred->id.data, selcred->id.len, cred_idr);
+ CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred_idr, sizeof(cred_idr)));
+ }
+ else {
+ CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len));
+ }
}
else {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, (uint8_t *)"\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01", 16));
@@ -660,8 +709,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
}
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, lu));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
- CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data,
- selcred->userId.len));
+ CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data, selcred->userId.len));
if (numberOfCredentials > 1 && allowList_len == 0) {
if (selcred->userName.present == true) {
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name"));
diff --git a/src/fido/cbor_get_info.c b/src/fido/cbor_get_info.c
index 74643cd..182e33a 100644
--- a/src/fido/cbor_get_info.c
+++ b/src/fido/cbor_get_info.c
@@ -15,6 +15,7 @@
* along with this program. If not, see .
*/
+#include "pico_keys.h"
#include "ctap2_cbor.h"
#include "hid/ctap_hid.h"
#include "fido.h"
@@ -27,22 +28,36 @@ int cbor_get_info() {
CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2;
CborError error = CborNoError;
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
- CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 15));
+ uint8_t lfields = 14;
+#ifndef ENABLE_EMULATION
+ if (phy_data.vid != 0x1050) {
+ lfields++;
+ }
+#else
+ lfields++;
+#endif
+ file_t *ef_pin_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF);
+ if (file_has_data(ef_pin_policy)) {
+ lfields += 2;
+ }
+ CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
- CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3));
+ CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 4));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "U2F_V2"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_0"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_1"));
+ CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_2"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
- CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 6));
+ CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 7));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credBlob"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobKey"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "minPinLength"));
+ CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret-mc"));
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "thirdPartyPayment"));
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
@@ -150,13 +165,36 @@ int cbor_get_info() {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0F));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CREDBLOB_LENGTH)); // maxCredBlobLength
+#ifndef ENABLE_EMULATION
+ if (phy_data.vid != 0x1050) {
+#endif
+ CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
+ uint8_t enabled_cmds = 4;
+#ifndef ENABLE_EMULATION
+ enabled_cmds += 4;
+#endif
+ CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, enabled_cmds));
+ CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
+ CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
+ CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_EA_UPLOAD));
+ CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PIN_POLICY));
+#ifndef ENABLE_EMULATION
+ CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_VIDPID));
+ CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_BTNESS));
+ CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_GPIO));
+ CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_OPTS));
+#endif
+ CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
+#ifndef ENABLE_EMULATION
+ }
+ if (file_has_data(ef_pin_policy)) {
+ CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1B));
+ CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
+ CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1C));
+ CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_pin_policy) + 2, file_get_size(ef_pin_policy) - 2));
+ }
- CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
- CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
- CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
- CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
- CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
-
+#endif
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
err:
if (error != CborNoError) {
diff --git a/src/fido/cbor_large_blobs.c b/src/fido/cbor_large_blobs.c
index 40e3dca..293a677 100644
--- a/src/fido/cbor_large_blobs.c
+++ b/src/fido/cbor_large_blobs.c
@@ -15,13 +15,13 @@
* along with this program. If not, see .
*/
+#include "pico_keys.h"
#include "ctap2_cbor.h"
#include "fido.h"
#include "ctap.h"
#include "hid/ctap_hid.h"
#include "files.h"
#include "apdu.h"
-#include "pico_keys.h"
#include "mbedtls/sha256.h"
static uint64_t expectedLength = 0, expectedNextOffset = 0;
@@ -129,7 +129,7 @@ int cbor_large_blobs(const uint8_t *data, size_t len) {
uint8_t verify_data[70] = { 0 };
memset(verify_data, 0xff, 32);
verify_data[32] = 0x0C;
- put_uint32_t_le(offset, verify_data + 34);
+ put_uint32_t_le((uint32_t)offset, verify_data + 34);
mbedtls_sha256(set.data, set.len, verify_data + 38, 0);
if (verify((uint8_t)pinUvAuthProtocol, paut.data, verify_data, (uint16_t)sizeof(verify_data), pinUvAuthParam.data) != 0) {
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
diff --git a/src/fido/cbor_make_credential.c b/src/fido/cbor_make_credential.c
index 5aea796..25a5d41 100644
--- a/src/fido/cbor_make_credential.c
+++ b/src/fido/cbor_make_credential.c
@@ -15,6 +15,7 @@
* along with this program. If not, see .
*/
+#include "pico_keys.h"
#include "cbor_make_credential.h"
#include "ctap2_cbor.h"
#include "hid/ctap_hid.h"
@@ -25,7 +26,7 @@
#include "credential.h"
#include "mbedtls/sha256.h"
#include "random.h"
-#include "pico_keys.h"
+#include "crypto_utils.h"
int cbor_make_credential(const uint8_t *data, size_t len) {
CborParser parser;
@@ -39,7 +40,11 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
PublicKeyCredentialDescriptor excludeList[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
size_t excludeList_len = 0;
CredOptions options = { 0 };
- uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0;
+ uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0, hmacSecretPinUvAuthProtocol = 1;
+ int64_t kty = 2, hmac_alg = 0, crv = 0;
+ CborByteString kax = { 0 }, kay = { 0 }, salt_enc = { 0 }, salt_auth = { 0 };
+ bool hmac_secret_mc = false;
+ const bool *pin_complexity_policy = NULL;
uint8_t *aut_data = NULL;
size_t resp_size = 0;
CredExtensions extensions = { 0 };
@@ -127,12 +132,39 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_PARSE_MAP_START(_f1, 2)
{
CBOR_FIELD_GET_KEY_TEXT(2);
+ if (strcmp(_fd2, "hmac-secret-mc") == 0) {
+ hmac_secret_mc = true;
+ uint64_t ukey = 0;
+ CBOR_PARSE_MAP_START(_f2, 3)
+ {
+ CBOR_FIELD_GET_UINT(ukey, 3);
+ if (ukey == 0x01) {
+ CBOR_CHECK(COSE_read_key(&_f3, &kty, &hmac_alg, &crv, &kax, &kay));
+ }
+ else if (ukey == 0x02) {
+ CBOR_FIELD_GET_BYTES(salt_enc, 3);
+ }
+ else if (ukey == 0x03) {
+ CBOR_FIELD_GET_BYTES(salt_auth, 3);
+ }
+ else if (ukey == 0x04) {
+ CBOR_FIELD_GET_UINT(hmacSecretPinUvAuthProtocol, 3);
+ }
+ else {
+ CBOR_ADVANCE(3);
+ }
+ }
+ CBOR_PARSE_MAP_END(_f2, 3);
+ continue;
+ }
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", extensions.hmac_secret);
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "minPinLength", extensions.minPinLength);
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey);
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment);
+ CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "pinComplexityPolicy", pin_complexity_policy);
+
CBOR_ADVANCE(2);
}
CBOR_PARSE_MAP_END(_f1, 2);
@@ -202,21 +234,36 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0) {
CBOR_ERROR(CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
}
- if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256) {
+ if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP256) {
if (curve <= 0) {
curve = FIDO2_CURVE_P256;
}
}
- else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384) {
+ else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP384) {
if (curve <= 0) {
curve = FIDO2_CURVE_P384;
}
}
- else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512) {
+ else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP512) {
if (curve <= 0) {
curve = FIDO2_CURVE_P521;
}
}
+ else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB256) {
+ if (curve <= 0) {
+ curve = FIDO2_CURVE_BP256R1;
+ }
+ }
+ else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB384) {
+ if (curve <= 0) {
+ curve = FIDO2_CURVE_BP384R1;
+ }
+ }
+ else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB512) {
+ if (curve <= 0) {
+ curve = FIDO2_CURVE_BP512R1;
+ }
+ }
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256K
#ifndef ENABLE_EMULATION
&& (phy_data.enabled_curves & PHY_CURVE_SECP256K1)
@@ -227,11 +274,16 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
}
}
#ifdef MBEDTLS_EDDSA_C
- else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA) {
+ else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA || pubKeyCredParams[i].alg == FIDO2_ALG_ED25519) {
if (curve <= 0) {
curve = FIDO2_CURVE_ED25519;
}
}
+ else if (pubKeyCredParams[i].alg == FIDO2_ALG_ED448) {
+ if (curve <= 0) {
+ curve = FIDO2_CURVE_ED448;
+ }
+ }
#endif
else if (pubKeyCredParams[i].alg <= FIDO2_ALG_RS256 && pubKeyCredParams[i].alg >= FIDO2_ALG_RS512) {
// pass
@@ -298,12 +350,26 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
continue;
}
Credential ecred = {0};
- if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash,
- &ecred) == 0 &&
- (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED ||
- (flags & FIDO2_AUT_FLAG_UV))) {
+ if (credential_is_resident(excludeList[e].id.data, excludeList[e].id.len)) {
+ for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
+ file_t *ef_cred = search_dynamic_file((uint16_t)(EF_CRED + i));
+ if (!file_has_data(ef_cred) || memcmp(file_get_data(ef_cred), rp_id_hash, 32) != 0) {
+ continue;
+ }
+ uint8_t *cred_idr = file_get_data(ef_cred) + 32;
+ if (memcmp(cred_idr, excludeList[e].id.data, CRED_RESIDENT_LEN) == 0) {
+ if (credential_load_resident(ef_cred, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) {
+ credential_free(&ecred);
+ CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
+ }
+ }
+ }
+ }
+ else {
+ if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) {
credential_free(&ecred);
- CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
+ CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
+ }
}
credential_free(&ecred);
}
@@ -313,6 +379,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
}
+ if (hmac_secret_mc && extensions.hmac_secret != ptrue) {
+ CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
+ }
+
if (options.up == ptrue || options.up == NULL) { //14.1
if (pinUvAuthParam.present == true) {
if (getUserPresentFlagValue() == false) {
@@ -332,11 +402,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
const known_app_t *ka = find_app_by_rp_id_hash(rp_id_hash);
uint8_t cred_id[MAX_CRED_ID_LENGTH] = {0};
- size_t cred_id_len = 0;
+ uint16_t cred_id_len = 0;
- CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options,
- &extensions, (!ka || ka->use_sign_count == ptrue), alg, curve,
- cred_id, &cred_id_len));
+ CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options, &extensions, (!ka || ka->use_sign_count == ptrue), alg, curve, cred_id, &cred_id_len));
if (getUserVerifiedFlagValue()) {
flags |= FIDO2_AUT_FLAG_UV;
@@ -372,6 +440,12 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
if (extensions.credBlob.present == true) {
l++;
}
+ if (hmac_secret_mc) {
+ l++;
+ }
+ if (pin_complexity_policy == ptrue) {
+ l++;
+ }
if (l > 0) {
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
if (extensions.credBlob.present == true) {
@@ -383,15 +457,69 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
}
if (extensions.hmac_secret == ptrue) {
-
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
}
if (minPinLen > 0) {
-
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen));
}
+ if (hmac_secret_mc) {
+ CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret-mc"));
+
+ uint8_t sharedSecret[64] = {0};
+ mbedtls_ecp_point Qp;
+ mbedtls_ecp_point_init(&Qp);
+ mbedtls_mpi_lset(&Qp.Z, 1);
+ if (mbedtls_mpi_read_binary(&Qp.X, kax.data, kax.len) != 0) {
+ mbedtls_ecp_point_free(&Qp);
+ CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
+ }
+ if (mbedtls_mpi_read_binary(&Qp.Y, kay.data, kay.len) != 0) {
+ mbedtls_ecp_point_free(&Qp);
+ CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
+ }
+ int ret = ecdh((uint8_t)hmacSecretPinUvAuthProtocol, &Qp, sharedSecret);
+ mbedtls_ecp_point_free(&Qp);
+ if (ret != 0) {
+ mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
+ CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
+ }
+ if (verify((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_auth.data) != 0) {
+ mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
+ CBOR_ERROR(CTAP2_ERR_EXTENSION_FIRST);
+ }
+ uint8_t salt_dec[64] = {0}, poff = ((uint8_t)hmacSecretPinUvAuthProtocol - 1) * IV_SIZE;
+ ret = decrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_dec);
+ if (ret != 0) {
+ mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
+ CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
+ }
+ uint8_t cred_random[64] = {0}, *crd = NULL;
+ ret = credential_derive_hmac_key(cred_id, cred_id_len, cred_random);
+ if (ret != 0) {
+ mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
+ CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
+ }
+ if (flags & FIDO2_AUT_FLAG_UV) {
+ crd = cred_random + 32;
+ }
+ else {
+ crd = cred_random;
+ }
+ uint8_t out1[64] = {0}, hmac_res[80] = {0};
+ mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec, 32, out1);
+ if ((uint8_t)salt_enc.len == 64 + poff) {
+ mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec + 32, 32, out1 + 32);
+ }
+ encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res);
+ CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len));
+ }
+ if (pin_complexity_policy == ptrue) {
+ CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "pinComplexityPolicy"));
+ file_t *ef_pin_complexity_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF);
+ CBOR_CHECK(cbor_encode_boolean(&mapEncoder, file_has_data(ef_pin_complexity_policy)));
+ }
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
@@ -417,15 +545,23 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_CHECK(COSE_key(&ekey, &encoder, &mapEncoder));
size_t rs = cbor_encoder_get_buffer_size(&encoder, cbor_buf);
- size_t aut_data_len = 32 + 1 + 4 + (16 + 2 + cred_id_len + rs) + ext_len;
+ size_t aut_data_len = 32 + 1 + 4 + (16 + 2 + (options.rk == ptrue ? CRED_RESIDENT_LEN : cred_id_len) + rs) + ext_len;
aut_data = (uint8_t *) calloc(1, aut_data_len + clientDataHash.len);
uint8_t *pa = aut_data;
memcpy(pa, rp_id_hash, 32); pa += 32;
*pa++ = flags;
pa += put_uint32_t_be(ctr, pa);
memcpy(pa, aaguid, 16); pa += 16;
- pa += put_uint16_t_be(cred_id_len, pa);
- memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len;
+ if (options.rk == ptrue) {
+ uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
+ pa += put_uint16_t_be(sizeof(cred_idr), pa);
+ credential_derive_resident(cred_id, cred_id_len, cred_idr);
+ memcpy(pa, cred_idr, sizeof(cred_idr)); pa += sizeof(cred_idr);
+ }
+ else {
+ pa += put_uint16_t_be(cred_id_len, pa);
+ memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len;
+ }
memcpy(pa, cbor_buf, rs); pa += (uint16_t)rs;
memcpy(pa, ext, ext_len); pa += (uint16_t)ext_len;
if ((size_t)(pa - aut_data) != aut_data_len) {
@@ -436,14 +572,14 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
memcpy(pa, clientDataHash.data, clientDataHash.len);
uint8_t hash[64] = {0}, sig[MBEDTLS_ECDSA_MAX_LEN] = {0};
const mbedtls_md_info_t *md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
- if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1) {
+ if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1 || ekey.grp.id == MBEDTLS_ECP_DP_BP384R1) {
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384);
}
- else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) {
+ else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1 || ekey.grp.id == MBEDTLS_ECP_DP_BP512R1) {
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
}
#ifdef MBEDTLS_EDDSA_C
- else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519) {
+ else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519 || ekey.grp.id == MBEDTLS_ECP_DP_ED448) {
md = NULL;
}
#endif
@@ -483,7 +619,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
#ifndef ENABLE_EMULATION
uint8_t *p = (uint8_t *)user.parent.name.data + 5;
if (memcmp(p, "CommissionProfile", 17) == 0) {
- ret = phy_unserialize_data(user.id.data, user.id.len, &phy_data);
+ ret = phy_unserialize_data(user.id.data, (uint16_t)user.id.len, &phy_data);
if (ret == PICOKEY_OK) {
ret = phy_save();
}
@@ -519,12 +655,12 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aut_data, aut_data_len));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
- CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false || is_nitrokey ? 3 : 2));
+ CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false || is_nk ? 3 : 2));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg"));
- CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation || is_nitrokey ? -alg : -FIDO2_ALG_ES256));
+ CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation || is_nk ? -alg : -FIDO2_ALG_ES256));
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "sig"));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, sig, olen));
- if (self_attestation == false || is_nitrokey) {
+ if (self_attestation == false || is_nk) {
CborEncoder arrEncoder;
file_t *ef_cert = NULL;
if (enterpriseAttestation == 2) {
@@ -569,6 +705,10 @@ err:
CBOR_FREE_BYTE_STRING(user.id);
CBOR_FREE_BYTE_STRING(user.displayName);
CBOR_FREE_BYTE_STRING(user.parent.name);
+ CBOR_FREE_BYTE_STRING(kax);
+ CBOR_FREE_BYTE_STRING(kay);
+ CBOR_FREE_BYTE_STRING(salt_enc);
+ CBOR_FREE_BYTE_STRING(salt_auth);
if (extensions.present == true) {
CBOR_FREE_BYTE_STRING(extensions.credBlob);
}
diff --git a/src/fido/cbor_reset.c b/src/fido/cbor_reset.c
index c5ee2ab..c09d8c8 100644
--- a/src/fido/cbor_reset.c
+++ b/src/fido/cbor_reset.c
@@ -15,10 +15,11 @@
* along with this program. If not, see .
*/
+#include "pico_keys.h"
#include "file.h"
#include "fido.h"
#include "ctap.h"
-#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
+#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#ifdef ESP_PLATFORM
diff --git a/src/fido/cbor_selection.c b/src/fido/cbor_selection.c
index 90ccdc1..079d4d6 100644
--- a/src/fido/cbor_selection.c
+++ b/src/fido/cbor_selection.c
@@ -15,6 +15,7 @@
* along with this program. If not, see .
*/
+#include "pico_keys.h"
#include "fido.h"
#include "ctap.h"
diff --git a/src/fido/cbor_vendor.c b/src/fido/cbor_vendor.c
index b077a98..e102aab 100644
--- a/src/fido/cbor_vendor.c
+++ b/src/fido/cbor_vendor.c
@@ -15,13 +15,13 @@
* along with this program. If not, see .
*/
+#include "pico_keys.h"
#include "ctap2_cbor.h"
#include "fido.h"
#include "ctap.h"
#include "hid/ctap_hid.h"
#include "files.h"
#include "apdu.h"
-#include "pico_keys.h"
#include "random.h"
#include "mbedtls/ecdh.h"
#include "mbedtls/chachapoly.h"
@@ -237,25 +237,14 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, buffer + sizeof(buffer) - ret, ret));
}
- else if (vendorCmd == 0x02) {
- if (vendorParam.present == false) {
- CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
- }
- file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
- if (ef_ee_ea) {
- file_put_data(ef_ee_ea, vendorParam.data, (uint16_t)vendorParam.len);
- }
- low_flash_available();
- goto err;
- }
}
#ifndef ENABLE_EMULATION
else if (cmd == CTAP_VENDOR_PHY_OPTS) {
if (vendorCmd == 0x01) {
uint16_t opts = 0;
if (file_has_data(ef_phy)) {
- uint8_t *data = file_get_data(ef_phy);
- opts = get_uint16_t_be(data + PHY_OPTS);
+ uint8_t *pdata = file_get_data(ef_phy);
+ opts = get_uint16_t_be(pdata + PHY_OPTS);
}
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
diff --git a/src/fido/cmd_authenticate.c b/src/fido/cmd_authenticate.c
index 024b373..2616f42 100644
--- a/src/fido/cmd_authenticate.c
+++ b/src/fido/cmd_authenticate.c
@@ -15,8 +15,8 @@
* along with this program. If not, see .
*/
-#include "fido.h"
#include "pico_keys.h"
+#include "fido.h"
#include "apdu.h"
#include "ctap.h"
#include "random.h"
diff --git a/src/fido/cmd_register.c b/src/fido/cmd_register.c
index 52f5c72..a545448 100644
--- a/src/fido/cmd_register.c
+++ b/src/fido/cmd_register.c
@@ -15,8 +15,8 @@
* along with this program. If not, see .
*/
-#include "fido.h"
#include "pico_keys.h"
+#include "fido.h"
#include "apdu.h"
#include "ctap.h"
#include "random.h"
@@ -69,11 +69,7 @@ int cmd_register() {
}
if (memcmp(req->appId, bogus_firefox,
CTAP_APPID_SIZE) == 0 || memcmp(req->appId, bogus_chrome, CTAP_APPID_SIZE) == 0)
-#ifndef ENABLE_EMULATION
{ return ctap_error(CTAP1_ERR_CHANNEL_BUSY); }
-#else
- { return SW_DATA_INVALID(); }
-#endif
mbedtls_ecdsa_context key;
mbedtls_ecdsa_init(&key);
int ret = derive_key(req->appId, true, resp->keyHandleCertSig, MBEDTLS_ECP_DP_SECP256R1, &key);
diff --git a/src/fido/credential.c b/src/fido/credential.c
index f55f80f..6bc479a 100644
--- a/src/fido/credential.c
+++ b/src/fido/credential.c
@@ -15,10 +15,11 @@
* along with this program. If not, see .
*/
+#include "pico_keys.h"
#include "mbedtls/chachapoly.h"
#include "mbedtls/sha256.h"
#include "credential.h"
-#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
+#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include "hid/ctap_hid.h"
@@ -26,7 +27,6 @@
#include "ctap.h"
#include "random.h"
#include "files.h"
-#include "pico_keys.h"
#include "otp.h"
int credential_derive_chacha_key(uint8_t *outk, const uint8_t *);
@@ -93,7 +93,7 @@ int credential_create(CborCharString *rpId,
int alg,
int curve,
uint8_t *cred_id,
- size_t *cred_id_len) {
+ uint16_t *cred_id_len) {
CborEncoder encoder, mapEncoder, mapEncoder2;
CborError error = CborNoError;
uint8_t rp_id_hash[32];
@@ -150,7 +150,7 @@ int credential_create(CborCharString *rpId,
}
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
size_t rs = cbor_encoder_get_buffer_size(&encoder, cred_id);
- *cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN;
+ *cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + (uint16_t)rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN;
uint8_t key[32] = {0};
credential_derive_chacha_key(key, (const uint8_t *)CRED_PROTO);
uint8_t iv[CRED_IV_LEN] = {0};
@@ -314,7 +314,7 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
if (memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
continue;
}
- ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &rcred);
+ ret = credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &rcred);
if (ret != 0) {
credential_free(&rcred);
continue;
@@ -330,11 +330,14 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
if (sloti == -1) {
return -1;
}
- uint8_t *data = (uint8_t *) calloc(1, cred_id_len + 32);
+ uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
+ credential_derive_resident(cred_id, cred_id_len, cred_idr);
+ uint8_t *data = (uint8_t *) calloc(1, cred_id_len + 32 + CRED_RESIDENT_LEN);
memcpy(data, rp_id_hash, 32);
- memcpy(data + 32, cred_id, cred_id_len);
+ memcpy(data + 32, cred_idr, CRED_RESIDENT_LEN);
+ memcpy(data + 32 + CRED_RESIDENT_LEN, cred_id, cred_id_len);
file_t *ef = file_new((uint16_t)(EF_CRED + sloti));
- file_put_data(ef, data, (uint16_t)cred_id_len + 32);
+ file_put_data(ef, data, (uint16_t)cred_id_len + 32 + CRED_RESIDENT_LEN);
free(data);
if (new_record == true) { //increase rps
@@ -421,3 +424,33 @@ int credential_derive_large_blob_key(const uint8_t *cred_id, size_t cred_id_len,
mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk);
return 0;
}
+
+int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk) {
+ memset(outk, 0, CRED_RESIDENT_LEN);
+ const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
+ uint8_t *cred_idr = outk + CRED_RESIDENT_HEADER_LEN;
+ mbedtls_md_hmac(md_info, cred_idr, 32, pico_serial.id, sizeof(pico_serial.id), outk);
+ memcpy(outk + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN);
+ outk[4 + CRED_PROTO_RESIDENT_LEN] = 0x00;
+ outk[4 + CRED_PROTO_RESIDENT_LEN + 1] = 0x00;
+
+ mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "SLIP-0022", 9, cred_idr);
+ mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) cred_id, CRED_PROTO_LEN, cred_idr);
+ mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "resident", 8, cred_idr);
+ mbedtls_md_hmac(md_info, cred_idr, 32, cred_id, cred_id_len, cred_idr);
+ return 0;
+}
+
+bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len) {
+ if (cred_id_len < 4 + CRED_PROTO_RESIDENT_LEN) {
+ return false;
+ }
+ return memcmp(cred_id + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN) == 0;
+}
+
+int credential_load_resident(const file_t *ef, const uint8_t *rp_id_hash, Credential *cred) {
+ if (credential_is_resident(file_get_data(ef) + 32, file_get_size(ef) - 32)) {
+ return credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, cred);
+ }
+ return credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, cred);
+}
diff --git a/src/fido/credential.h b/src/fido/credential.h
index d7235f9..a77b72f 100644
--- a/src/fido/credential.h
+++ b/src/fido/credential.h
@@ -19,6 +19,7 @@
#define _CREDENTIAL_H_
#include "ctap2_cbor.h"
+#include "file.h"
typedef struct CredOptions {
const bool *rk;
@@ -58,6 +59,7 @@ typedef struct Credential {
#define CRED_PROTO_21_S "\xf1\xd0\x02\x01"
#define CRED_PROTO_22_S "\xf1\xd0\x02\x02"
+#define CRED_PROTO_23_S "\xf1\xd0\x02\x03"
#define CRED_PROTO CRED_PROTO_22_S
@@ -66,6 +68,11 @@ typedef struct Credential {
#define CRED_TAG_LEN 16
#define CRED_SILENT_TAG_LEN 16
+#define CRED_PROTO_RESIDENT CRED_PROTO_23_S
+#define CRED_PROTO_RESIDENT_LEN 4
+#define CRED_RESIDENT_HEADER_LEN (CRED_PROTO_RESIDENT_LEN + 6)
+#define CRED_RESIDENT_LEN (CRED_RESIDENT_HEADER_LEN + 32)
+
typedef enum
{
CRED_PROTO_21 = 0x01,
@@ -83,7 +90,7 @@ extern int credential_create(CborCharString *rpId,
int alg,
int curve,
uint8_t *cred_id,
- size_t *cred_id_len);
+ uint16_t *cred_id_len);
extern void credential_free(Credential *cred);
extern int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash);
extern int credential_load(const uint8_t *cred_id,
@@ -94,5 +101,8 @@ extern int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len
extern int credential_derive_large_blob_key(const uint8_t *cred_id,
size_t cred_id_len,
uint8_t *outk);
+extern int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk);
+extern bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len);
+extern int credential_load_resident(const file_t *ef, const uint8_t *rp_id_hash, Credential *cred);
#endif // _CREDENTIAL_H_
diff --git a/src/fido/ctap.h b/src/fido/ctap.h
index 2db44bf..15bb84d 100644
--- a/src/fido/ctap.h
+++ b/src/fido/ctap.h
@@ -114,10 +114,14 @@ typedef struct {
#define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2
#define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9
+#define CTAP_CONFIG_EA_UPLOAD 0x66f2a674c29a8dcf
+#define CTAP_CONFIG_PIN_POLICY 0x6c07d70fe96c3897
+#ifndef ENABLE_EMULATION
#define CTAP_CONFIG_PHY_VIDPID 0x6fcb19b0cbe3acfa
-#define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948
#define CTAP_CONFIG_PHY_LED_BTNESS 0x76a85945985d02fd
-#define CTAP_CONFIG_PHY_OPTS 0x969f3b09eceb805f
+#define CTAP_CONFIG_PHY_LED_GPIO 0x7b392a394de9f948
+#define CTAP_CONFIG_PHY_OPTS 0x269f3b09eceb805f
+#endif
#define CTAP_VENDOR_CBOR (CTAPHID_VENDOR_FIRST + 1)
@@ -134,6 +138,7 @@ typedef struct {
#define CTAP_PERMISSION_BE 0x08 // BioEnrollment
#define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite
#define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration
+#define CTAP_PERMISSION_PCMR 0x40 // PerCredentialManagementReadOnly
typedef struct mse {
uint8_t Qpt[65];
diff --git a/src/fido/defs.c b/src/fido/defs.c
index c8cdc0f..74713be 100644
--- a/src/fido/defs.c
+++ b/src/fido/defs.c
@@ -15,6 +15,7 @@
* along with this program. If not, see .
*/
- #include "fido.h"
+#include "pico_keys.h"
+#include "fido.h"
uint8_t PICO_PRODUCT = 2; // Pico FIDO
diff --git a/src/fido/fido.c b/src/fido/fido.c
index 96fe65d..d6cb9d9 100644
--- a/src/fido/fido.c
+++ b/src/fido/fido.c
@@ -15,9 +15,9 @@
* along with this program. If not, see .
*/
+#include "pico_keys.h"
#include "fido.h"
#include "kek.h"
-#include "pico_keys.h"
#include "apdu.h"
#include "ctap.h"
#include "files.h"
@@ -25,10 +25,10 @@
#include "random.h"
#include "mbedtls/x509_crt.h"
#include "mbedtls/hkdf.h"
-#if defined(USB_ITF_CCID) || defined(ENABLE_EMULATION)
+#if defined(USB_ITF_CCID)
#include "ccid/ccid.h"
#endif
-#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
+#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
#include
@@ -42,6 +42,7 @@ int fido_process_apdu();
int fido_unload();
pinUvAuthToken_t paut = { 0 };
+persistentPinUvAuthToken_t ppaut = { 0 };
uint8_t keydev_dec[32];
bool has_keydev_dec = false;
@@ -118,6 +119,15 @@ mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) {
return MBEDTLS_ECP_DP_ED448;
}
#endif
+ else if (curve == FIDO2_CURVE_BP256R1) {
+ return MBEDTLS_ECP_DP_BP256R1;
+ }
+ else if (curve == FIDO2_CURVE_BP384R1) {
+ return MBEDTLS_ECP_DP_BP384R1;
+ }
+ else if (curve == FIDO2_CURVE_BP512R1) {
+ return MBEDTLS_ECP_DP_BP512R1;
+ }
return MBEDTLS_ECP_DP_NONE;
}
int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) {
@@ -158,7 +168,7 @@ int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key) {
uint8_t key_path[KEY_PATH_LEN];
memcpy(key_path, cred_id, KEY_PATH_LEN);
*(uint32_t *) key_path = 0x80000000 | 10022;
- for (int i = 1; i < KEY_PATH_ENTRIES; i++) {
+ for (size_t i = 1; i < KEY_PATH_ENTRIES; i++) {
*(uint32_t *) (key_path + i * sizeof(uint32_t)) |= 0x80000000;
}
return derive_key(NULL, false, key_path, mbedtls_curve, key);
@@ -194,7 +204,7 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
return ret;
}
-int load_keydev(uint8_t *key) {
+int load_keydev(uint8_t key[32]) {
if (has_keydev_dec == false && !file_has_data(ef_keydev)) {
return PICOKEY_ERR_MEMORY_FATAL;
}
@@ -203,13 +213,39 @@ int load_keydev(uint8_t *key) {
memcpy(key, keydev_dec, sizeof(keydev_dec));
}
else {
- memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev));
-
- if (mkek_decrypt(key, 32) != PICOKEY_OK) {
- return PICOKEY_EXEC_ERROR;
+ uint16_t fid_size = file_get_size(ef_keydev);
+ if (fid_size == 32) {
+ memcpy(key, file_get_data(ef_keydev), 32);
+ if (mkek_decrypt(key, 32) != PICOKEY_OK) {
+ return PICOKEY_EXEC_ERROR;
+ }
+ if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) {
+ return PICOKEY_EXEC_ERROR;
+ }
}
- if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) {
- return PICOKEY_EXEC_ERROR;
+ else if (fid_size == 33 || fid_size == 61) {
+ uint8_t format = *file_get_data(ef_keydev);
+ if (format == 0x01 || format == 0x02) { // Format indicator
+ if (format == 0x02) {
+ uint8_t tmp_key[61];
+ memcpy(tmp_key, file_get_data(ef_keydev), sizeof(tmp_key));
+ int ret = decrypt_with_aad(session_pin, tmp_key + 1, 60, key);
+ if (ret != PICOKEY_OK) {
+ return PICOKEY_EXEC_ERROR;
+ }
+ }
+ else {
+ memcpy(key, file_get_data(ef_keydev) + 1, 32);
+ }
+ uint8_t kbase[32];
+ derive_kbase(kbase);
+ int ret = aes_decrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32);
+ if (ret != PICOKEY_OK) {
+ mbedtls_platform_zeroize(kbase, sizeof(kbase));
+ return PICOKEY_EXEC_ERROR;
+ }
+ mbedtls_platform_zeroize(kbase, sizeof(kbase));
+ }
}
}
@@ -217,7 +253,7 @@ int load_keydev(uint8_t *key) {
}
int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecp_keypair *key) {
- for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
+ for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) {
uint32_t k = *(uint32_t *) &keyHandle[i * sizeof(uint32_t)];
if (!(k & 0x80000000)) {
return -1;
@@ -258,7 +294,7 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
return r;
}
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
- for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
+ for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) {
if (new_key == true) {
uint32_t val = 0;
random_gen(NULL, (uint8_t *) &val, sizeof(val));
@@ -305,6 +341,23 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
return r;
}
+int encrypt_keydev_f1(const uint8_t keydev[32]) {
+ uint8_t kdata[33] = {0};
+ kdata[0] = 0x01; // Format indicator
+ memcpy(kdata + 1, keydev, 32);
+ uint8_t kbase[32];
+ derive_kbase(kbase);
+ int ret = aes_encrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata + 1, 32);
+ mbedtls_platform_zeroize(kbase, sizeof(kbase));
+ if (ret != PICOKEY_OK) {
+ return ret;
+ }
+ ret = file_put_data(ef_keydev, kdata, 33);
+ mbedtls_platform_zeroize(kdata, sizeof(kdata));
+ low_flash_available();
+ return ret;
+}
+
int scan_files_fido() {
ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF);
ef_keydev_enc = search_by_fid(EF_KEY_DEV_ENC, NULL, SPECIFY_EF);
@@ -320,17 +373,16 @@ int scan_files_fido() {
mbedtls_ecdsa_free(&ecdsa);
return ret;
}
- uint8_t kdata[64];
+ uint8_t keydev[32] = {0};
size_t key_size = 0;
- ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, kdata, sizeof(kdata));
- if (ret != PICOKEY_OK) {
- return ret;
+ ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, keydev, sizeof(keydev));
+ if (ret != 0 || key_size != 32) {
+ mbedtls_platform_zeroize(keydev, sizeof(keydev));
+ mbedtls_ecdsa_free(&ecdsa);
+ return ret != 0 ? ret : PICOKEY_EXEC_ERROR;
}
- if (otp_key_1) {
- ret = aes_encrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata, 32);
- }
- ret = file_put_data(ef_keydev, kdata, (uint16_t)key_size);
- mbedtls_platform_zeroize(kdata, sizeof(kdata));
+ encrypt_keydev_f1(keydev);
+ mbedtls_platform_zeroize(keydev, sizeof(keydev));
mbedtls_ecdsa_free(&ecdsa);
if (ret != PICOKEY_OK) {
return ret;
@@ -341,21 +393,6 @@ int scan_files_fido() {
else {
printf("FATAL ERROR: KEY DEV not found in memory!\r\n");
}
- if (ef_mkek) { // No encrypted MKEK
- if (!file_has_data(ef_mkek)) {
- uint8_t mkek[MKEK_IV_SIZE + MKEK_KEY_SIZE];
- random_gen(NULL, mkek, sizeof(mkek));
- file_put_data(ef_mkek, mkek, sizeof(mkek));
- int ret = aes_encrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), file_get_data(ef_keydev), 32);
- mbedtls_platform_zeroize(mkek, sizeof(mkek));
- if (ret != 0) {
- printf("FATAL ERROR: MKEK encryption failed!\r\n");
- }
- }
- }
- else {
- printf("FATAL ERROR: MKEK not found in memory!\r\n");
- }
ef_certdev = search_by_fid(EF_EE_DEV, NULL, SPECIFY_EF);
if (ef_certdev) {
if (!file_has_data(ef_certdev)) {
@@ -399,13 +436,6 @@ int scan_files_fido() {
printf("FATAL ERROR: Global counter not found in memory!\r\n");
}
ef_pin = search_by_fid(EF_PIN, NULL, SPECIFY_EF);
- if (file_get_size(ef_pin) == 18) { // Upgrade PIN storage
- uint8_t pin_data[34] = { 0 }, dhash[32];
- memcpy(pin_data, file_get_data(ef_pin), 18);
- double_hash_pin(pin_data + 2, 16, dhash);
- memcpy(pin_data + 2, dhash, 32);
- file_put_data(ef_pin, pin_data, 34);
- }
ef_authtoken = search_by_fid(EF_AUTHTOKEN, NULL, SPECIFY_EF);
if (ef_authtoken) {
if (!file_has_data(ef_authtoken)) {
@@ -419,6 +449,19 @@ int scan_files_fido() {
else {
printf("FATAL ERROR: Auth Token not found in memory!\r\n");
}
+ file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF);
+ if (ef_pauthtoken) {
+ if (!file_has_data(ef_pauthtoken)) {
+ uint8_t t[32];
+ random_gen(NULL, t, sizeof(t));
+ file_put_data(ef_pauthtoken, t, sizeof(t));
+ }
+ ppaut.data = file_get_data(ef_pauthtoken);
+ ppaut.len = file_get_size(ef_pauthtoken);
+ }
+ else {
+ printf("FATAL ERROR: Persistent Auth Token not found in memory!\r\n");
+ }
ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF);
if (!file_has_data(ef_largeblob)) {
file_put_data(ef_largeblob, (const uint8_t *) "\x80\x76\xbe\x8b\x52\x8d\x00\x75\xf7\xaa\xe9\x8d\x6f\xa5\x7a\x6d\x3c", 17);
@@ -436,12 +479,14 @@ void scan_all() {
extern void init_otp();
void init_fido() {
scan_all();
+#ifdef ENABLE_OTP_APP
init_otp();
+#endif
}
bool wait_button_pressed() {
uint32_t val = EV_PRESS_BUTTON;
-#ifndef ENABLE_EMULATION
+#if defined(PICO_PLATFORM) || defined(ESP_PLATFORM)
queue_try_add(&card_to_usb_q, &val);
do {
queue_remove_blocking(&usb_to_card_q, &val);
@@ -485,11 +530,13 @@ extern int cmd_register();
extern int cmd_authenticate();
extern int cmd_version();
extern int cbor_parse(int, uint8_t *, size_t);
+extern void driver_init_hid();
#define CTAP_CBOR 0x10
int cmd_cbor() {
uint8_t *old_buf = res_APDU;
+ driver_init_hid();
int ret = cbor_parse(0x90, apdu.data, apdu.nc);
if (ret != 0) {
return SW_EXEC_ERROR();
diff --git a/src/fido/fido.h b/src/fido/fido.h
index c69836e..481aa6c 100644
--- a/src/fido/fido.h
+++ b/src/fido/fido.h
@@ -18,7 +18,7 @@
#ifndef _FIDO_H_
#define _FIDO_H_
-#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
+#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif
#ifndef ESP_PLATFORM
@@ -31,11 +31,7 @@
#ifdef MBEDTLS_EDDSA_C
#include "mbedtls/eddsa.h"
#endif
-#ifndef ENABLE_EMULATION
#include "hid/ctap_hid.h"
-#else
-#include
-#endif
#define CTAP_PUBKEY_LEN (65)
#define KEY_PATH_LEN (32)
@@ -55,20 +51,28 @@ extern void init_fido();
extern mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve);
extern int mbedtls_curve_to_fido(mbedtls_ecp_group_id id);
extern int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key);
-extern int load_keydev(uint8_t *key);
+extern int load_keydev(uint8_t key[32]);
extern int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out);
extern int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out);
extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret);
-#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256 P256
+#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256
#define FIDO2_ALG_EDDSA -8 //EdDSA
-#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384 P384
-#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512 P521
+#define FIDO2_ALG_ESP256 -9 //ECDSA-SHA256 P256
+#define FIDO2_ALG_ED25519 -19 //EDDSA Ed25519
+#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384
+#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512
#define FIDO2_ALG_ECDH_ES_HKDF_256 -25 //ECDH-ES + HKDF-256
#define FIDO2_ALG_ES256K -47
+#define FIDO2_ALG_ESP384 -51 //ECDSA-SHA384 P384
+#define FIDO2_ALG_ESP512 -52 //ECDSA-SHA512 P521
+#define FIDO2_ALG_ED448 -53 //EDDSA Ed448
#define FIDO2_ALG_RS256 -257
#define FIDO2_ALG_RS384 -258
#define FIDO2_ALG_RS512 -259
+#define FIDO2_ALG_ESB256 -265 //ECDSA-SHA256 BP256r1
+#define FIDO2_ALG_ESB384 -267 //ECDSA-SHA384 BP384r1
+#define FIDO2_ALG_ESB512 -268 //ECDSA-SHA512 BP512r1
#define FIDO2_CURVE_P256 1
#define FIDO2_CURVE_P384 2
@@ -78,6 +82,9 @@ extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSec
#define FIDO2_CURVE_ED25519 6
#define FIDO2_CURVE_ED448 7
#define FIDO2_CURVE_P256K1 8
+#define FIDO2_CURVE_BP256R1 9
+#define FIDO2_CURVE_BP384R1 10
+#define FIDO2_CURVE_BP512R1 11
#define FIDO2_AUT_FLAG_UP 0x1
#define FIDO2_AUT_FLAG_UV 0x4
@@ -129,9 +136,17 @@ typedef struct pinUvAuthToken {
bool user_verified;
} pinUvAuthToken_t;
+typedef struct persistentPinUvAuthToken {
+ uint8_t *data;
+ size_t len;
+ uint8_t permissions;
+} persistentPinUvAuthToken_t;
+
extern uint32_t user_present_time_limit;
extern pinUvAuthToken_t paut;
+extern persistentPinUvAuthToken_t ppaut;
+
extern int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t len, uint8_t *sign);
extern uint8_t session_pin[32];
diff --git a/src/fido/files.c b/src/fido/files.c
index 0f6b35b..bc278d5 100644
--- a/src/fido/files.c
+++ b/src/fido/files.c
@@ -27,6 +27,7 @@ file_t file_entries[] = {
{ .fid = EF_COUNTER, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global counter
{ .fid = EF_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PIN
{ .fid = EF_AUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // AUTH TOKEN
+ { .fid = EF_PAUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // PERSISTENT AUTH TOKEN
{ .fid = EF_MINPINLEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // MIN PIN LENGTH
{ .fid = EF_OPTS, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Global options
{ .fid = EF_LARGEBLOB, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = { 0xff } }, // Large Blob
diff --git a/src/fido/files.h b/src/fido/files.h
index 8434136..1fe844b 100644
--- a/src/fido/files.h
+++ b/src/fido/files.h
@@ -29,7 +29,9 @@
#define EF_OPTS 0xC001
#define EF_PIN 0x1080
#define EF_AUTHTOKEN 0x1090
+#define EF_PAUTHTOKEN 0x1091
#define EF_MINPINLEN 0x1100
+#define EF_PIN_COMPLEXITY_POLICY 0x1102
#define EF_DEV_CONF 0x1122
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF
#define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF
diff --git a/src/fido/kek.c b/src/fido/kek.c
index 1501060..a7b380f 100644
--- a/src/fido/kek.c
+++ b/src/fido/kek.c
@@ -15,10 +15,10 @@
* along with this program. If not, see .
*/
-#include "fido.h"
#include "pico_keys.h"
+#include "fido.h"
#include "stdlib.h"
-#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
+#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif
#include "kek.h"
@@ -85,46 +85,6 @@ void release_mkek(uint8_t *mkek) {
mbedtls_platform_zeroize(mkek, MKEK_SIZE);
}
-int store_mkek(const uint8_t *mkek) {
- uint8_t tmp_mkek[MKEK_SIZE];
- if (mkek == NULL) {
- const uint8_t *rd = random_bytes_get(MKEK_IV_SIZE + MKEK_KEY_SIZE);
- memcpy(tmp_mkek, rd, MKEK_IV_SIZE + MKEK_KEY_SIZE);
- }
- else {
- memcpy(tmp_mkek, mkek, MKEK_SIZE);
- }
- if (otp_key_1) {
- mkek_masked(tmp_mkek, otp_key_1);
- }
- *(uint32_t *) MKEK_CHECKSUM(tmp_mkek) = crc32c(MKEK_KEY(tmp_mkek), MKEK_KEY_SIZE);
- uint8_t tmp_mkek_pin[MKEK_SIZE];
- memcpy(tmp_mkek_pin, tmp_mkek, MKEK_SIZE);
- file_t *tf = search_file(EF_MKEK);
- if (!tf) {
- release_mkek(tmp_mkek);
- release_mkek(tmp_mkek_pin);
- return PICOKEY_ERR_FILE_NOT_FOUND;
- }
- aes_encrypt_cfb_256(session_pin, MKEK_IV(tmp_mkek_pin), MKEK_KEY(tmp_mkek_pin), MKEK_KEY_SIZE + MKEK_KEY_CS_SIZE);
- file_put_data(tf, tmp_mkek_pin, MKEK_SIZE);
- release_mkek(tmp_mkek_pin);
- low_flash_available();
- release_mkek(tmp_mkek);
- return PICOKEY_OK;
-}
-
-int mkek_encrypt(uint8_t *data, uint16_t len) {
- int r;
- uint8_t mkek[MKEK_SIZE + 4];
- if ((r = load_mkek(mkek)) != PICOKEY_OK) {
- return r;
- }
- r = aes_encrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), data, len);
- release_mkek(mkek);
- return r;
-}
-
int mkek_decrypt(uint8_t *data, uint16_t len) {
int r;
uint8_t mkek[MKEK_SIZE + 4];
diff --git a/src/fido/known_apps.c b/src/fido/known_apps.c
index 9fc2c93..f724528 100644
--- a/src/fido/known_apps.c
+++ b/src/fido/known_apps.c
@@ -15,6 +15,7 @@
* along with this program. If not, see .
*/
+#include "pico_keys.h"
#include "fido.h"
#include "ctap2_cbor.h"
diff --git a/src/fido/management.c b/src/fido/management.c
index b3025b1..3a61178 100644
--- a/src/fido/management.c
+++ b/src/fido/management.c
@@ -15,8 +15,8 @@
* along with this program. If not, see .
*/
-#include "fido.h"
#include "pico_keys.h"
+#include "fido.h"
#include "apdu.h"
#include "version.h"
#include "files.h"
@@ -40,7 +40,9 @@ int man_select(app_t *a, uint8_t force) {
apdu.ne = res_APDU_size;
if (force) {
scan_all();
+#ifdef ENABLE_OTP_APP
init_otp();
+#endif
}
return PICOKEY_OK;
}
@@ -114,7 +116,7 @@ int man_get_config() {
if (!file_has_data(ef)) {
res_APDU[res_APDU_size++] = TAG_USB_ENABLED;
res_APDU[res_APDU_size++] = 2;
- uint16_t caps = 0;
+ caps = 0;
if (cap_supported(CAP_FIDO2)) {
caps |= CAP_FIDO2;
}
diff --git a/src/fido/management.h b/src/fido/management.h
index e5d6bd1..4651c61 100644
--- a/src/fido/management.h
+++ b/src/fido/management.h
@@ -19,7 +19,7 @@
#define _MANAGEMENT_H_
#include
-#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
+#if defined(PICO_PLATFORM)
#include "pico/stdlib.h"
#endif
diff --git a/src/fido/oath.c b/src/fido/oath.c
index fc4e592..0c293c7 100644
--- a/src/fido/oath.c
+++ b/src/fido/oath.c
@@ -15,8 +15,8 @@
* along with this program. If not, see .
*/
-#include "fido.h"
#include "pico_keys.h"
+#include "fido.h"
#include "apdu.h"
#include "files.h"
#include "random.h"
@@ -24,6 +24,7 @@
#include "asn1.h"
#include "crypto_utils.h"
#include "management.h"
+extern bool is_nk;
#define MAX_OATH_CRED 255
#define CHALLENGE_LEN 8
@@ -44,6 +45,10 @@
#define TAG_PASSWORD 0x80
#define TAG_NEW_PASSWORD 0x81
#define TAG_PIN_COUNTER 0x82
+#define TAG_PWS_LOGIN 0x83
+#define TAG_PWS_PASSWORD 0x84
+#define TAG_PWS_METADATA 0x85
+#define TAG_SERIAL_NUMBER 0x8F
#define ALG_HMAC_SHA1 0x01
#define ALG_HMAC_SHA256 0x02
@@ -56,6 +61,7 @@
#define PROP_INC 0x01
#define PROP_TOUCH 0x02
+#define PROP_PIN 0x03
int oath_process_apdu();
int oath_unload();
@@ -99,6 +105,12 @@ int oath_select(app_t *a, uint8_t force) {
res_APDU[res_APDU_size++] = TAG_ALGO;
res_APDU[res_APDU_size++] = 1;
res_APDU[res_APDU_size++] = ALG_HMAC_SHA1;
+ if (is_nk) {
+ res_APDU[res_APDU_size++] = TAG_SERIAL_NUMBER;
+ res_APDU[res_APDU_size++] = 8;
+ memcpy(res_APDU + res_APDU_size, pico_serial_str, 8);
+ res_APDU_size += 8;
+ }
apdu.ne = res_APDU_size;
return PICOKEY_OK;
}
@@ -270,16 +282,27 @@ int cmd_list() {
if (validated == false) {
return SW_SECURITY_STATUS_NOT_SATISFIED();
}
+ bool ext = (apdu.nc == 1 && apdu.data[0] == 0x01);
for (int i = 0; i < MAX_OATH_CRED; i++) {
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
if (file_has_data(ef)) {
- asn1_ctx_t ctxi, key = { 0 }, name = { 0 };
+ asn1_ctx_t ctxi, key = { 0 }, name = { 0 }, pws = { 0 };
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == true && asn1_find_tag(&ctxi, TAG_KEY, &key) == true) {
res_APDU[res_APDU_size++] = TAG_NAME_LIST;
- res_APDU[res_APDU_size++] = (uint8_t)(name.len + 1);
+ res_APDU[res_APDU_size++] = (uint8_t)(name.len + 1 + (ext ? 1 : 0));
res_APDU[res_APDU_size++] = key.data[0];
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
+ if (ext) {
+ uint8_t props = 0x0;
+ if (asn1_find_tag(&ctxi, TAG_PWS_LOGIN, &pws) == true || asn1_find_tag(&ctxi, TAG_PWS_PASSWORD, &pws) == true || asn1_find_tag(&ctxi, TAG_PWS_METADATA, &pws) == true) {
+ props |= 0x4;
+ }
+ if (asn1_find_tag(&ctxi, TAG_PROPERTY, &pws) == true && (pws.data[0] & PROP_TOUCH)) {
+ props |= 0x1;
+ }
+ res_APDU[res_APDU_size++] = props;
+ }
}
}
}
@@ -615,7 +638,7 @@ int cmd_rename() {
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
return SW_WRONG_DATA();
}
- uint8_t *new_data = (uint8_t *) calloc(sizeof(uint8_t), fsize + new_name.len - name.len);
+ uint8_t *new_data = (uint8_t *) calloc(fsize + new_name.len - name.len, sizeof(uint8_t));
memcpy(new_data, fdata, name.data - fdata);
*(new_data + (name.data - fdata) - 1) = new_name.len;
memcpy(new_data + (name.data - fdata), new_name.data, new_name.len);
@@ -626,6 +649,53 @@ int cmd_rename() {
return SW_OK();
}
+int cmd_get_credential() {
+ asn1_ctx_t ctxi, name = { 0 };
+ if (apdu.nc < 3) {
+ return SW_INCORRECT_PARAMS();
+ }
+ if (apdu.data[0] != TAG_NAME) {
+ return SW_WRONG_DATA();
+ }
+ asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
+ if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
+ return SW_WRONG_DATA();
+ }
+ file_t *ef = find_oath_cred(name.data, name.len);
+ if (file_has_data(ef) == false) {
+ return SW_DATA_INVALID();
+ }
+ asn1_ctx_t login = { 0 }, pw = { 0 }, meta = { 0 }, prop = { 0 };
+ asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
+ if (asn1_find_tag(&ctxi, TAG_NAME, &name) == true) {
+ res_APDU[res_APDU_size++] = TAG_NAME;
+ res_APDU[res_APDU_size++] = (uint8_t)(name.len);
+ memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
+ }
+ if (asn1_find_tag(&ctxi, TAG_PWS_LOGIN, &login) == true) {
+ res_APDU[res_APDU_size++] = TAG_PWS_LOGIN;
+ res_APDU[res_APDU_size++] = (uint8_t)(login.len);
+ memcpy(res_APDU + res_APDU_size, login.data, login.len); res_APDU_size += login.len;
+ }
+ if (asn1_find_tag(&ctxi, TAG_PWS_PASSWORD, &pw) == true) {
+ res_APDU[res_APDU_size++] = TAG_PWS_PASSWORD;
+ res_APDU[res_APDU_size++] = (uint8_t)(pw.len);
+ memcpy(res_APDU + res_APDU_size, pw.data, pw.len); res_APDU_size += pw.len;
+ }
+ if (asn1_find_tag(&ctxi, TAG_PWS_METADATA, &meta) == true) {
+ res_APDU[res_APDU_size++] = TAG_PWS_METADATA;
+ res_APDU[res_APDU_size++] = (uint8_t)(meta.len);
+ memcpy(res_APDU + res_APDU_size, meta.data, meta.len); res_APDU_size += meta.len;
+ }
+ if (asn1_find_tag(&ctxi, TAG_PROPERTY, &prop) == true) {
+ res_APDU[res_APDU_size++] = TAG_PROPERTY;
+ res_APDU[res_APDU_size++] = (uint8_t)(prop.len);
+ memcpy(res_APDU + res_APDU_size, prop.data, prop.len); res_APDU_size += prop.len;
+ }
+ apdu.ne = res_APDU_size;
+ return SW_OK();
+}
+
#define INS_PUT 0x01
#define INS_DELETE 0x02
#define INS_SET_CODE 0x03
@@ -640,6 +710,7 @@ int cmd_rename() {
#define INS_VERIFY_PIN 0xb2
#define INS_CHANGE_PIN 0xb3
#define INS_SET_PIN 0xb4
+#define INS_GET_CREDENTIAL 0xb5
static const cmd_t cmds[] = {
{ INS_PUT, cmd_put },
@@ -656,6 +727,7 @@ static const cmd_t cmds[] = {
{ INS_CHANGE_PIN, cmd_change_otp_pin },
{ INS_VERIFY_PIN, cmd_verify_otp_pin },
{ INS_VERIFY_CODE, cmd_verify_hotp },
+ { INS_GET_CREDENTIAL, cmd_get_credential },
{ 0x00, 0x0 }
};
diff --git a/src/fido/otp.c b/src/fido/otp.c
index 582f72c..29d7da8 100644
--- a/src/fido/otp.c
+++ b/src/fido/otp.c
@@ -15,8 +15,8 @@
* along with this program. If not, see .
*/
-#include "fido.h"
#include "pico_keys.h"
+#include "fido.h"
#include "apdu.h"
#include "files.h"
#include "random.h"
@@ -24,14 +24,17 @@
#include "asn1.h"
#include "hid/ctap_hid.h"
#include "usb.h"
-#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
+#if defined(PICO_PLATFORM)
#include "bsp/board.h"
#endif
+#ifdef ENABLE_EMULATION
+void add_keyboard_buffer(const uint8_t *buf, size_t len, bool press_enter) {}
+void append_keyboard_buffer(const uint8_t *buf, size_t len) {}
+#else
+#include "tusb.h"
+#endif
#include "mbedtls/aes.h"
#include "management.h"
-#ifndef ENABLE_EMULATION
-#include "tusb.h"
-#endif
#define FIXED_SIZE 16
#define KEY_SIZE 16
@@ -116,12 +119,10 @@ uint16_t otp_status(bool is_otp);
int otp_process_apdu();
int otp_unload();
-#ifndef ENABLE_EMULATION
extern int (*hid_set_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
extern uint16_t (*hid_get_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);
int otp_hid_set_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
uint16_t otp_hid_get_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);
-#endif
const uint8_t otp_aid[] = {
7,
@@ -200,15 +201,12 @@ uint16_t calculate_crc(const uint8_t *data, size_t data_len) {
return crc & 0xFFFF;
}
-#ifndef ENABLE_EMULATION
static uint8_t session_counter[2] = { 0 };
-#endif
int otp_button_pressed(uint8_t slot) {
init_otp();
if (!cap_supported(CAP_OTP)) {
return 3;
}
-#ifndef ENABLE_EMULATION
file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
const uint8_t *data = file_get_data(ef);
otp_config_t *otp_config = (otp_config_t *) data;
@@ -218,6 +216,7 @@ int otp_button_pressed(uint8_t slot) {
if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) {
return 2;
}
+#ifdef ENABLE_OATH_APP
if (otp_config->tkt_flags & OATH_HOTP) {
uint8_t tmp_key[KEY_SIZE + 2];
tmp_key[0] = 0x01;
@@ -259,6 +258,7 @@ int otp_button_pressed(uint8_t slot) {
append_keyboard_buffer((const uint8_t *) "\r", 1);
}
}
+#endif
else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) {
uint8_t fixed_size = FIXED_SIZE + UID_SIZE + KEY_SIZE;
if (otp_config->cfg_flags & SHORT_TICKET) { // Not clear which is the purpose of SHORT_TICKET
@@ -317,19 +317,15 @@ int otp_button_pressed(uint8_t slot) {
low_flash_available();
}
}
-#else
- (void) slot;
-#endif
+
return 0;
}
INITIALIZER( otp_ctor ) {
register_app(otp_select, otp_aid);
button_pressed_cb = otp_button_pressed;
-#ifndef ENABLE_EMULATION
hid_set_report_cb = otp_hid_set_report_cb;
hid_get_report_cb = otp_hid_get_report_cb;
-#endif
}
int otp_unload() {
@@ -490,20 +486,20 @@ int cmd_otp() {
return SW_WRONG_DATA();
}
int ret = 0;
-#ifndef ENABLE_EMULATION
uint8_t *rdata_bk = apdu.rdata;
if (otp_config->cfg_flags & CHAL_BTN_TRIG) {
status_byte = 0x20;
otp_status(_is_otp);
+#ifndef ENABLE_EMULATION
if (wait_button() == true) {
status_byte = 0x00;
otp_status(_is_otp);
return SW_CONDITIONS_NOT_SATISFIED();
}
+#endif
status_byte = 0x10;
apdu.rdata = rdata_bk;
}
-#endif
if (p1 == 0x30 || p1 == 0x38) {
if (!(otp_config->cfg_flags & CHAL_HMAC)) {
return SW_WRONG_DATA();
@@ -568,8 +564,6 @@ int otp_process_apdu() {
return SW_INS_NOT_SUPPORTED();
}
-#ifndef ENABLE_EMULATION
-
uint8_t otp_frame_rx[70] = {0};
uint8_t otp_frame_tx[70] = {0};
uint8_t otp_exp_seq = 0, otp_curr_seq = 0;
@@ -671,5 +665,3 @@ uint16_t otp_hid_get_report_cb(uint8_t itf,
return reqlen;
}
-
-#endif
diff --git a/tests/conftest.py b/tests/conftest.py
index 8828e91..dce2795 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -20,12 +20,13 @@
from http import client
from fido2.hid import CtapHidDevice
-from fido2.client import Fido2Client, UserInteraction, ClientError, _Ctap1ClientBackend
+from fido2.client import Fido2Client, UserInteraction, ClientError, _Ctap1ClientBackend, DefaultClientDataCollector
from fido2.attestation import FidoU2FAttestation
from fido2.ctap2.pin import ClientPin
from fido2.server import Fido2Server
from fido2.ctap import CtapError
-from fido2.webauthn import CollectedClientData, PublicKeyCredentialParameters, PublicKeyCredentialType
+from fido2.webauthn import PublicKeyCredentialParameters, PublicKeyCredentialType, PublicKeyCredentialCreationOptions, PublicKeyCredentialRpEntity, PublicKeyCredentialUserEntity, AuthenticatorSelectionCriteria, UserVerificationRequirement, PublicKeyCredentialRequestOptions
+from fido2.ctap2.extensions import HmacSecretExtension, LargeBlobExtension, CredBlobExtension, CredProtectExtension, MinPinLengthExtension, CredPropsExtension, ThirdPartyPaymentExtension
from utils import *
from fido2.cose import ES256
import sys
@@ -70,11 +71,13 @@ class DeviceSelectCredential:
pass
class Device():
- def __init__(self, origin="https://example.com", user_interaction=CliInteraction(),uv="discouraged",rp={"id": "example.com", "name": "Example RP"}, attestation="direct"):
+ def __init__(self, origin="https://example.com", user_interaction=CliInteraction(), uv="discouraged", rp={"id": "example.com", "name": "Example RP"}, attestation="direct"):
self.__user = None
self.__set_client(origin=origin, user_interaction=user_interaction, uv=uv)
self.__set_server(rp=rp, attestation=attestation)
+ def __verify_rp(rp_id, origin):
+ return True
def __set_client(self, origin, user_interaction, uv):
self.__uv = uv
@@ -101,14 +104,23 @@ class Device():
sys.exit(1)
# Set up a FIDO 2 client using the origin https://example.com
- self.__client = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction)
+ extensions = [
+ HmacSecretExtension(allow_hmac_secret=True),
+ LargeBlobExtension(),
+ CredBlobExtension(),
+ CredProtectExtension(),
+ MinPinLengthExtension(),
+ CredPropsExtension(),
+ ThirdPartyPaymentExtension()
+ ]
+ self.__client = Fido2Client(self.__dev, client_data_collector=DefaultClientDataCollector(self.__origin, verify=Device.__verify_rp), user_interaction=self.__user_interaction, extensions=extensions)
# Prefer UV if supported and configured
if self.__client.info.options.get("uv") or self.__client.info.options.get("pinUvAuthToken"):
self.__uv = "preferred"
print("Authenticator supports User Verification")
- self.__client1 = Fido2Client(self.__dev, self.__origin, user_interaction=self.__user_interaction)
+ self.__client1 = Fido2Client(self.__dev, client_data_collector=DefaultClientDataCollector(self.__origin, verify=Device.__verify_rp), user_interaction=self.__user_interaction)
self.__client1._backend = _Ctap1ClientBackend(self.__dev, user_interaction=self.__user_interaction)
self.ctap1 = self.__client1._backend.ctap1
@@ -117,7 +129,7 @@ class Device():
self.__attestation = attestation
self.__server = Fido2Server(self.__rp, attestation=self.__attestation)
self.__server.allowed_algorithms = [
- PublicKeyCredentialParameters(PublicKeyCredentialType.PUBLIC_KEY, p['alg'])
+ PublicKeyCredentialParameters(type=PublicKeyCredentialType.PUBLIC_KEY, alg=p['alg'])
for p in self.__client._backend.info.algorithms
]
@@ -216,9 +228,7 @@ class Device():
'key_params':key_params}}
def doMC(self, client_data=Ellipsis, rp=Ellipsis, user=Ellipsis, key_params=Ellipsis, exclude_list=None, extensions=None, rk=None, user_verification=None, enterprise_attestation=None, event=None, ctap1=False):
- client_data = client_data if client_data is not Ellipsis else CollectedClientData.create(
- type=CollectedClientData.TYPE.CREATE, origin=self.__origin, challenge=os.urandom(32)
- )
+ client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
rp = rp if rp is not Ellipsis else self.__rp
user = user if user is not Ellipsis else self.user()
key_params = key_params if key_params is not Ellipsis else self.__server.allowed_algorithms
@@ -226,22 +236,31 @@ class Device():
client = self.__client1
else:
client = self.__client
- result = client._backend.do_make_credential(
- client_data=client_data,
- rp=rp,
- user=user,
- key_params=key_params,
- exclude_list=exclude_list,
+ options=PublicKeyCredentialCreationOptions(
+ rp=PublicKeyCredentialRpEntity.from_dict(rp),
+ user=PublicKeyCredentialUserEntity.from_dict(user),
+ pub_key_cred_params=key_params,
+ exclude_credentials=exclude_list,
extensions=extensions,
- rk=rk,
- user_verification=user_verification,
- enterprise_attestation=enterprise_attestation,
+ challenge=os.urandom(32),
+ authenticator_selection=AuthenticatorSelectionCriteria(
+ require_resident_key=rk,
+ user_verification=UserVerificationRequirement.REQUIRED if user_verification else UserVerificationRequirement.DISCOURAGED
+ ),
+ attestation=enterprise_attestation
+ )
+ client_data, rp_id = client_data.collect_client_data(options=options)
+ result = client._backend.do_make_credential(
+ options=options,
+ client_data=client_data,
+ rp_id=rp_id,
+ enterprise_rpid_list=None,
event=event
)
- return {'res':result,'req':{'client_data':client_data,
+ return {'res':result.response,'req':{'client_data':client_data,
'rp':rp,
'user':user,
- 'key_params':key_params}}
+ 'key_params':key_params},'client_extension_results':result.client_extension_results}
def try_make_credential(self, options=None):
if (options is None):
@@ -267,14 +286,14 @@ class Device():
# Complete registration
auth_data = self.__server.register_complete(
- state, result.client_data, result.attestation_object
+ state=state, response=result
)
credentials = [auth_data.credential_data]
print("New credential created!")
- print("CLIENT DATA:", result.client_data)
- print("ATTESTATION OBJECT:", result.attestation_object)
+ print("CLIENT DATA:", result.response.client_data)
+ print("ATTESTATION OBJECT:", result.response.attestation_object)
print()
print("CREDENTIAL DATA:", auth_data.credential_data)
@@ -294,17 +313,14 @@ class Device():
self.__server.authenticate_complete(
state,
credentials,
- result.credential_id,
- result.client_data,
- result.authenticator_data,
- result.signature,
+ result
)
print("Credential authenticated!")
- print("CLIENT DATA:", result.client_data)
+ print("CLIENT DATA:", result.response.client_data)
print()
- print("AUTH DATA:", result.authenticator_data)
+ print("AUTH DATA:", result.response.authenticator_data)
def GA(self, rp_id=Ellipsis, client_data_hash=Ellipsis, allow_list=None, extensions=None, options=None, pin_uv_param=None, pin_uv_protocol=None):
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
@@ -325,21 +341,31 @@ class Device():
return self.__client._backend.ctap2.get_next_assertion()
def doGA(self, client_data=Ellipsis, rp_id=Ellipsis, allow_list=None, extensions=None, user_verification=None, event=None, ctap1=False, check_only=False):
- client_data = client_data if client_data is not Ellipsis else CollectedClientData.create(
- type=CollectedClientData.TYPE.GET, origin=self.__origin, challenge=os.urandom(32)
- )
+ client_data = client_data if client_data is not Ellipsis else DefaultClientDataCollector(origin=self.__origin, verify=Device.__verify_rp)
+ if (ctap1 is True):
+ client = self.__client1
+ else:
+ client = self.__client
+
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
+ options=PublicKeyCredentialRequestOptions(
+ challenge=os.urandom(32),
+ rp_id=rp_id,
+ allow_credentials=allow_list,
+ user_verification=UserVerificationRequirement.REQUIRED if user_verification else UserVerificationRequirement.DISCOURAGED,
+ extensions=extensions
+ )
+ client_data, rp_id = client_data.collect_client_data(options=options)
+
if (ctap1 is True):
client = self.__client1
else:
client = self.__client
try:
result = client._backend.do_get_assertion(
+ options=options,
client_data=client_data,
rp_id=rp_id,
- allow_list=allow_list,
- extensions=extensions,
- user_verification=user_verification,
event=event
)
except ClientError as e:
@@ -347,11 +373,9 @@ class Device():
client_pin = ClientPin(self.__client._backend.ctap2)
client_pin.set_pin(DEFAULT_PIN)
result = client._backend.do_get_assertion(
+ options=options,
client_data=client_data,
rp_id=rp_id,
- allow_list=allow_list,
- extensions=extensions,
- user_verification=user_verification,
event=event
)
else:
@@ -416,8 +440,8 @@ def AuthRes(device, RegRes, *args):
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
], *args)
aut_data = res['res'].get_response(0)
- m = aut_data.authenticator_data.rp_id_hash + aut_data.authenticator_data.flags.to_bytes(1, 'big') + aut_data.authenticator_data.counter.to_bytes(4, 'big') + aut_data.client_data.hash
- ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.signature)
+ m = aut_data.response.authenticator_data.rp_id_hash + aut_data.response.authenticator_data.flags.to_bytes(1, 'big') + aut_data.response.authenticator_data.counter.to_bytes(4, 'big') + aut_data.response.client_data.hash
+ ES256(RegRes['res'].attestation_object.auth_data.credential_data.public_key).verify(m, aut_data.response.signature)
return aut_data
@pytest.fixture(scope="class")
diff --git a/tests/docker/bookworm/Dockerfile b/tests/docker/bookworm/Dockerfile
new file mode 100644
index 0000000..db2074a
--- /dev/null
+++ b/tests/docker/bookworm/Dockerfile
@@ -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 /
diff --git a/tests/docker/bullseye/Dockerfile b/tests/docker/bullseye/Dockerfile
index f4e20f8..b261379 100644
--- a/tests/docker/bullseye/Dockerfile
+++ b/tests/docker/bullseye/Dockerfile
@@ -22,11 +22,7 @@ RUN apt install -y libccid \
cmake \
libfuse-dev \
&& rm -rf /var/lib/apt/lists/*
-RUN pip3 install pytest pycvc cryptography pyscard inputimeout
-RUN git clone https://github.com/polhenarejos/python-fido2.git
-WORKDIR /python-fido2
-RUN git checkout development
-RUN pip3 install .
+RUN pip3 install pytest pycvc cryptography pyscard inputimeout fido2==2.0.0
WORKDIR /
RUN git clone https://github.com/frankmorgner/vsmartcard.git
WORKDIR /vsmartcard/virtualsmartcard
diff --git a/tests/docker/fido2/__init__.py b/tests/docker/fido2/__init__.py
index 83359b2..1755c76 100644
--- a/tests/docker/fido2/__init__.py
+++ b/tests/docker/fido2/__init__.py
@@ -27,16 +27,17 @@
from __future__ import annotations
-from .base import HidDescriptor
-from ..ctap import CtapDevice, CtapError, STATUS
-from ..utils import LOG_LEVEL_TRAFFIC
-from threading import Event
-from enum import IntEnum, IntFlag, unique
-from typing import Tuple, Optional, Callable, Iterator
+import logging
+import os
import struct
import sys
-import os
-import logging
+from enum import IntEnum, IntFlag, unique
+from threading import Event
+from typing import Callable, Iterator
+
+from ..ctap import STATUS, CtapDevice, CtapError
+from ..utils import LOG_LEVEL_TRAFFIC
+from .base import HidDescriptor
logger = logging.getLogger(__name__)
@@ -55,6 +56,7 @@ elif sys.platform.startswith("openbsd"):
from . import openbsd as backend
else:
raise Exception("Unsupported platform")
+
from . import emulation as backend
list_descriptors = backend.list_descriptors
@@ -62,6 +64,10 @@ get_descriptor = backend.get_descriptor
open_connection = backend.open_connection
+class ConnectionFailure(Exception):
+ """The CTAP connection failed or returned an invalid response."""
+
+
@unique
class CTAPHID(IntEnum):
PING = 0x01
@@ -109,7 +115,7 @@ class CtapHidDevice(CtapDevice):
response = self.call(CTAPHID.INIT, nonce)
r_nonce, response = response[:8], response[8:]
if r_nonce != nonce:
- raise Exception("Wrong nonce")
+ raise ConnectionFailure("Wrong nonce")
(
self._channel_id,
self._u2fhid_version,
@@ -129,7 +135,7 @@ class CtapHidDevice(CtapDevice):
return self._u2fhid_version
@property
- def device_version(self) -> Tuple[int, int, int]:
+ def device_version(self) -> tuple[int, int, int]:
"""Device version number."""
return self._device_version
@@ -139,12 +145,12 @@ class CtapHidDevice(CtapDevice):
return self._capabilities
@property
- def product_name(self) -> Optional[str]:
+ def product_name(self) -> str | None:
"""Product name of device."""
return self.descriptor.product_name
@property
- def serial_number(self) -> Optional[str]:
+ def serial_number(self) -> str | None:
"""Serial number of device."""
return self.descriptor.serial_number
@@ -159,10 +165,22 @@ class CtapHidDevice(CtapDevice):
self,
cmd: int,
data: bytes = b"",
- event: Optional[Event] = None,
- on_keepalive: Optional[Callable[[int], None]] = None,
+ event: Event | None = None,
+ on_keepalive: Callable[[STATUS], None] | None = None,
) -> bytes:
event = event or Event()
+
+ while True:
+ try:
+ return self._do_call(cmd, data, event, on_keepalive)
+ except CtapError as e:
+ if e.code == CtapError.ERR.CHANNEL_BUSY:
+ if not event.wait(0.1):
+ logger.warning("CTAP channel busy, trying again...")
+ continue # Keep retrying on BUSY while not cancelled
+ raise
+
+ def _do_call(self, cmd, data, event, on_keepalive):
remaining = data
seq = 0
@@ -194,7 +212,7 @@ class CtapHidDevice(CtapDevice):
r_channel = struct.unpack_from(">I", recv)[0]
recv = recv[4:]
if r_channel != self._channel_id:
- raise Exception("Wrong channel")
+ raise ConnectionFailure("Wrong channel")
if not response: # Initialization packet
r_cmd, r_len = struct.unpack_from(">BH", recv)
@@ -202,13 +220,12 @@ class CtapHidDevice(CtapDevice):
if r_cmd == TYPE_INIT | cmd:
pass # first data packet
elif r_cmd == TYPE_INIT | CTAPHID.KEEPALIVE:
- ka_status = struct.unpack_from(">B", recv)[0]
- logger.debug(f"Got keepalive status: {ka_status:02x}")
+ try:
+ ka_status = STATUS(struct.unpack_from(">B", recv)[0])
+ logger.debug(f"Got keepalive status: {ka_status:02x}")
+ except ValueError:
+ raise ConnectionFailure("Invalid keepalive status")
if on_keepalive and ka_status != last_ka:
- try:
- ka_status = STATUS(ka_status)
- except ValueError:
- pass # Unknown status value
last_ka = ka_status
on_keepalive(ka_status)
continue
@@ -220,7 +237,7 @@ class CtapHidDevice(CtapDevice):
r_seq = struct.unpack_from(">B", recv)[0]
recv = recv[1:]
if r_seq != seq:
- raise Exception("Wrong sequence number")
+ raise ConnectionFailure("Wrong sequence number")
seq += 1
response += recv
diff --git a/tests/docker_env.sh b/tests/docker_env.sh
index 384bc9c..7c8cebe 100644
--- a/tests/docker_env.sh
+++ b/tests/docker_env.sh
@@ -46,7 +46,7 @@
# default values, can be overridden by the environment
-: ${MBEDTLS_DOCKER_GUEST:=bullseye}
+: ${MBEDTLS_DOCKER_GUEST:=bookworm}
DOCKER_IMAGE_TAG="pico-hsm-test:${MBEDTLS_DOCKER_GUEST}"
diff --git a/tests/pico-fido/test_020_register.py b/tests/pico-fido/test_020_register.py
index 28c37e6..b43c4fe 100644
--- a/tests/pico-fido/test_020_register.py
+++ b/tests/pico-fido/test_020_register.py
@@ -20,8 +20,6 @@
from fido2.client import CtapError
from fido2.cose import ES256, ES384, ES512, EdDSA
-import fido2.features
-fido2.features.webauthn_json_mapping.enabled = False
from utils import ES256K
import pytest
@@ -51,13 +49,13 @@ def test_bad_type_cdh(device):
def test_missing_user(device):
with pytest.raises(CtapError) as e:
- device.doMC(user=None)
+ device.MC(user=None)
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
def test_bad_type_user_user(device):
with pytest.raises(CtapError) as e:
- device.doMC(user=b"12345678")
+ device.MC(user=b"12345678")
def test_missing_rp(device):
with pytest.raises(CtapError) as e:
@@ -71,7 +69,7 @@ def test_bad_type_rp(device):
def test_missing_pubKeyCredParams(device):
with pytest.raises(CtapError) as e:
- device.doMC(key_params=None)
+ device.MC(key_params=None)
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
@@ -93,35 +91,23 @@ def test_bad_type_options(device):
def test_bad_type_rp_name(device):
with pytest.raises(CtapError) as e:
- device.doMC(rp={"id": "test.org", "name": 8, "icon": "icon"})
+ device.MC(rp={"id": "test.org", "name": 8, "icon": "icon"})
def test_bad_type_rp_id(device):
with pytest.raises(CtapError) as e:
- device.doMC(rp={"id": 8, "name": "name", "icon": "icon"})
-
-def test_bad_type_rp_icon(device):
- with pytest.raises(CtapError) as e:
- device.doMC(rp={"id": "test.org", "name": "name", "icon": 8})
+ device.MC(rp={"id": 8, "name": "name", "icon": "icon"})
def test_bad_type_user_name(device):
with pytest.raises(CtapError) as e:
- device.doMC(user={"id": b"user_id", "name": 8})
+ device.MC(user={"id": b"user_id", "name": 8})
def test_bad_type_user_id(device):
with pytest.raises(CtapError) as e:
- device.doMC(user={"id": "user_id", "name": "name"})
+ device.MC(user={"id": "user_id", "name": "name"})
def test_bad_type_user_displayName(device):
with pytest.raises(CtapError) as e:
- device.doMC(user={"id": "user_id", "name": "name", "displayName": 8})
-
-def test_bad_type_user_icon(device):
- with pytest.raises(CtapError) as e:
- device.doMC(user={"id": "user_id", "name": "name", "icon": 8})
-
-def test_bad_type_pubKeyCredParams(device):
- with pytest.raises(CtapError) as e:
- device.doMC(key_params=["wrong"])
+ device.MC(user={"id": "user_id", "name": "name", "displayName": 8})
@pytest.mark.parametrize(
"alg", [ES256.ALGORITHM, ES384.ALGORITHM, ES512.ALGORITHM, ES256K.ALGORITHM, EdDSA.ALGORITHM]
@@ -132,13 +118,13 @@ def test_algorithms(device, info, alg):
def test_missing_pubKeyCredParams_type(device):
with pytest.raises(CtapError) as e:
- device.doMC(key_params=[{"alg": ES256.ALGORITHM}])
+ device.MC(key_params=[{"alg": ES256.ALGORITHM}])
assert e.value.code == CtapError.ERR.INVALID_CBOR
def test_missing_pubKeyCredParams_alg(device):
with pytest.raises(CtapError) as e:
- device.doMC(key_params=[{"type": "public-key"}])
+ device.MC(key_params=[{"type": "public-key"}])
assert e.value.code in [
CtapError.ERR.INVALID_CBOR,
@@ -147,7 +133,7 @@ def test_missing_pubKeyCredParams_alg(device):
def test_bad_type_pubKeyCredParams_alg(device):
with pytest.raises(CtapError) as e:
- device.doMC(key_params=[{"alg": "7", "type": "public-key"}])
+ device.MC(key_params=[{"alg": "7", "type": "public-key"}])
assert e.value.code == CtapError.ERR.CBOR_UNEXPECTED_TYPE
@@ -158,26 +144,26 @@ def test_unsupported_algorithm(device):
assert e.value.code == CtapError.ERR.UNSUPPORTED_ALGORITHM
def test_exclude_list(resetdevice):
- resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "rot13"}])
+ resetdevice.MC(exclude_list=[{"id": b"1234", "type": "rot13"}])
def test_exclude_list2(resetdevice):
- resetdevice.doMC(exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}])
+ resetdevice.MC(exclude_list=[{"id": b"1234", "type": "mangoPapayaCoconutNotAPublicKey"}])
def test_bad_type_exclude_list(device):
with pytest.raises(CtapError) as e:
- device.doMC(exclude_list=["1234"])
+ device.MC(exclude_list=["1234"])
def test_missing_exclude_list_type(device):
with pytest.raises(CtapError) as e:
- device.doMC(exclude_list=[{"id": b"1234"}])
+ device.MC(exclude_list=[{"id": b"1234"}])
def test_missing_exclude_list_id(device):
with pytest.raises(CtapError) as e:
- device.doMC(exclude_list=[{"type": "public-key"}])
+ device.MC(exclude_list=[{"type": "public-key"}])
def test_bad_type_exclude_list_id(device):
with pytest.raises(CtapError) as e:
- device.doMC(exclude_list=[{"type": "public-key", "id": "1234"}])
+ device.MC(exclude_list=[{"type": "public-key", "id": "1234"}])
def test_bad_type_exclude_list_type(device):
with pytest.raises(CtapError) as e:
diff --git a/tests/pico-fido/test_021_authenticate.py b/tests/pico-fido/test_021_authenticate.py
index 14260b7..96e5338 100644
--- a/tests/pico-fido/test_021_authenticate.py
+++ b/tests/pico-fido/test_021_authenticate.py
@@ -31,10 +31,10 @@ def test_authenticate(device):
AUTRes = device.authenticate(credentials)
def test_assertion_auth_data(GARes):
- assert len(GARes['res'].get_response(0).authenticator_data) == 37
+ assert len(GARes['res'].get_response(0).response.authenticator_data) == 37
def test_Check_that_AT_flag_is_not_set(GARes):
- assert (GARes['res'].get_response(0).authenticator_data.flags & 0xF8) == 0
+ assert (GARes['res'].get_response(0).response.authenticator_data.flags & 0xF8) == 0
def test_that_user_credential_and_numberOfCredentials_are_not_present(device, MCRes):
res = device.GA(allow_list=[
@@ -63,8 +63,8 @@ def test_get_assertion_allow_list_filtering_and_buffering(device):
""" Check that authenticator filters and stores items in allow list correctly """
allow_list = []
- rp1 = {"id": "rp1.com", "name": "rp1.com"}
- rp2 = {"id": "rp2.com", "name": "rp2.com"}
+ rp1 = {"id": "example.com", "name": "rp1.com"}
+ rp2 = {"id": "example.com", "name": "rp2.com"}
rp1_registrations = []
rp2_registrations = []
@@ -127,7 +127,7 @@ def test_mismatched_rp(device, GARes):
rp_id += ".com"
with pytest.raises(CtapError) as e:
- device.doGA(rp_id=rp_id)
+ device.GA(rp_id=rp_id)
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_missing_rp(device):
@@ -137,7 +137,7 @@ def test_missing_rp(device):
def test_bad_rp(device):
with pytest.raises(CtapError) as e:
- device.doGA(rp_id={"id": {"type": "wrong"}})
+ device.GA(rp_id={"id": {"type": "wrong"}})
def test_missing_cdh(device):
with pytest.raises(CtapError) as e:
@@ -150,11 +150,11 @@ def test_bad_cdh(device):
def test_bad_allow_list(device):
with pytest.raises(CtapError) as e:
- device.doGA(allow_list={"type": "wrong"})
+ device.GA(allow_list={"type": "wrong"})
def test_bad_allow_list_item(device, MCRes):
with pytest.raises(CtapError) as e:
- device.doGA(allow_list=["wrong"] + [
+ device.GA(allow_list=["wrong"] + [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
@@ -177,7 +177,7 @@ def test_option_up(device, info, GARes):
assert res.auth_data.flags & (1 << 0)
def test_allow_list_fake_item(device, MCRes):
- device.doGA(allow_list=[{"type": "rot13", "id": b"1234"}]
+ device.GA(allow_list=[{"type": "rot13", "id": b"1234"}]
+ [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
],
@@ -185,7 +185,7 @@ def test_allow_list_fake_item(device, MCRes):
def test_allow_list_missing_field(device, MCRes):
with pytest.raises(CtapError) as e:
- device.doGA(allow_list=[{"id": b"1234"}] + [
+ device.GA(allow_list=[{"id": b"1234"}] + [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
@@ -200,7 +200,7 @@ def test_allow_list_field_wrong_type(device, MCRes):
def test_allow_list_id_wrong_type(device, MCRes):
with pytest.raises(CtapError) as e:
- device.doGA(allow_list=[{"type": "public-key", "id": 42}]
+ device.GA(allow_list=[{"type": "public-key", "id": 42}]
+ [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
@@ -208,7 +208,7 @@ def test_allow_list_id_wrong_type(device, MCRes):
def test_allow_list_missing_id(device, MCRes):
with pytest.raises(CtapError) as e:
- device.doGA(allow_list=[{"type": "public-key"}] + [
+ device.GA(allow_list=[{"type": "public-key"}] + [
{"id": MCRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
]
)
diff --git a/tests/pico-fido/test_022_discoverable.py b/tests/pico-fido/test_022_discoverable.py
index d1d3c3f..36b7055 100644
--- a/tests/pico-fido/test_022_discoverable.py
+++ b/tests/pico-fido/test_022_discoverable.py
@@ -58,8 +58,9 @@ def test_with_allow_list_after_reset(device, MCRes_DC, GARes_DC):
device.reset()
# It returns a silent authentication
- ga_res = device.doGA(allow_list=allow_list)
-
+ with pytest.raises(CtapError) as e:
+ ga_res = device.doGA(allow_list=allow_list)
+ assert e.value.code == CtapError.ERR.NO_CREDENTIALS
def test_resident_key(MCRes_DC, info):
@@ -85,7 +86,7 @@ def test_multiple_rk_nodisplay(device, MCRes_DC):
auths = []
regs = []
# Use unique RP to not collide with other credentials
- rp = {"id": f"unique-{random.random()}.com", "name": "Example"}
+ rp = {"id": "example.com", "name": "Example"}
for i in range(0, 3):
res = device.doMC(rp=rp, rk=True, user=generate_random_user())
regs.append(res)
@@ -116,7 +117,7 @@ def test_rk_maximum_size_nodisplay(device):
auths = resGA.get_assertions()
user_max_GA = auths[0]
- print(auths)
+
for y in ("name", "displayName", "id"):
if (y in user_max_GA):
assert user_max_GA.user[y] == user_max[y]
@@ -126,7 +127,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
"""
Test maximum returned capacity of the RK for the given RP
"""
-
+ device.reset()
# Try to determine from get_info, or default to 19.
RK_CAPACITY_PER_RP = info.max_creds_in_list
if not RK_CAPACITY_PER_RP:
@@ -140,7 +141,7 @@ def test_rk_maximum_list_capacity_per_rp_nodisplay(info, device, MCRes_DC):
return user
# Use unique RP to not collide with other credentials from other tests.
- rp = {"id": f"unique-{random.random()}.com", "name": "Example"}
+ rp = {"id": "example.com", "name": "Example"}
# req = FidoRequest(MCRes_DC, options=None, user=get_user(), rp = rp)
# res = device.sendGA(*req.toGA())
@@ -183,10 +184,10 @@ def test_rk_with_allowlist_of_different_rp(resetdevice):
"""
rk_rp = {"id": "rk-cred.org", "name": "Example"}
- rk_res = resetdevice.doMC(rp = rk_rp, rk=True)['res'].attestation_object
+ rk_res = resetdevice.MC(rp = rk_rp, options={"rk":True})['res']
server_rp = {"id": "server-cred.com", "name": "Example"}
- server_res = resetdevice.doMC(rp = server_rp, rk=True)['res'].attestation_object
+ server_res = resetdevice.MC(rp = server_rp, options={"rk":True})['res']
allow_list_with_different_rp_cred = [
{
@@ -197,7 +198,7 @@ def test_rk_with_allowlist_of_different_rp(resetdevice):
with pytest.raises(CtapError) as e:
- res = resetdevice.doGA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred)
+ res = resetdevice.GA(rp_id = rk_rp['id'], allow_list = allow_list_with_different_rp_cred)
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
@@ -208,10 +209,10 @@ def test_same_userId_overwrites_rk(resetdevice):
rp = {"id": "overwrite.org", "name": "Example"}
user = generate_random_user()
- mc_res1 = resetdevice.doMC(rp = rp, rk=True, user = user)
+ mc_res1 = resetdevice.MC(rp = rp, options={"rk":True}, user = user)
# Should overwrite the first credential.
- mc_res2 = resetdevice.doMC(rp = rp, rk=True, user = user)
+ mc_res2 = resetdevice.MC(rp = rp, options={"rk":True}, user = user)
ga_res = resetdevice.GA(rp_id=rp['id'])['res']
@@ -227,7 +228,7 @@ def test_larger_icon_than_128(device):
user = generate_random_user()
user['icon'] = 'https://www.w3.org/TR/webauthn/?icon=' + ("A" * 128)
- device.doMC(rp = rp, rk=True, user = user)
+ device.MC(rp = rp, options={"rk":True}, user = user)
def test_returned_credential(device):
diff --git a/tests/pico-fido/test_031_blob.py b/tests/pico-fido/test_031_blob.py
index f5d2a73..6aba82d 100644
--- a/tests/pico-fido/test_031_blob.py
+++ b/tests/pico-fido/test_031_blob.py
@@ -21,6 +21,7 @@
import pytest
from fido2.ctap import CtapError
from fido2.ctap2.pin import PinProtocolV2, ClientPin
+from fido2.utils import websafe_decode
from utils import verify
import os
@@ -46,22 +47,24 @@ def GACredBlob(device, MCCredBlob):
@pytest.fixture(scope="function")
def MCLBK(device):
- res = device.doMC(
+ mc = device.doMC(
rk=True,
extensions={'largeBlob':{'support':'required'}}
- )['res']
- return res
+ )
+ res = mc['res']
+ ext = mc['client_extension_results']
+ return {'res': res, 'ext': ext}
@pytest.fixture(scope="function")
def GALBRead(device, MCLBK):
res = device.doGA(
allow_list=[
- {"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
+ {"id": MCLBK['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
],extensions={'largeBlob':{'read': True}}
)
assertions = res['res'].get_assertions()
for a in assertions:
- verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
+ verify(MCLBK['res'].attestation_object, a, res['req']['client_data'].hash)
return res['res']
@pytest.fixture(scope="function")
@@ -70,18 +73,19 @@ def GALBReadLBK(GALBRead):
@pytest.fixture(scope="function")
def GALBReadLB(GALBRead):
+ print(GALBRead.get_response(0))
return GALBRead.get_response(0)
@pytest.fixture(scope="function")
def GALBWrite(device, MCLBK):
res = device.doGA(
allow_list=[
- {"id": MCLBK.attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
+ {"id": MCLBK['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
],extensions={'largeBlob':{'write': LARGE_BLOB}}
)
assertions = res['res'].get_assertions()
for a in assertions:
- verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
+ verify(MCLBK['res'].attestation_object, a, res['req']['client_data'].hash)
return res['res'].get_response(0)
def test_supports_credblob(info):
@@ -136,15 +140,17 @@ def test_supports_largeblobs(info):
assert info.max_large_blob is None or (info.max_large_blob > 1024)
def test_get_largeblobkey_mc(MCLBK):
- assert 'supported' in MCLBK.extension_results
- assert MCLBK.extension_results['supported'] is True
+ assert 'largeBlob' in MCLBK['ext']
+ assert 'supported' in MCLBK['ext']['largeBlob']
+ assert MCLBK['ext']['largeBlob']['supported'] is True
def test_get_largeblobkey_ga(GALBReadLBK):
assert GALBReadLBK.large_blob_key is not None
def test_get_largeblob_rw(GALBWrite, GALBReadLB):
- assert 'written' in GALBWrite.extension_results
- assert GALBWrite.extension_results['written'] is True
+ assert 'largeBlob' in GALBWrite.client_extension_results
+ assert 'written' in GALBWrite.client_extension_results['largeBlob']
+ assert GALBWrite.client_extension_results['largeBlob']['written'] is True
- assert 'blob' in GALBReadLB.extension_results
- assert GALBReadLB.extension_results['blob'] == LARGE_BLOB
+ assert 'blob' in GALBReadLB.client_extension_results['largeBlob']
+ assert websafe_decode(GALBReadLB.client_extension_results['largeBlob']['blob']) == LARGE_BLOB
diff --git a/tests/pico-fido/test_033_credprotect.py b/tests/pico-fido/test_033_credprotect.py
index 83e02c8..b4f4438 100644
--- a/tests/pico-fido/test_033_credprotect.py
+++ b/tests/pico-fido/test_033_credprotect.py
@@ -22,6 +22,7 @@ import pytest
from fido2.ctap2.extensions import CredProtectExtension
from fido2.webauthn import UserVerificationRequirement
from fido2.ctap import CtapError
+from utils import generate_random_user
class CredProtect:
UserVerificationOptional = 1
@@ -30,140 +31,139 @@ class CredProtect:
@pytest.fixture(scope="class")
def MCCredProtectOptional(resetdevice):
- res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL})['res'].attestation_object
+ res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL})['res'].attestation_object
return res
@pytest.fixture(scope="class")
def MCCredProtectOptionalList(resetdevice):
- res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST})['res'].attestation_object
+ res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST})['res'].attestation_object
return res
@pytest.fixture(scope="class")
def MCCredProtectRequired(resetdevice):
- res = resetdevice.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object
+ res = resetdevice.doMC(rk=True, user=generate_random_user(), extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED})['res'].attestation_object
return res
+class TestCredProtect(object):
+ def test_credprotect_make_credential_1(self, MCCredProtectOptional):
+ assert MCCredProtectOptional.auth_data.extensions
+ assert "credProtect" in MCCredProtectOptional.auth_data.extensions
+ assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1
-def test_credprotect_make_credential_1(MCCredProtectOptional):
- assert MCCredProtectOptional.auth_data.extensions
- assert "credProtect" in MCCredProtectOptional.auth_data.extensions
- assert MCCredProtectOptional.auth_data.extensions["credProtect"] == 1
+ def test_credprotect_make_credential_2(self, MCCredProtectOptionalList):
+ assert MCCredProtectOptionalList.auth_data.extensions
+ assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions
+ assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2
-def test_credprotect_make_credential_2(MCCredProtectOptionalList):
- assert MCCredProtectOptionalList.auth_data.extensions
- assert "credProtect" in MCCredProtectOptionalList.auth_data.extensions
- assert MCCredProtectOptionalList.auth_data.extensions["credProtect"] == 2
+ def test_credprotect_make_credential_3(self, MCCredProtectRequired):
+ assert MCCredProtectRequired.auth_data.extensions
+ assert "credProtect" in MCCredProtectRequired.auth_data.extensions
+ assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3
-def test_credprotect_make_credential_3(MCCredProtectRequired):
- assert MCCredProtectRequired.auth_data.extensions
- assert "credProtect" in MCCredProtectRequired.auth_data.extensions
- assert MCCredProtectRequired.auth_data.extensions["credProtect"] == 3
+ def test_credprotect_optional_excluded(self, device, MCCredProtectOptional):
+ """ CredProtectOptional Cred should be visible to be excluded with no UV """
+ exclude_list = [
+ {
+ "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
+ "type": "public-key",
+ }
+ ]
-def test_credprotect_optional_excluded(device, MCCredProtectOptional):
- """ CredProtectOptional Cred should be visible to be excluded with no UV """
- exclude_list = [
- {
- "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
- "type": "public-key",
- }
- ]
+ with pytest.raises(CtapError) as e:
+ device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)
- with pytest.raises(CtapError) as e:
- device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL}, exclude_list=exclude_list)
+ assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
- assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
+ def test_credprotect_optional_list_excluded(self, device, MCCredProtectOptionalList):
+ """ CredProtectOptionalList Cred should be visible to be excluded with no UV """
+ exclude_list = [
+ {
+ "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
+ "type": "public-key",
+ }
+ ]
-def test_credprotect_optional_list_excluded(device, MCCredProtectOptionalList):
- """ CredProtectOptionalList Cred should be visible to be excluded with no UV """
- exclude_list = [
- {
- "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
- "type": "public-key",
- }
- ]
+ with pytest.raises(CtapError) as e:
+ device.MC(options={'rk': True}, extensions={'credProtect': CredProtect.UserVerificationOptionalWithCredentialId}, exclude_list=exclude_list)
- with pytest.raises(CtapError) as e:
- device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL_WITH_LIST}, exclude_list=exclude_list)
+ assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
- assert e.value.code == CtapError.ERR.CREDENTIAL_EXCLUDED
+ def test_credprotect_required_not_excluded_with_no_uv(self, device, MCCredProtectRequired):
+ """ CredProtectRequired Cred should NOT be visible to be excluded with no UV """
+ exclude_list = [
+ {
+ "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
+ "type": "public-key",
+ }
+ ]
-def test_credprotect_required_not_excluded_with_no_uv(device, MCCredProtectRequired):
- """ CredProtectRequired Cred should NOT be visible to be excluded with no UV """
- exclude_list = [
- {
- "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
- "type": "public-key",
- }
- ]
+ # works
+ device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)
- # works
- device.doMC(rk=True, extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.REQUIRED}, exclude_list=exclude_list)
+ def test_credprotect_optional_works_with_no_allowList_no_uv(self, device, MCCredProtectOptional):
-def test_credprotect_optional_works_with_no_allowList_no_uv(device, MCCredProtectOptional):
+ # works
+ res = device.doGA()['res'].get_assertions()[0]
- # works
- res = device.doGA()['res'].get_assertions()[0]
+ # If there's only one credential, this is None
+ assert res.number_of_credentials == None
- # If there's only one credential, this is None
- assert res.number_of_credentials == None
+ def test_credprotect_optional_and_list_works_no_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired):
+ allow_list = [
+ {
+ "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
+ "type": "public-key",
+ },
+ {
+ "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
+ "type": "public-key",
+ },
+ {
+ "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
+ "type": "public-key",
+ },
+ ]
+ # works
+ res1 = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()[0]
+ assert res1.number_of_credentials in (None, 2)
-def test_credprotect_optional_and_list_works_no_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired):
- allow_list = [
- {
- "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
- "type": "public-key",
- },
- {
- "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
- "type": "public-key",
- },
- {
- "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
- "type": "public-key",
- },
- ]
- # works
- res1 = device.doGA(allow_list=allow_list)['res'].get_assertions()[0]
- assert res1.number_of_credentials in (None, 2)
+ results = device.doGA(allow_list=allow_list, user_verification=False)['res'].get_assertions()
- results = device.doGA(allow_list=allow_list)['res'].get_assertions()
+ # the required credProtect is not returned.
+ for res in results:
+ assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:]
- # the required credProtect is not returned.
- for res in results:
- assert res.credential["id"] != MCCredProtectRequired.auth_data.credential_data.credential_id[:]
+ def test_hmac_secret_and_credProtect_make_credential(self, resetdevice, MCCredProtectOptional):
-def test_hmac_secret_and_credProtect_make_credential(resetdevice, MCCredProtectOptional
-):
+ res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object
- res = resetdevice.doMC(extensions={'credentialProtectionPolicy': CredProtectExtension.POLICY.OPTIONAL, 'hmacCreateSecret': True})['res'].attestation_object
+ for ext in ["credProtect", "hmac-secret"]:
+ assert res.auth_data.extensions
+ assert ext in res.auth_data.extensions
+ assert res.auth_data.extensions[ext] == True
- for ext in ["credProtect", "hmac-secret"]:
- assert res.auth_data.extensions
- assert ext in res.auth_data.extensions
- assert res.auth_data.extensions[ext] == True
+class TestCredProtectUv:
+ def test_credprotect_all_with_uv(self, device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin):
+ allow_list = [
+ {
+ "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
+ "type": "public-key",
+ },
+ {
+ "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
+ "type": "public-key",
+ },
+ {
+ "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
+ "type": "public-key",
+ },
+ ]
+ pin = "12345678"
-def test_credprotect_all_with_uv(device, MCCredProtectOptional, MCCredProtectOptionalList, MCCredProtectRequired, client_pin):
- allow_list = [
- {
- "id": MCCredProtectOptional.auth_data.credential_data.credential_id[:],
- "type": "public-key",
- },
- {
- "id": MCCredProtectOptionalList.auth_data.credential_data.credential_id[:],
- "type": "public-key",
- },
- {
- "id": MCCredProtectRequired.auth_data.credential_data.credential_id[:],
- "type": "public-key",
- },
- ]
+ client_pin.set_pin(pin)
- pin = "12345678"
+ res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]
- client_pin.set_pin(pin)
-
- res1 = device.doGA(user_verification=UserVerificationRequirement.REQUIRED, allow_list=allow_list)['res'].get_assertions()[0]
-
- assert res1.number_of_credentials in (None, 3)
+ assert res1.number_of_credentials in (None, 3)
diff --git a/tests/pico-fido/test_035_hmac_secret.py b/tests/pico-fido/test_035_hmac_secret.py
index ed2861c..ccd276c 100644
--- a/tests/pico-fido/test_035_hmac_secret.py
+++ b/tests/pico-fido/test_035_hmac_secret.py
@@ -24,6 +24,7 @@ from fido2.ctap2.extensions import HmacSecretExtension
from fido2.utils import hmac_sha256
from fido2.ctap2.pin import PinProtocolV2
from fido2.webauthn import UserVerificationRequirement
+from fido2.client import ClientError
from utils import *
salt1 = b"\xa5" * 32
@@ -38,10 +39,6 @@ def MCHmacSecret(resetdevice):
res = resetdevice.doMC(extensions={"hmacCreateSecret": True},rk=True)
return res['res'].attestation_object
-@pytest.fixture(scope="class")
-def hmac(resetdevice):
- return HmacSecretExtension(resetdevice.client()._backend.ctap2, pin_protocol=PinProtocolV2())
-
def test_hmac_secret_make_credential(MCHmacSecret):
assert MCHmacSecret.auth_data.extensions
assert "hmac-secret" in MCHmacSecret.auth_data.extensions
@@ -55,51 +52,51 @@ def test_fake_extension(device):
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
-def test_hmac_secret_entropy(device, MCHmacSecret, hmac, salts
+def test_hmac_secret_entropy(device, MCHmacSecret, salts
):
hout = {'salt1':salts[0]}
if (len(salts) > 1):
hout['salt2'] = salts[1]
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
- ext = auth.extension_results
+ ext = auth.client_extension_results
assert ext
assert "hmacGetSecret" in ext
- assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
+ assert len(auth.response.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
- #print(shannon_entropy(auth.authenticator_data.extensions['hmac-secret']))
+ #print(shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']))
if len(salts) == 1:
- assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 4.5
- assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5
+ assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 4.5
+ assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
if len(salts) == 2:
- assert shannon_entropy(auth.authenticator_data.extensions['hmac-secret']) > 5.4
- assert shannon_entropy(ext["hmacGetSecret"]['output1']) > 4.5
- assert shannon_entropy(ext["hmacGetSecret"]['output2']) > 4.5
+ assert shannon_entropy(auth.response.authenticator_data.extensions['hmac-secret']) > 5.4
+ assert shannon_entropy(ext.hmac_get_secret.output1) > 4.5
+ assert shannon_entropy(ext.hmac_get_secret.output2) > 4.5
-def get_output(device, MCHmacSecret, hmac, salts):
+def get_output(device, MCHmacSecret, salts):
hout = {'salt1':salts[0]}
if (len(salts) > 1):
hout['salt2'] = salts[1]
auth = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
- ext = auth.extension_results
+ ext = auth.client_extension_results
assert ext
assert "hmacGetSecret" in ext
- assert len(auth.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
+ assert len(auth.response.authenticator_data.extensions['hmac-secret']) == len(salts) * 32 + 16
if len(salts) == 2:
- return ext["hmacGetSecret"]['output1'], ext["hmacGetSecret"]['output2']
+ return ext.hmac_get_secret.output1, ext.hmac_get_secret.output2
else:
- return ext["hmacGetSecret"]['output1']
+ return ext.hmac_get_secret.output1
-def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
- output1 = get_output(device, MCHmacSecret, hmac, (salt1,))
+def test_hmac_secret_sanity(device, MCHmacSecret):
+ output1 = get_output(device, MCHmacSecret, (salt1,))
output12 = get_output(
- device, MCHmacSecret, hmac, (salt1, salt2)
+ device, MCHmacSecret, (salt1, salt2)
)
output21 = get_output(
- device, MCHmacSecret, hmac, (salt2, salt1)
+ device, MCHmacSecret, (salt2, salt1)
)
assert output12[0] == output1
@@ -107,60 +104,60 @@ def test_hmac_secret_sanity(device, MCHmacSecret, hmac):
assert output21[0] == output12[1]
assert output12[0] != output12[1]
-def test_missing_keyAgreement(device, hmac):
- hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
+def test_missing_keyAgreement(device):
with pytest.raises(CtapError):
- device.GA(extensions={"hmac-secret": {2: hout[2], 3: hout[3]}})
+ device.GA(extensions={"hmac-secret": {2: b'1234', 3: b'1234'}})
-def test_missing_saltAuth(device, hmac):
- hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
+def test_missing_saltAuth(device):
with pytest.raises(CtapError) as e:
- device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2]}})
+ device.GA(extensions={"hmac-secret": {2: b'1234'}})
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
-def test_missing_saltEnc(device, hmac):
- hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
+def test_missing_saltEnc(device,):
with pytest.raises(CtapError) as e:
- device.GA(extensions={"hmac-secret": {1: hout[1], 3: hout[3]}})
+ device.GA(extensions={"hmac-secret": { 3: b'1234'}})
assert e.value.code == CtapError.ERR.MISSING_PARAMETER
-def test_bad_auth(device, hmac, MCHmacSecret):
+def test_bad_auth(device, MCHmacSecret):
- hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salt3}})
- bad_auth = list(hout[3][:])
- bad_auth[len(bad_auth) // 2] = bad_auth[len(bad_auth) // 2] ^ 1
- bad_auth = bytes(bad_auth)
+ key_agreement = {
+ 1: 2,
+ 3: -25, # Per the spec, "although this is NOT the algorithm actually used"
+ -1: 1,
+ -2: b'\x00'*32,
+ -3: b'\x00'*32,
+ }
with pytest.raises(CtapError) as e:
- device.GA(extensions={"hmac-secret": {1: hout[1], 2: hout[2], 3: bad_auth, 4: 2}})
+ device.GA(extensions={"hmac-secret": {1: key_agreement, 2: b'\x00'*80, 3: b'\x00'*32, 4: 2}})
assert e.value.code == CtapError.ERR.EXTENSION_FIRST
@pytest.mark.parametrize("salts", [(salt4,), (salt4, salt5)])
-def test_invalid_salt_length(device, hmac, salts):
- with pytest.raises(ValueError) as e:
+def test_invalid_salt_length(device, salts):
+ with pytest.raises((CtapError,ClientError)) as e:
if (len(salts) == 2):
- hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
+ hout = {"salt1":salts[0],"salt2":salts[1]}
else:
- hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
+ hout = {"salt1":salts[0]}
device.doGA(extensions={"hmacGetSecret": hout})
@pytest.mark.parametrize("salts", [(salt1,), (salt1, salt2)])
def test_get_next_assertion_has_extension(
- device, hmac, salts
+ device, salts
):
""" Check that get_next_assertion properly returns extension information for multiple accounts. """
if (len(salts) == 2):
- hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
+ hout = {"salt1":salts[0],"salt2":salts[1]}
else:
- hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
+ hout = {"salt1":salts[0]}
accounts = 3
regs = []
auths = []
- rp = {"id": f"example_salts_{len(salts)}.org", "name": "ExampleRP_2"}
+ rp = {"id": f"example.com", "name": "ExampleRP_2"}
fixed_users = [generate_random_user() for _ in range(accounts)]
for i in range(accounts):
res = device.doMC(extensions={"hmacCreateSecret": True},
@@ -183,21 +180,19 @@ def test_get_next_assertion_has_extension(
assert "hmac-secret" in ext
assert isinstance(ext["hmac-secret"], bytes)
assert len(ext["hmac-secret"]) == len(salts) * 32 + 16
- key = hmac.process_get_output(x)
-
-def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac):
+def test_hmac_secret_different_with_uv(device, MCHmacSecret):
salts = [salt1]
if (len(salts) == 2):
- hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0],"salt2":salts[1]}})
+ hout = {"salt1":salts[0],"salt2":salts[1]}
else:
- hout = hmac.process_get_input({"hmacGetSecret":{"salt1":salts[0]}})
+ hout = {"salt1":salts[0]}
- auth_no_uv = device.GA(extensions={"hmac-secret": hout})['res']
- assert (auth_no_uv.auth_data.flags & (1 << 2)) == 0
+ auth_no_uv = device.doGA(extensions={"hmacGetSecret": hout})['res'].get_response(0)
+ assert (auth_no_uv.response.authenticator_data.flags & (1 << 2)) == 0
- ext_no_uv = auth_no_uv.auth_data.extensions
+ ext_no_uv = auth_no_uv.response.authenticator_data.extensions
assert ext_no_uv
assert "hmac-secret" in ext_no_uv
assert isinstance(ext_no_uv["hmac-secret"], bytes)
@@ -209,11 +204,11 @@ def test_hmac_secret_different_with_uv(device, MCHmacSecret, hmac):
hout['salt2'] = salts[1]
auth_uv = device.doGA(extensions={"hmacGetSecret": hout}, user_verification=UserVerificationRequirement.REQUIRED)['res'].get_response(0)
- assert auth_uv.authenticator_data.flags & (1 << 2)
- ext_uv = auth_uv.extension_results
+ assert auth_uv.response.authenticator_data.flags & (1 << 2)
+ ext_uv = auth_uv.client_extension_results
assert ext_uv
assert "hmacGetSecret" in ext_uv
- assert len(ext_uv["hmacGetSecret"]) == len(salts)
+ assert len([p for p in ext_uv["hmacGetSecret"] if len(ext_uv["hmacGetSecret"][p]) > 0]) == len(salts)
# Now see if the hmac-secrets are different
assert ext_no_uv["hmac-secret"][:32] != ext_uv["hmacGetSecret"]['output1']
diff --git a/tests/pico-fido/test_051_ctap1_interop.py b/tests/pico-fido/test_051_ctap1_interop.py
index da7e244..d8d3eab 100644
--- a/tests/pico-fido/test_051_ctap1_interop.py
+++ b/tests/pico-fido/test_051_ctap1_interop.py
@@ -29,7 +29,7 @@ def test_authenticate_ctap1_through_ctap2(device, RegRes):
res = device.doGA(ctap1=False, allow_list=[
{"id": RegRes['res'].attestation_object.auth_data.credential_data.credential_id, "type": "public-key"}
])
- assert res['res'].get_response(0).credential_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id
+ assert res['res'].get_response(0).raw_id == RegRes['res'].attestation_object.auth_data.credential_data.credential_id
# Test FIDO2 register works with U2F auth
diff --git a/tests/start-up-and-test.sh b/tests/start-up-and-test.sh
index 1620c97..613b594 100755
--- a/tests/start-up-and-test.sh
+++ b/tests/start-up-and-test.sh
@@ -3,6 +3,6 @@
/usr/sbin/pcscd &
sleep 2
rm -f memory.flash
-cp -R tests/docker/fido2/* /usr/local/lib/python3.9/dist-packages/fido2/hid
+cp -R tests/docker/fido2/* /usr/local/lib/python3.11/dist-packages/fido2/hid
./build_in_docker/pico_fido > /dev/null &
pytest tests
diff --git a/tools/pico-fido-tool.py b/tools/pico-fido-tool.py
index 963e70f..07c252f 100644
--- a/tools/pico-fido-tool.py
+++ b/tools/pico-fido-tool.py
@@ -24,7 +24,7 @@ import argparse
import platform
from binascii import hexlify
from threading import Event
-from typing import Mapping, Any, Optional, Callable
+from typing import List, Mapping, Any, Optional, Callable
import struct
import urllib.request
import json
@@ -73,21 +73,21 @@ def get_pki_data(url, data=None, method='GET'):
return j
class VendorConfig(Config):
-
class PARAM(IntEnum):
VENDOR_COMMAND_ID = 0x01
- VENDOR_AUT_CT = 0x02
- VENDOR_PARAM = 0x02
+ VENDOR_PARAM_BYTESTRING = 0x02
+ VENDOR_PARAM_INT = 0x03
+ VENDOR_PARAM_TEXTSTRING = 0x04
class CMD(IntEnum):
CONFIG_AUT_ENABLE = 0x03e43f56b34285e2
CONFIG_AUT_DISABLE = 0x1831a40f04a25ed9
- CONFIG_VENDOR_PROTOTYPE = 0x7f
- CONFIG_VENDOR_PHY = 0x1b
+ CONFIG_EA_UPLOAD = 0x66f2a674c29a8dcf
CONFIG_PHY_VIDPID = 0x6fcb19b0cbe3acfa
- CONFIG_PHY_OPTS = 0x969f3b09eceb805f
- CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948
CONFIG_PHY_LED_BTNESS = 0x76a85945985d02fd
+ CONFIG_PHY_LED_GPIO = 0x7b392a394de9f948
+ CONFIG_PHY_OPTS = 0x269f3b09eceb805f
+ CONFIG_PIN_POLICY = 0x6c07d70fe96c3897
class RESP(IntEnum):
KEY_AGREEMENT = 0x01
@@ -97,16 +97,16 @@ class VendorConfig(Config):
def enable_device_aut(self, ct):
self._call(
- VendorConfig.CMD.CONFIG_VENDOR_PROTOTYPE,
+ Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_ENABLE,
- VendorConfig.PARAM.VENDOR_AUT_CT: ct
+ VendorConfig.PARAM.VENDOR_PARAM_BYTESTRING: ct
},
)
def disable_device_aut(self):
self._call(
- VendorConfig.CMD.CONFIG_VENDOR_PROTOTYPE,
+ Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_DISABLE
},
@@ -114,40 +114,63 @@ class VendorConfig(Config):
def vidpid(self, vid, pid):
self._call(
- VendorConfig.CMD.CONFIG_VENDOR_PHY,
+ Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_VIDPID,
- VendorConfig.PARAM.VENDOR_PARAM: (vid & 0xFFFF) << 16 | pid
+ VendorConfig.PARAM.VENDOR_PARAM_INT: (vid & 0xFFFF) << 16 | pid
},
)
def led_gpio(self, gpio):
self._call(
- VendorConfig.CMD.CONFIG_VENDOR_PHY,
+ Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_GPIO,
- VendorConfig.PARAM.VENDOR_PARAM: gpio
+ VendorConfig.PARAM.VENDOR_PARAM_INT: gpio
},
)
def led_brightness(self, brightness):
self._call(
- VendorConfig.CMD.CONFIG_VENDOR_PHY,
+ Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_LED_BTNESS,
- VendorConfig.PARAM.VENDOR_PARAM: brightness
+ VendorConfig.PARAM.VENDOR_PARAM_INT: brightness
},
)
def phy_opts(self, opts):
self._call(
- VendorConfig.CMD.CONFIG_VENDOR_PHY,
+ Config.CMD.VENDOR_PROTOTYPE,
{
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PHY_OPTS,
- VendorConfig.PARAM.VENDOR_PARAM: opts
+ VendorConfig.PARAM.VENDOR_PARAM_INT: opts
},
)
+ def upload_ea(self, der):
+ self._call(
+ Config.CMD.VENDOR_PROTOTYPE,
+ {
+ VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_EA_UPLOAD,
+ VendorConfig.PARAM.VENDOR_PARAM_BYTESTRING: der
+ },
+ )
+
+ def pin_policy(self, url: bytes|str = None, policy: int = None):
+ if (url is not None or policy is not None):
+ params = { VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_PIN_POLICY }
+ if (url is not None):
+ if (isinstance(url, str)):
+ url = url.encode()
+ params[VendorConfig.PARAM.VENDOR_PARAM_BYTESTRING] = url
+ if (policy is not None):
+ params[VendorConfig.PARAM.VENDOR_PARAM_INT] = policy
+ self._call(
+ Config.CMD.VENDOR_PROTOTYPE,
+ params,
+ )
+
class Ctap2Vendor(Ctap2):
def __init__(self, device: CtapDevice, strict_cbor: bool = True):
super().__init__(device=device, strict_cbor=strict_cbor)
@@ -245,7 +268,6 @@ class Vendor:
DISABLE = 0x02
KEY_AGREEMENT = 0x01
EA_CSR = 0x01
- EA_UPLOAD = 0x02
class RESP(IntEnum):
PARAM = 0x01
@@ -433,13 +455,7 @@ class Vendor:
)[Vendor.RESP.PARAM]
def upload_ea(self, der):
- self._call(
- Vendor.CMD.VENDOR_EA,
- Vendor.SUBCMD.EA_UPLOAD,
- {
- Vendor.PARAM.PARAM: der
- }
- )
+ self.vcfg.upload_ea(der)
def vidpid(self, vid, pid):
return self.vcfg.vidpid(vid, pid)
@@ -483,6 +499,21 @@ class Vendor:
)
return { 'free': resp[1], 'used': resp[2], 'total': resp[3], 'files': resp[4], 'size': resp[5] }
+ def enable_enterprise_attestation(self):
+ self.vcfg.enable_enterprise_attestation()
+
+ def set_min_pin_length(self, length, rpids: list[str] = None, url=None):
+ params = {
+ Config.PARAM.NEW_MIN_PIN_LENGTH: length,
+ Config.PARAM.MIN_PIN_LENGTH_RPIDS: rpids if rpids else None,
+ }
+ self.vcfg.set_min_pin_length(
+ min_pin_length=length,
+ rp_ids=rpids if rpids else None,
+ )
+ self.vcfg.pin_policy(url=url.encode() if url else None)
+
+
def parse_args():
parser = argparse.ArgumentParser()
subparser = parser.add_subparsers(title="commands", dest="command")
@@ -495,7 +526,7 @@ def parse_args():
parser_backup.add_argument('filename', help='File to save or load the backup.')
parser_attestation = subparser.add_parser('attestation', help='Manages Enterprise Attestation')
- parser_attestation.add_argument('subcommand', choices=['csr'])
+ parser_attestation.add_argument('subcommand', choices=['csr','enable'])
parser_attestation.add_argument('--filename', help='Uploads the certificate filename to the device as enterprise attestation certificate. If not provided, it will generate an enterprise attestation certificate automatically.')
parser_phy = subparser.add_parser('phy', help='Set PHY options.')
@@ -513,10 +544,15 @@ def parse_args():
parser_mem = subparser.add_parser('memory', help='Get current memory usage.')
+ parser_pin_policy = subparser.add_parser('pin_policy', help='Manage PIN policy.')
+ parser_pin_policy.add_argument('length', type=int, help='Minimum PIN length (4-63).')
+ parser_pin_policy.add_argument('--rpids', help='Comma separated list of Relying Party IDs that have authorization to receive minimum PIN length.')
+ parser_pin_policy.add_argument('--url', help='URL where the user can consult PIN policy.')
+
args = parser.parse_args()
return args
-def secure(vdr, args):
+def secure(vdr: Vendor, args):
if (args.subcommand == 'enable'):
vdr.enable_device_aut()
elif (args.subcommand == 'unlock'):
@@ -524,13 +560,13 @@ def secure(vdr, args):
elif (args.subcommand == 'disable'):
vdr.disable_device_aut()
-def backup(vdr, args):
+def backup(vdr: Vendor, args):
if (args.subcommand == 'save'):
vdr.backup_save(args.filename)
elif (args.subcommand == 'load'):
vdr.backup_load(args.filename)
-def attestation(vdr, args):
+def attestation(vdr: Vendor, args):
if (args.subcommand == 'csr'):
if (args.filename is None):
csr = x509.load_der_x509_csr(vdr.csr())
@@ -545,8 +581,10 @@ def attestation(vdr, args):
except ValueError:
cert = x509.load_pem_x509_certificate(dataf)
vdr.upload_ea(cert.public_bytes(Encoding.DER))
+ elif (args.subcommand == 'enable'):
+ vdr.enable_enterprise_attestation()
-def phy(vdr, args):
+def phy(vdr: Vendor, args):
val = args.value if 'value' in args else None
if (val):
if (args.subcommand == 'vidpid'):
@@ -570,7 +608,7 @@ def phy(vdr, args):
else:
print('Command executed successfully. Please, restart your Pico Key.')
-def memory(vdr, args):
+def memory(vdr: Vendor, args):
mem = vdr.memory()
print(f'Memory usage:')
print(f'\tFree: {mem["free"]/1024:.2f} kilobytes ({mem["free"]*100/mem["total"]:.2f}%)')
@@ -579,6 +617,14 @@ def memory(vdr, args):
print(f'\tFlash size: {mem["size"]/1024:.2f} kilobytes')
print(f'\tFiles: {mem["files"]}')
+
+def pin_policy(vdr: Vendor, args):
+ rpids = None
+ if (args.rpids):
+ rpids = args.rpids.split(',')
+ vdr.set_min_pin_length(args.length, rpids, args.url)
+
+
def main(args):
print('Pico Fido Tool v1.10')
print('Author: Pol Henarejos')
@@ -602,6 +648,9 @@ def main(args):
phy(vdr, args)
elif (args.command == 'memory'):
memory(vdr, args)
+ elif (args.command == 'pin_policy'):
+ pin_policy(vdr, args)
+
def run():
args = parse_args()