Merge branch 'development'
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#ifndef ESP_PLATFORM
|
||||
#include "common.h"
|
||||
#else
|
||||
@@ -27,7 +28,7 @@
|
||||
#include "cbor.h"
|
||||
#include "ctap.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include "hid/ctap_hid.h"
|
||||
@@ -35,7 +36,6 @@
|
||||
#include "files.h"
|
||||
#include "random.h"
|
||||
#include "crypto_utils.h"
|
||||
#include "pico_keys.h"
|
||||
#include "apdu.h"
|
||||
#include "kek.h"
|
||||
|
||||
@@ -44,6 +44,7 @@ uint32_t max_usage_time_period = 600 * 1000;
|
||||
bool needs_power_cycle = false;
|
||||
static mbedtls_ecdh_context hkey;
|
||||
static bool hkey_init = false;
|
||||
extern int encrypt_keydev_f1(const uint8_t keydev[32]);
|
||||
|
||||
int beginUsingPinUvAuthToken(bool userIsPresent) {
|
||||
paut.user_present = userIsPresent;
|
||||
@@ -105,11 +106,7 @@ int regenerate() {
|
||||
mbedtls_ecdh_init(&hkey);
|
||||
hkey_init = true;
|
||||
mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1);
|
||||
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp,
|
||||
&hkey.ctx.mbed_ecdh.d,
|
||||
&hkey.ctx.mbed_ecdh.Q,
|
||||
random_gen,
|
||||
NULL);
|
||||
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_gen, NULL);
|
||||
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
@@ -125,34 +122,15 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
|
||||
return ret;
|
||||
}
|
||||
if (protocol == 1) {
|
||||
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256),
|
||||
buf,
|
||||
sizeof(buf),
|
||||
sharedSecret);
|
||||
return mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), buf, sizeof(buf), sharedSecret);
|
||||
}
|
||||
else if (protocol == 2) {
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
ret = mbedtls_hkdf(md_info,
|
||||
NULL,
|
||||
0,
|
||||
buf,
|
||||
sizeof(buf),
|
||||
(uint8_t *) "CTAP2 HMAC key",
|
||||
14,
|
||||
sharedSecret,
|
||||
32);
|
||||
ret = mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 HMAC key", 14, sharedSecret, 32);
|
||||
if (ret != 0) {
|
||||
return ret;
|
||||
}
|
||||
return mbedtls_hkdf(md_info,
|
||||
NULL,
|
||||
0,
|
||||
buf,
|
||||
sizeof(buf),
|
||||
(uint8_t *) "CTAP2 AES key",
|
||||
13,
|
||||
sharedSecret + 32,
|
||||
32);
|
||||
return mbedtls_hkdf(md_info, NULL, 0, buf, sizeof(buf), (uint8_t *) "CTAP2 AES key", 13, sharedSecret + 32, 32);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -160,26 +138,38 @@ int kdf(uint8_t protocol, const mbedtls_mpi *z, uint8_t *sharedSecret) {
|
||||
int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret) {
|
||||
mbedtls_mpi z;
|
||||
mbedtls_mpi_init(&z);
|
||||
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp,
|
||||
&z,
|
||||
Q,
|
||||
&hkey.ctx.mbed_ecdh.d,
|
||||
random_gen,
|
||||
NULL);
|
||||
int ret = mbedtls_ecdh_compute_shared(&hkey.ctx.mbed_ecdh.grp, &z, Q, &hkey.ctx.mbed_ecdh.d, random_gen, NULL);
|
||||
ret = kdf(protocol, &z, sharedSecret);
|
||||
mbedtls_mpi_free(&z);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int resetPinUvAuthToken() {
|
||||
void resetAuthToken(bool persistent) {
|
||||
uint16_t fid = EF_AUTHTOKEN;
|
||||
if (persistent) {
|
||||
fid = EF_PAUTHTOKEN;
|
||||
}
|
||||
file_t *ef = search_by_fid(fid, NULL, SPECIFY_EF);
|
||||
uint8_t t[32];
|
||||
random_gen(NULL, t, sizeof(t));
|
||||
file_put_data(ef_authtoken, t, sizeof(t));
|
||||
file_put_data(ef, t, sizeof(t));
|
||||
low_flash_available();
|
||||
}
|
||||
|
||||
int resetPinUvAuthToken() {
|
||||
resetAuthToken(false);
|
||||
paut.permissions = 0;
|
||||
paut.data = file_get_data(ef_authtoken);
|
||||
paut.len = file_get_size(ef_authtoken);
|
||||
return 0;
|
||||
}
|
||||
|
||||
low_flash_available();
|
||||
int resetPersistentPinUvAuthToken() {
|
||||
resetAuthToken(true);
|
||||
file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF);
|
||||
ppaut.permissions = 0;
|
||||
ppaut.data = file_get_data(ef_pauthtoken);
|
||||
ppaut.len = file_get_size(ef_pauthtoken);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -210,11 +200,7 @@ int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in
|
||||
return -1;
|
||||
}
|
||||
|
||||
int authenticate(uint8_t protocol,
|
||||
const uint8_t *key,
|
||||
const uint8_t *data,
|
||||
size_t len,
|
||||
uint8_t *sign) {
|
||||
int authenticate(uint8_t protocol, const uint8_t *key, const uint8_t *data, size_t len, uint8_t *sign) {
|
||||
uint8_t hmac[32];
|
||||
int ret =
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), key, 32, data, len, hmac);
|
||||
@@ -242,10 +228,10 @@ int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t l
|
||||
return ret;
|
||||
}
|
||||
if (protocol == 1) {
|
||||
return memcmp(sign, hmac, 16);
|
||||
return ct_memcmp(sign, hmac, 16);
|
||||
}
|
||||
else if (protocol == 2) {
|
||||
return memcmp(sign, hmac, 32);
|
||||
return ct_memcmp(sign, hmac, 32);
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
@@ -280,17 +266,15 @@ int pinUvAuthTokenUsageTimerObserver() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int check_mkek_encrypted(const uint8_t *dhash) {
|
||||
if (file_get_size(ef_mkek) == MKEK_IV_SIZE + MKEK_KEY_SIZE) {
|
||||
hash_multi(dhash, 16, session_pin); // Only for storing MKEK
|
||||
uint8_t mkek[MKEK_SIZE] = {0};
|
||||
memcpy(mkek, file_get_data(ef_mkek), MKEK_IV_SIZE + MKEK_KEY_SIZE);
|
||||
int ret = store_mkek(mkek);
|
||||
mbedtls_platform_zeroize(mkek, sizeof(mkek));
|
||||
mbedtls_platform_zeroize(session_pin, sizeof(session_pin));
|
||||
if (ret != PICOKEY_OK) {
|
||||
return CTAP2_ERR_PIN_AUTH_INVALID;
|
||||
}
|
||||
int check_keydev_encrypted(const uint8_t pin_token[32]) {
|
||||
if (file_get_data(ef_keydev) && *file_get_data(ef_keydev) == 0x01) {
|
||||
uint8_t tmp_keydev[61];
|
||||
tmp_keydev[0] = 0x02; // Change format to encrypted
|
||||
encrypt_with_aad(pin_token, file_get_data(ef_keydev) + 1, 32, tmp_keydev + 1);
|
||||
DEBUG_DATA(tmp_keydev, sizeof(tmp_keydev));
|
||||
file_put_data(ef_keydev, tmp_keydev, sizeof(tmp_keydev));
|
||||
mbedtls_platform_zeroize(tmp_keydev, sizeof(tmp_keydev));
|
||||
low_flash_available();
|
||||
}
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
@@ -305,11 +289,11 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
CborEncoder encoder, mapEncoder;
|
||||
CborValue map;
|
||||
CborError error = CborNoError;
|
||||
CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 },
|
||||
kay = { 0 };
|
||||
CborByteString pinUvAuthParam = { 0 }, newPinEnc = { 0 }, pinHashEnc = { 0 }, kax = { 0 }, kay = { 0 };
|
||||
CborCharString rpId = { 0 };
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
uint8_t keydev[32] = {0};
|
||||
if (hkey_init == false) {
|
||||
initialize();
|
||||
}
|
||||
@@ -431,15 +415,17 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
if (pin_len < minPin) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
}
|
||||
uint8_t hsh[34], dhash[32];
|
||||
uint8_t hsh[35], dhash[32];
|
||||
hsh[0] = MAX_PIN_RETRIES;
|
||||
hsh[1] = pin_len;
|
||||
hsh[2] = 1; // New format indicator
|
||||
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
|
||||
double_hash_pin(dhash, 16, hsh + 2);
|
||||
file_put_data(ef_pin, hsh, 2 + 32);
|
||||
pin_derive_verifier(dhash, 16, hsh + 3);
|
||||
file_put_data(ef_pin, hsh, sizeof(hsh));
|
||||
low_flash_available();
|
||||
|
||||
ret = check_mkek_encrypted(dhash);
|
||||
pin_derive_session(dhash, 16, session_pin);
|
||||
ret = check_keydev_encrypted(session_pin);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
@@ -486,10 +472,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
uint8_t pin_data[34];
|
||||
memcpy(pin_data, file_get_data(ef_pin), 34);
|
||||
uint8_t pin_data[35];
|
||||
memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
|
||||
pin_data[0] -= 1;
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
file_put_data(ef_pin, pin_data, file_get_size(ef_pin));
|
||||
low_flash_available();
|
||||
uint8_t retries = pin_data[0];
|
||||
uint8_t paddedNewPin[64];
|
||||
@@ -498,9 +484,16 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
uint8_t dhash[32];
|
||||
double_hash_pin(paddedNewPin, 16, dhash);
|
||||
if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) {
|
||||
uint8_t dhash[32], off = 3;
|
||||
if (file_get_size(ef_pin) == 34) {
|
||||
off = 2;
|
||||
double_hash_pin(paddedNewPin, 16, dhash);
|
||||
}
|
||||
else {
|
||||
pin_derive_verifier(paddedNewPin, 16, dhash);
|
||||
}
|
||||
|
||||
if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) {
|
||||
regenerate();
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
if (retries == 0) {
|
||||
@@ -514,10 +507,28 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
}
|
||||
hash_multi(paddedNewPin, 16, session_pin);
|
||||
if (off == 2) {
|
||||
// Upgrade pin file to new format
|
||||
pin_data[2] = 1; // New format indicator
|
||||
pin_derive_verifier(paddedNewPin, 16, pin_data + 3);
|
||||
|
||||
hash_multi(paddedNewPin, 16, session_pin);
|
||||
ret = load_keydev(keydev);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
encrypt_keydev_f1(keydev);
|
||||
}
|
||||
pin_derive_session(paddedNewPin, 16, session_pin);
|
||||
pin_data[0] = MAX_PIN_RETRIES;
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
low_flash_available();
|
||||
|
||||
ret = check_keydev_encrypted(session_pin);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
|
||||
new_pin_mismatches = 0;
|
||||
ret = decrypt((uint8_t)pinUvAuthProtocol, sharedSecret, newPinEnc.data, (uint16_t)newPinEnc.len, paddedNewPin);
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
@@ -539,35 +550,33 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
if (pin_len < minPin) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
}
|
||||
uint8_t hsh[34];
|
||||
hsh[0] = MAX_PIN_RETRIES;
|
||||
hsh[1] = pin_len;
|
||||
|
||||
// New PIN is valid and verified
|
||||
ret = load_keydev(keydev);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
encrypt_keydev_f1(keydev);
|
||||
|
||||
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, dhash);
|
||||
double_hash_pin(dhash, 16, hsh + 2);
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 &&
|
||||
memcmp(hsh + 2, file_get_data(ef_pin) + 2, 32) == 0) {
|
||||
pin_derive_session(dhash, 16, session_pin);
|
||||
ret = check_keydev_encrypted(session_pin);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
low_flash_available();
|
||||
|
||||
pin_data[0] = MAX_PIN_RETRIES;
|
||||
pin_data[1] = pin_len;
|
||||
pin_data[2] = 1; // New format indicator
|
||||
pin_derive_verifier(dhash, 16, pin_data + 3);
|
||||
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 && ct_memcmp(pin_data + 3, file_get_data(ef_pin) + 3, 32) == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
}
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
|
||||
uint8_t mkek[MKEK_SIZE] = {0};
|
||||
ret = load_mkek(mkek);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
file_put_data(ef_pin, hsh, 2 + 32);
|
||||
|
||||
ret = check_mkek_encrypted(dhash);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
|
||||
hash_multi(dhash, 16, session_pin);
|
||||
ret = store_mkek(mkek);
|
||||
mbedtls_platform_zeroize(mkek, sizeof(mkek));
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
mbedtls_platform_zeroize(hsh, sizeof(hsh));
|
||||
mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
|
||||
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
|
||||
uint8_t *tmpf = (uint8_t *) calloc(1, file_get_size(ef_minpin));
|
||||
@@ -578,6 +587,7 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
}
|
||||
low_flash_available();
|
||||
resetPinUvAuthToken();
|
||||
resetPersistentPinUvAuthToken();
|
||||
goto err; // No return
|
||||
}
|
||||
else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions
|
||||
@@ -598,7 +608,9 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
if ((permissions & CTAP_PERMISSION_BE)) { // Not supported yet
|
||||
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
|
||||
}
|
||||
|
||||
if ((permissions & CTAP_PERMISSION_PCMR) && permissions != CTAP_PERMISSION_PCMR) {
|
||||
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
|
||||
}
|
||||
}
|
||||
if (!file_has_data(ef_pin)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
|
||||
@@ -618,10 +630,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t pin_data[34];
|
||||
memcpy(pin_data, file_get_data(ef_pin), 34);
|
||||
uint8_t pin_data[35];
|
||||
memcpy(pin_data, file_get_data(ef_pin), file_get_size(ef_pin));
|
||||
pin_data[0] -= 1;
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
file_put_data(ef_pin, pin_data, file_get_size(ef_pin));
|
||||
low_flash_available();
|
||||
uint8_t retries = pin_data[0];
|
||||
uint8_t paddedNewPin[64], poff = ((uint8_t)pinUvAuthProtocol - 1) * IV_SIZE;
|
||||
@@ -630,11 +642,18 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
uint8_t dhash[32];
|
||||
double_hash_pin(paddedNewPin, 16, dhash);
|
||||
if (memcmp(dhash, file_get_data(ef_pin) + 2, 32) != 0) {
|
||||
uint8_t dhash[32], off = 3;
|
||||
if (file_get_size(ef_pin) == 34) {
|
||||
off = 2;
|
||||
double_hash_pin(paddedNewPin, 16, dhash);
|
||||
}
|
||||
else {
|
||||
pin_derive_verifier(paddedNewPin, 16, dhash);
|
||||
}
|
||||
if (ct_memcmp(dhash, file_get_data(ef_pin) + off, 32) != 0) {
|
||||
regenerate();
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||
if (retries == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_BLOCKED);
|
||||
}
|
||||
@@ -646,39 +665,59 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
}
|
||||
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||
|
||||
ret = check_mkek_encrypted(paddedNewPin);
|
||||
if (off == 2) {
|
||||
// Upgrade pin file to new format
|
||||
pin_data[2] = 1; // New format indicator
|
||||
pin_derive_verifier(paddedNewPin, 16, pin_data + 3);
|
||||
hash_multi(paddedNewPin, 16, session_pin);
|
||||
ret = load_keydev(keydev);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
encrypt_keydev_f1(keydev);
|
||||
}
|
||||
|
||||
pin_derive_session(paddedNewPin, 16, session_pin);
|
||||
ret = check_keydev_encrypted(session_pin);
|
||||
if (ret != PICOKEY_OK) {
|
||||
CBOR_ERROR(ret);
|
||||
}
|
||||
|
||||
hash_multi(paddedNewPin, 16, session_pin);
|
||||
pin_data[0] = MAX_PIN_RETRIES;
|
||||
new_pin_mismatches = 0;
|
||||
|
||||
file_put_data(ef_pin, pin_data, sizeof(pin_data));
|
||||
mbedtls_platform_zeroize(pin_data, sizeof(pin_data));
|
||||
mbedtls_platform_zeroize(dhash, sizeof(dhash));
|
||||
|
||||
low_flash_available();
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
resetPinUvAuthToken();
|
||||
beginUsingPinUvAuthToken(false);
|
||||
if (subcommand == 0x05) {
|
||||
permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA;
|
||||
}
|
||||
paut.permissions = (uint8_t)permissions;
|
||||
if (rpId.present == true) {
|
||||
mbedtls_sha256((uint8_t *) rpId.data, rpId.len, paut.rp_id_hash, 0);
|
||||
paut.has_rp_id = true;
|
||||
uint8_t pinUvAuthToken_enc[32 + IV_SIZE], *pdata = NULL;
|
||||
if (permissions & CTAP_PERMISSION_PCMR) {
|
||||
ppaut.permissions = CTAP_PERMISSION_PCMR;
|
||||
pdata = ppaut.data;
|
||||
}
|
||||
else {
|
||||
paut.has_rp_id = false;
|
||||
resetPinUvAuthToken();
|
||||
beginUsingPinUvAuthToken(false);
|
||||
if (subcommand == 0x05) {
|
||||
permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA;
|
||||
}
|
||||
paut.permissions = (uint8_t)permissions;
|
||||
if (rpId.present == true) {
|
||||
mbedtls_sha256((uint8_t *) rpId.data, rpId.len, paut.rp_id_hash, 0);
|
||||
paut.has_rp_id = true;
|
||||
}
|
||||
else {
|
||||
paut.has_rp_id = false;
|
||||
}
|
||||
pdata = paut.data;
|
||||
}
|
||||
uint8_t pinUvAuthToken_enc[32 + IV_SIZE];
|
||||
encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc);
|
||||
encrypt((uint8_t)pinUvAuthProtocol, sharedSecret, pdata, 32, pinUvAuthToken_enc);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, pinUvAuthToken_enc, 32 + poff));
|
||||
@@ -695,6 +734,7 @@ err:
|
||||
CBOR_FREE_BYTE_STRING(kax);
|
||||
CBOR_FREE_BYTE_STRING(kay);
|
||||
CBOR_FREE_BYTE_STRING(rpId);
|
||||
mbedtls_platform_zeroize(keydev, sizeof(keydev));
|
||||
if (error != CborNoError) {
|
||||
if (error == CborErrorImproperValue) {
|
||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
@@ -22,7 +23,6 @@
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "credential.h"
|
||||
#include "pico_keys.h"
|
||||
#include "random.h"
|
||||
#include "mbedtls/ecdh.h"
|
||||
#include "mbedtls/chachapoly.h"
|
||||
@@ -31,14 +31,16 @@
|
||||
|
||||
extern uint8_t keydev_dec[32];
|
||||
extern bool has_keydev_dec;
|
||||
extern void resetPersistentPinUvAuthToken();
|
||||
extern void resetPinUvAuthToken();
|
||||
|
||||
int cbor_config(const uint8_t *data, size_t len) {
|
||||
CborParser parser;
|
||||
CborValue map;
|
||||
CborError error = CborNoError;
|
||||
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParam = 0;
|
||||
CborByteString pinUvAuthParam = { 0 }, vendorAutCt = { 0 };
|
||||
CborCharString minPinLengthRPIDs[32] = { 0 };
|
||||
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0, vendorParamInt = 0;
|
||||
CborByteString pinUvAuthParam = { 0 }, vendorParamByteString = { 0 };
|
||||
CborCharString minPinLengthRPIDs[32] = { 0 }, vendorParamTextString = { 0 };
|
||||
size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0;
|
||||
CborEncoder encoder;
|
||||
//CborEncoder mapEncoder;
|
||||
@@ -66,13 +68,19 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
raw_subpara = (uint8_t *) cbor_value_get_next_byte(&_f1);
|
||||
CBOR_PARSE_MAP_START(_f1, 2)
|
||||
{
|
||||
if (subcommand == 0x7f) { // Config Aut
|
||||
if (subcommand == 0xFF) { // Vendor
|
||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||
if (subpara == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(vendorCommandId, 2);
|
||||
}
|
||||
else if (subpara == 0x02) {
|
||||
CBOR_FIELD_GET_BYTES(vendorAutCt, 2);
|
||||
CBOR_FIELD_GET_BYTES(vendorParamByteString, 2);
|
||||
}
|
||||
else if (subpara == 0x03) {
|
||||
CBOR_FIELD_GET_UINT(vendorParamInt, 2);
|
||||
}
|
||||
else if (subpara == 0x04) {
|
||||
CBOR_FIELD_GET_TEXT(vendorParamTextString, 2);
|
||||
}
|
||||
}
|
||||
else if (subcommand == 0x03) { // Extensions
|
||||
@@ -95,15 +103,6 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
CBOR_FIELD_GET_BOOL(forceChangePin, 2);
|
||||
}
|
||||
}
|
||||
else if (subcommand == 0x1B) { // PHY
|
||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||
if (subpara == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(vendorCommandId, 2);
|
||||
}
|
||||
else if (subpara == 0x02) {
|
||||
CBOR_FIELD_GET_UINT(vendorParam, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
raw_subpara_len = cbor_value_get_next_byte(&_f1) - raw_subpara;
|
||||
@@ -122,9 +121,12 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
if (pinUvAuthParam.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
|
||||
}
|
||||
if (pinUvAuthProtocol == 0) {
|
||||
if (pinUvAuthProtocol == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
uint8_t *verify_payload = (uint8_t *) calloc(1, 32 + 1 + 1 + raw_subpara_len);
|
||||
memset(verify_payload, 0xff, 32);
|
||||
@@ -141,8 +143,14 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
|
||||
if (subcommand == 0x7f) {
|
||||
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) {
|
||||
if (subcommand == 0xFF) {
|
||||
#ifndef ENABLE_EMULATION
|
||||
const bool is_phy = (vendorCommandId == CTAP_CONFIG_PHY_VIDPID ||
|
||||
vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO ||
|
||||
vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS ||
|
||||
vendorCommandId == CTAP_CONFIG_PHY_OPTS);
|
||||
#endif
|
||||
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE){
|
||||
if (!file_has_data(ef_keydev_enc)) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
@@ -163,7 +171,7 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
mbedtls_chachapoly_context chatx;
|
||||
int ret = mse_decrypt_ct(vendorAutCt.data, vendorAutCt.len);
|
||||
int ret = mse_decrypt_ct(vendorParamByteString.data, vendorParamByteString.len);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
@@ -171,7 +179,7 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
uint8_t key_dev_enc[12 + 32 + 16];
|
||||
random_gen(NULL, key_dev_enc, 12);
|
||||
mbedtls_chachapoly_init(&chatx);
|
||||
mbedtls_chachapoly_setkey(&chatx, vendorAutCt.data);
|
||||
mbedtls_chachapoly_setkey(&chatx, vendorParamByteString.data);
|
||||
ret = mbedtls_chachapoly_encrypt_and_tag(&chatx, file_get_size(ef_keydev), key_dev_enc, NULL, 0, file_get_data(ef_keydev), key_dev_enc + 12, key_dev_enc + 12 + file_get_size(ef_keydev));
|
||||
mbedtls_chachapoly_free(&chatx);
|
||||
if (ret != 0) {
|
||||
@@ -184,6 +192,56 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
file_put_data(ef_keydev, NULL, 0); // Set ef to 0 bytes
|
||||
low_flash_available();
|
||||
}
|
||||
|
||||
#ifndef ENABLE_EMULATION
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) {
|
||||
phy_data.vid = (vendorParamInt >> 16) & 0xFFFF;
|
||||
phy_data.pid = vendorParamInt & 0xFFFF;
|
||||
phy_data.vidpid_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) {
|
||||
phy_data.led_gpio = (uint8_t)vendorParamInt;
|
||||
phy_data.led_gpio_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) {
|
||||
phy_data.led_brightness = (uint8_t)vendorParamInt;
|
||||
phy_data.led_brightness_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) {
|
||||
phy_data.opts = (uint16_t)vendorParamInt;
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||
}
|
||||
if (is_phy && phy_save() != PICOKEY_OK) {
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
#endif
|
||||
else if (vendorCommandId == CTAP_CONFIG_EA_UPLOAD) {
|
||||
if (vendorParamByteString.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
|
||||
if (ef_ee_ea) {
|
||||
file_put_data(ef_ee_ea, vendorParamByteString.data, (uint16_t)vendorParamByteString.len);
|
||||
}
|
||||
low_flash_available();
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PIN_POLICY) {
|
||||
file_t *ef_pin_policy = file_new(EF_PIN_COMPLEXITY_POLICY);
|
||||
if (ef_pin_policy) {
|
||||
uint8_t *val = calloc(1, 2 + vendorParamByteString.len);
|
||||
if (val) {
|
||||
// Not ready yet for integer param
|
||||
// val[0] = (uint8_t)(vendorParamInt >> 8);
|
||||
// val[1] = (uint8_t)(vendorParamInt & 0xFF);
|
||||
memcpy(val + 2, vendorParamByteString.data, vendorParamByteString.len);
|
||||
file_put_data(ef_pin_policy, val, 2 + (uint16_t)vendorParamByteString.len);
|
||||
free(val);
|
||||
}
|
||||
}
|
||||
low_flash_available();
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
|
||||
}
|
||||
@@ -207,6 +265,10 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength) {
|
||||
forceChangePin = ptrue;
|
||||
}
|
||||
if (forceChangePin) {
|
||||
resetPersistentPinUvAuthToken();
|
||||
resetPinUvAuthToken();
|
||||
}
|
||||
uint8_t *dataf = (uint8_t *) calloc(1, 2 + minPinLengthRPIDs_len * 32);
|
||||
dataf[0] = (uint8_t)newMinPinLength;
|
||||
dataf[1] = forceChangePin == ptrue ? 1 : 0;
|
||||
@@ -222,35 +284,6 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
set_opts(get_opts() | FIDO2_OPT_EA);
|
||||
goto err;
|
||||
}
|
||||
#ifndef ENABLE_EMULATION
|
||||
else if (subcommand == 0x1B) {
|
||||
if (vendorParam == 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
if (vendorCommandId == CTAP_CONFIG_PHY_VIDPID) {
|
||||
phy_data.vid = (vendorParam >> 16) & 0xFFFF;
|
||||
phy_data.pid = vendorParam & 0xFFFF;
|
||||
phy_data.vidpid_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_GPIO) {
|
||||
phy_data.led_gpio = (uint8_t)vendorParam;
|
||||
phy_data.led_gpio_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_LED_BTNESS) {
|
||||
phy_data.led_brightness = (uint8_t)vendorParam;
|
||||
phy_data.led_brightness_present = true;
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_PHY_OPTS) {
|
||||
phy_data.opts = (uint16_t)vendorParam;
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||
}
|
||||
if (phy_save() != PICOKEY_OK) {
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||
}
|
||||
@@ -259,7 +292,8 @@ int cbor_config(const uint8_t *data, size_t len) {
|
||||
|
||||
err:
|
||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||
CBOR_FREE_BYTE_STRING(vendorAutCt);
|
||||
CBOR_FREE_BYTE_STRING(vendorParamByteString);
|
||||
CBOR_FREE_BYTE_STRING(vendorParamTextString);
|
||||
for (size_t i = 0; i < minPinLengthRPIDs_len; i++) {
|
||||
CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]);
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
@@ -22,7 +23,6 @@
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "credential.h"
|
||||
#include "pico_keys.h"
|
||||
|
||||
uint8_t rp_counter = 1;
|
||||
uint8_t rp_total = 0;
|
||||
@@ -79,8 +79,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
if (strcmp(_fd3, "transports") == 0) {
|
||||
CBOR_PARSE_ARRAY_START(_f3, 4)
|
||||
{
|
||||
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.
|
||||
transports_len], 4);
|
||||
CBOR_FIELD_GET_TEXT(credentialId.transports[credentialId.transports_len], 4);
|
||||
credentialId.transports_len++;
|
||||
}
|
||||
CBOR_PARSE_ARRAY_END(_f3, 4);
|
||||
@@ -122,12 +121,19 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
||||
if (subcommand == 0x01) {
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) == CborNoError) {
|
||||
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
if (is_preview == false &&
|
||||
(!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
else {
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x01", 1, pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (is_preview == false &&
|
||||
(!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
uint8_t existing = 0;
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
@@ -144,11 +150,18 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
else if (subcommand == 0x02 || subcommand == 0x03) {
|
||||
file_t *rp_ef = NULL;
|
||||
if (subcommand == 0x02) {
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) == CborNoError) {
|
||||
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
else {
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, (const uint8_t *) "\x02", 1, pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (is_preview == false && (!(paut.permissions & CTAP_PERMISSION_CM) || paut.has_rp_id == true)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
rp_counter = 1;
|
||||
rp_total = 0;
|
||||
@@ -199,13 +212,20 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
}
|
||||
if (subcommand == 0x04) {
|
||||
*(raw_subpara - 1) = 0x04;
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (verify((uint8_t)pinUvAuthProtocol, ppaut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) == CborNoError) {
|
||||
if (!(ppaut.permissions & CTAP_PERMISSION_PCMR)) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
if (is_preview == false &&
|
||||
(!(paut.permissions & CTAP_PERMISSION_CM) ||
|
||||
(paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
else {
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, raw_subpara - 1, (uint16_t)(raw_subpara_len + 1), pinUvAuthParam.data) != CborNoError) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
if (is_preview == false &&
|
||||
(!(paut.permissions & CTAP_PERMISSION_CM) ||
|
||||
(paut.has_rp_id == true && memcmp(paut.rp_id_hash, rpIdHash.data, 32) != 0))) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
}
|
||||
cred_counter = 1;
|
||||
cred_total = 0;
|
||||
@@ -239,7 +259,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
Credential cred = { 0 };
|
||||
if (credential_load(file_get_data(cred_ef) + 32, file_get_size(cred_ef) - 32, rpIdHash.data, &cred) != 0) {
|
||||
if (credential_load_resident(cred_ef, rpIdHash.data, &cred) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@@ -296,7 +316,9 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred.id.data, cred.id.len));
|
||||
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
|
||||
credential_derive_resident(cred.id.data, cred.id.len, cred_idr);
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred_idr, sizeof(cred_idr)));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
@@ -352,7 +374,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
}
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) {
|
||||
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, CRED_RESIDENT_LEN) == 0) {
|
||||
uint8_t *rp_id_hash = file_get_data(ef);
|
||||
if (delete_file(ef) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
@@ -394,10 +416,10 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
}
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, MIN(file_get_size(ef) - 32, credentialId.id.len)) == 0) {
|
||||
if (file_has_data(ef) && memcmp(file_get_data(ef) + 32, credentialId.id.data, CRED_RESIDENT_LEN) == 0) {
|
||||
Credential cred = { 0 };
|
||||
uint8_t *rp_id_hash = file_get_data(ef);
|
||||
if (credential_load(rp_id_hash + 32, file_get_size(ef) - 32, rp_id_hash, &cred) != 0) {
|
||||
if (credential_load_resident(ef, rp_id_hash, &cred) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
if (memcmp(user.id.data, cred.userId.data, MIN(user.id.len, cred.userId.len)) != 0) {
|
||||
@@ -405,11 +427,11 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t newcred[MAX_CRED_ID_LENGTH];
|
||||
size_t newcred_len = 0;
|
||||
uint16_t newcred_len = 0;
|
||||
if (credential_create(&cred.rpId, &cred.userId, &user.parent.name,
|
||||
&user.displayName, &cred.opts, &cred.extensions,
|
||||
cred.use_sign_count, (int)cred.alg,
|
||||
(int)cred.curve, newcred, &newcred_len) != 0) {
|
||||
&user.displayName, &cred.opts, &cred.extensions,
|
||||
cred.use_sign_count, (int)cred.alg,
|
||||
(int)cred.curve, newcred, &newcred_len) != 0) {
|
||||
credential_free(&cred);
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
}
|
||||
|
||||
@@ -15,16 +15,16 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "cbor.h"
|
||||
#include "ctap.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "fido.h"
|
||||
#include "files.h"
|
||||
#include "crypto_utils.h"
|
||||
#include "pico_keys.h"
|
||||
#include "apdu.h"
|
||||
#include "cbor_make_credential.h"
|
||||
#include "credential.h"
|
||||
@@ -295,27 +295,48 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
if (strcmp(allowList[e].type.data, "public-key") != 0) {
|
||||
continue;
|
||||
}
|
||||
if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) {
|
||||
credential_free(&creds[creds_len]);
|
||||
}
|
||||
else {
|
||||
creds_len++;
|
||||
silent = false; // If we are able to load a credential, we are not silent
|
||||
// Even we provide allowList, we need to check if the credential is resident
|
||||
if (!resident) {
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) {
|
||||
if (credential_load_resident(ef, rp_id_hash, &creds[creds_len]) != 0) {
|
||||
// Should never happen
|
||||
credential_free(&creds[creds_len]);
|
||||
continue;
|
||||
}
|
||||
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, allowList[e].id.len) == 0) {
|
||||
resident = true;
|
||||
resident = true;
|
||||
creds_len++;
|
||||
silent = false; // If we are able to load a credential, we are not silent
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) {
|
||||
credential_free(&creds[creds_len]);
|
||||
}
|
||||
else {
|
||||
creds_len++;
|
||||
silent = false; // If we are able to load a credential, we are not silent
|
||||
// Even we provide allowList, we need to check if the credential is resident
|
||||
if (!resident) {
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, allowList[e].id.len) == 0) {
|
||||
resident = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (resident) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (resident) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -326,7 +347,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
}
|
||||
int ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &creds[creds_len]);
|
||||
int ret = credential_load_resident(ef, rp_id_hash, &creds[creds_len]);
|
||||
if (ret != 0) {
|
||||
credential_free(&creds[creds_len]);
|
||||
}
|
||||
@@ -343,8 +364,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
if (creds[i].extensions.credProtect == CRED_PROT_UV_REQUIRED && !(flags & FIDO2_AUT_FLAG_UV)) {
|
||||
credential_free(&creds[i]);
|
||||
}
|
||||
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST &&
|
||||
resident == true && !(flags & FIDO2_AUT_FLAG_UV)) {
|
||||
else if (creds[i].extensions.credProtect == CRED_PROT_UV_OPTIONAL_WITH_LIST && allowList_len == 0 && !(flags & FIDO2_AUT_FLAG_UV)) {
|
||||
credential_free(&creds[i]);
|
||||
}
|
||||
else {
|
||||
@@ -375,8 +395,24 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
if (strcmp(allowList[e].type.data, "public-key") != 0) {
|
||||
continue;
|
||||
}
|
||||
if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) {
|
||||
numberOfCredentials++;
|
||||
if (credential_is_resident(allowList[e].id.data, allowList[e].id.len)) {
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS && creds_len < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (!file_has_data(ef) || memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
}
|
||||
if (memcmp(file_get_data(ef) + 32, allowList[e].id.data, CRED_RESIDENT_LEN) == 0) {
|
||||
if (credential_verify(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, true) == 0) {
|
||||
numberOfCredentials++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) {
|
||||
numberOfCredentials++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -427,10 +463,16 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
}
|
||||
else {
|
||||
selcred = &creds[0];
|
||||
if (resident && allowList_len > 1) {
|
||||
numberOfCredentials = 1;
|
||||
}
|
||||
if (numberOfCredentials > 1) {
|
||||
asserted = true;
|
||||
residentx = resident;
|
||||
for (int i = 0; i < MAX_CREDENTIAL_COUNT_IN_LIST; i++) {
|
||||
credential_free(&credsx[i]);
|
||||
}
|
||||
for (int i = 0; i < numberOfCredentials; i++) {
|
||||
credsx[i] = creds[i];
|
||||
}
|
||||
numberOfCredentialsx = numberOfCredentials;
|
||||
@@ -633,7 +675,14 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
||||
if (selcred) {
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len));
|
||||
if (resident) {
|
||||
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
|
||||
credential_derive_resident(selcred->id.data, selcred->id.len, cred_idr);
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, cred_idr, sizeof(cred_idr)));
|
||||
}
|
||||
else {
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len));
|
||||
}
|
||||
}
|
||||
else {
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, (uint8_t *)"\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01", 16));
|
||||
@@ -660,8 +709,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, lu));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data,
|
||||
selcred->userId.len));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->userId.data, selcred->userId.len));
|
||||
if (numberOfCredentials > 1 && allowList_len == 0) {
|
||||
if (selcred->userName.present == true) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "name"));
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "fido.h"
|
||||
@@ -27,22 +28,36 @@ int cbor_get_info() {
|
||||
CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2;
|
||||
CborError error = CborNoError;
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 15));
|
||||
uint8_t lfields = 14;
|
||||
#ifndef ENABLE_EMULATION
|
||||
if (phy_data.vid != 0x1050) {
|
||||
lfields++;
|
||||
}
|
||||
#else
|
||||
lfields++;
|
||||
#endif
|
||||
file_t *ef_pin_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_pin_policy)) {
|
||||
lfields += 2;
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 4));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "U2F_V2"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_0"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_1"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "FIDO_2_2"));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 6));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 7));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credBlob"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credProtect"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobKey"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "minPinLength"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "hmac-secret-mc"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "thirdPartyPayment"));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
@@ -150,13 +165,36 @@ int cbor_get_info() {
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0F));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CREDBLOB_LENGTH)); // maxCredBlobLength
|
||||
#ifndef ENABLE_EMULATION
|
||||
if (phy_data.vid != 0x1050) {
|
||||
#endif
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
|
||||
uint8_t enabled_cmds = 4;
|
||||
#ifndef ENABLE_EMULATION
|
||||
enabled_cmds += 4;
|
||||
#endif
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, enabled_cmds));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_EA_UPLOAD));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PIN_POLICY));
|
||||
#ifndef ENABLE_EMULATION
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_VIDPID));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_BTNESS));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_LED_GPIO));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_PHY_OPTS));
|
||||
#endif
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
#ifndef ENABLE_EMULATION
|
||||
}
|
||||
if (file_has_data(ef_pin_policy)) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1B));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x1C));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_pin_policy) + 2, file_get_size(ef_pin_policy) - 2));
|
||||
}
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
#endif
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
err:
|
||||
if (error != CborNoError) {
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "pico_keys.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
|
||||
static uint64_t expectedLength = 0, expectedNextOffset = 0;
|
||||
@@ -129,7 +129,7 @@ int cbor_large_blobs(const uint8_t *data, size_t len) {
|
||||
uint8_t verify_data[70] = { 0 };
|
||||
memset(verify_data, 0xff, 32);
|
||||
verify_data[32] = 0x0C;
|
||||
put_uint32_t_le(offset, verify_data + 34);
|
||||
put_uint32_t_le((uint32_t)offset, verify_data + 34);
|
||||
mbedtls_sha256(set.data, set.len, verify_data + 38, 0);
|
||||
if (verify((uint8_t)pinUvAuthProtocol, paut.data, verify_data, (uint16_t)sizeof(verify_data), pinUvAuthParam.data) != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "cbor_make_credential.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
@@ -25,7 +26,7 @@
|
||||
#include "credential.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "random.h"
|
||||
#include "pico_keys.h"
|
||||
#include "crypto_utils.h"
|
||||
|
||||
int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CborParser parser;
|
||||
@@ -39,7 +40,11 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
PublicKeyCredentialDescriptor excludeList[MAX_CREDENTIAL_COUNT_IN_LIST] = { 0 };
|
||||
size_t excludeList_len = 0;
|
||||
CredOptions options = { 0 };
|
||||
uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0;
|
||||
uint64_t pinUvAuthProtocol = 0, enterpriseAttestation = 0, hmacSecretPinUvAuthProtocol = 1;
|
||||
int64_t kty = 2, hmac_alg = 0, crv = 0;
|
||||
CborByteString kax = { 0 }, kay = { 0 }, salt_enc = { 0 }, salt_auth = { 0 };
|
||||
bool hmac_secret_mc = false;
|
||||
const bool *pin_complexity_policy = NULL;
|
||||
uint8_t *aut_data = NULL;
|
||||
size_t resp_size = 0;
|
||||
CredExtensions extensions = { 0 };
|
||||
@@ -127,12 +132,39 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_PARSE_MAP_START(_f1, 2)
|
||||
{
|
||||
CBOR_FIELD_GET_KEY_TEXT(2);
|
||||
if (strcmp(_fd2, "hmac-secret-mc") == 0) {
|
||||
hmac_secret_mc = true;
|
||||
uint64_t ukey = 0;
|
||||
CBOR_PARSE_MAP_START(_f2, 3)
|
||||
{
|
||||
CBOR_FIELD_GET_UINT(ukey, 3);
|
||||
if (ukey == 0x01) {
|
||||
CBOR_CHECK(COSE_read_key(&_f3, &kty, &hmac_alg, &crv, &kax, &kay));
|
||||
}
|
||||
else if (ukey == 0x02) {
|
||||
CBOR_FIELD_GET_BYTES(salt_enc, 3);
|
||||
}
|
||||
else if (ukey == 0x03) {
|
||||
CBOR_FIELD_GET_BYTES(salt_auth, 3);
|
||||
}
|
||||
else if (ukey == 0x04) {
|
||||
CBOR_FIELD_GET_UINT(hmacSecretPinUvAuthProtocol, 3);
|
||||
}
|
||||
else {
|
||||
CBOR_ADVANCE(3);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f2, 3);
|
||||
continue;
|
||||
}
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", extensions.hmac_secret);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "minPinLength", extensions.minPinLength);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "thirdPartyPayment", extensions.thirdPartyPayment);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "pinComplexityPolicy", pin_complexity_policy);
|
||||
|
||||
CBOR_ADVANCE(2);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
@@ -202,21 +234,36 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
if (strcmp(pubKeyCredParams[i].type.data, "public-key") != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_CBOR_UNEXPECTED_TYPE);
|
||||
}
|
||||
if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256) {
|
||||
if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP256) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_P256;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384) {
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES384 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP384) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_P384;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512) {
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES512 || pubKeyCredParams[i].alg == FIDO2_ALG_ESP512) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_P521;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB256) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_BP256R1;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB384) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_BP384R1;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ESB512) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_BP512R1;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ES256K
|
||||
#ifndef ENABLE_EMULATION
|
||||
&& (phy_data.enabled_curves & PHY_CURVE_SECP256K1)
|
||||
@@ -227,11 +274,16 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
}
|
||||
}
|
||||
#ifdef MBEDTLS_EDDSA_C
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA) {
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_EDDSA || pubKeyCredParams[i].alg == FIDO2_ALG_ED25519) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_ED25519;
|
||||
}
|
||||
}
|
||||
else if (pubKeyCredParams[i].alg == FIDO2_ALG_ED448) {
|
||||
if (curve <= 0) {
|
||||
curve = FIDO2_CURVE_ED448;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (pubKeyCredParams[i].alg <= FIDO2_ALG_RS256 && pubKeyCredParams[i].alg >= FIDO2_ALG_RS512) {
|
||||
// pass
|
||||
@@ -298,12 +350,26 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
continue;
|
||||
}
|
||||
Credential ecred = {0};
|
||||
if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash,
|
||||
&ecred) == 0 &&
|
||||
(ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED ||
|
||||
(flags & FIDO2_AUT_FLAG_UV))) {
|
||||
if (credential_is_resident(excludeList[e].id.data, excludeList[e].id.len)) {
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
file_t *ef_cred = search_dynamic_file((uint16_t)(EF_CRED + i));
|
||||
if (!file_has_data(ef_cred) || memcmp(file_get_data(ef_cred), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
}
|
||||
uint8_t *cred_idr = file_get_data(ef_cred) + 32;
|
||||
if (memcmp(cred_idr, excludeList[e].id.data, CRED_RESIDENT_LEN) == 0) {
|
||||
if (credential_load_resident(ef_cred, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) {
|
||||
credential_free(&ecred);
|
||||
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (credential_load(excludeList[e].id.data, excludeList[e].id.len, rp_id_hash, &ecred) == 0 && (ecred.extensions.credProtect != CRED_PROT_UV_REQUIRED || (flags & FIDO2_AUT_FLAG_UV))) {
|
||||
credential_free(&ecred);
|
||||
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
|
||||
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
|
||||
}
|
||||
}
|
||||
credential_free(&ecred);
|
||||
}
|
||||
@@ -313,6 +379,10 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
||||
}
|
||||
|
||||
if (hmac_secret_mc && extensions.hmac_secret != ptrue) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
|
||||
if (options.up == ptrue || options.up == NULL) { //14.1
|
||||
if (pinUvAuthParam.present == true) {
|
||||
if (getUserPresentFlagValue() == false) {
|
||||
@@ -332,11 +402,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
const known_app_t *ka = find_app_by_rp_id_hash(rp_id_hash);
|
||||
|
||||
uint8_t cred_id[MAX_CRED_ID_LENGTH] = {0};
|
||||
size_t cred_id_len = 0;
|
||||
uint16_t cred_id_len = 0;
|
||||
|
||||
CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options,
|
||||
&extensions, (!ka || ka->use_sign_count == ptrue), alg, curve,
|
||||
cred_id, &cred_id_len));
|
||||
CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options, &extensions, (!ka || ka->use_sign_count == ptrue), alg, curve, cred_id, &cred_id_len));
|
||||
|
||||
if (getUserVerifiedFlagValue()) {
|
||||
flags |= FIDO2_AUT_FLAG_UV;
|
||||
@@ -372,6 +440,12 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
if (extensions.credBlob.present == true) {
|
||||
l++;
|
||||
}
|
||||
if (hmac_secret_mc) {
|
||||
l++;
|
||||
}
|
||||
if (pin_complexity_policy == ptrue) {
|
||||
l++;
|
||||
}
|
||||
if (l > 0) {
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
||||
if (extensions.credBlob.present == true) {
|
||||
@@ -383,15 +457,69 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
|
||||
}
|
||||
if (extensions.hmac_secret == ptrue) {
|
||||
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||
}
|
||||
if (minPinLen > 0) {
|
||||
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen));
|
||||
}
|
||||
if (hmac_secret_mc) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret-mc"));
|
||||
|
||||
uint8_t sharedSecret[64] = {0};
|
||||
mbedtls_ecp_point Qp;
|
||||
mbedtls_ecp_point_init(&Qp);
|
||||
mbedtls_mpi_lset(&Qp.Z, 1);
|
||||
if (mbedtls_mpi_read_binary(&Qp.X, kax.data, kax.len) != 0) {
|
||||
mbedtls_ecp_point_free(&Qp);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (mbedtls_mpi_read_binary(&Qp.Y, kay.data, kay.len) != 0) {
|
||||
mbedtls_ecp_point_free(&Qp);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
int ret = ecdh((uint8_t)hmacSecretPinUvAuthProtocol, &Qp, sharedSecret);
|
||||
mbedtls_ecp_point_free(&Qp);
|
||||
if (ret != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (verify((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_auth.data) != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_EXTENSION_FIRST);
|
||||
}
|
||||
uint8_t salt_dec[64] = {0}, poff = ((uint8_t)hmacSecretPinUvAuthProtocol - 1) * IV_SIZE;
|
||||
ret = decrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, salt_enc.data, (uint16_t)salt_enc.len, salt_dec);
|
||||
if (ret != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t cred_random[64] = {0}, *crd = NULL;
|
||||
ret = credential_derive_hmac_key(cred_id, cred_id_len, cred_random);
|
||||
if (ret != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (flags & FIDO2_AUT_FLAG_UV) {
|
||||
crd = cred_random + 32;
|
||||
}
|
||||
else {
|
||||
crd = cred_random;
|
||||
}
|
||||
uint8_t out1[64] = {0}, hmac_res[80] = {0};
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec, 32, out1);
|
||||
if ((uint8_t)salt_enc.len == 64 + poff) {
|
||||
mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), crd, 32, salt_dec + 32, 32, out1 + 32);
|
||||
}
|
||||
encrypt((uint8_t)hmacSecretPinUvAuthProtocol, sharedSecret, out1, (uint16_t)(salt_enc.len - poff), hmac_res);
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, hmac_res, salt_enc.len));
|
||||
}
|
||||
if (pin_complexity_policy == ptrue) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "pinComplexityPolicy"));
|
||||
file_t *ef_pin_complexity_policy = search_by_fid(EF_PIN_COMPLEXITY_POLICY, NULL, SPECIFY_EF);
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, file_has_data(ef_pin_complexity_policy)));
|
||||
}
|
||||
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
|
||||
@@ -417,15 +545,23 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(COSE_key(&ekey, &encoder, &mapEncoder));
|
||||
size_t rs = cbor_encoder_get_buffer_size(&encoder, cbor_buf);
|
||||
|
||||
size_t aut_data_len = 32 + 1 + 4 + (16 + 2 + cred_id_len + rs) + ext_len;
|
||||
size_t aut_data_len = 32 + 1 + 4 + (16 + 2 + (options.rk == ptrue ? CRED_RESIDENT_LEN : cred_id_len) + rs) + ext_len;
|
||||
aut_data = (uint8_t *) calloc(1, aut_data_len + clientDataHash.len);
|
||||
uint8_t *pa = aut_data;
|
||||
memcpy(pa, rp_id_hash, 32); pa += 32;
|
||||
*pa++ = flags;
|
||||
pa += put_uint32_t_be(ctr, pa);
|
||||
memcpy(pa, aaguid, 16); pa += 16;
|
||||
pa += put_uint16_t_be(cred_id_len, pa);
|
||||
memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len;
|
||||
if (options.rk == ptrue) {
|
||||
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
|
||||
pa += put_uint16_t_be(sizeof(cred_idr), pa);
|
||||
credential_derive_resident(cred_id, cred_id_len, cred_idr);
|
||||
memcpy(pa, cred_idr, sizeof(cred_idr)); pa += sizeof(cred_idr);
|
||||
}
|
||||
else {
|
||||
pa += put_uint16_t_be(cred_id_len, pa);
|
||||
memcpy(pa, cred_id, cred_id_len); pa += (uint16_t)cred_id_len;
|
||||
}
|
||||
memcpy(pa, cbor_buf, rs); pa += (uint16_t)rs;
|
||||
memcpy(pa, ext, ext_len); pa += (uint16_t)ext_len;
|
||||
if ((size_t)(pa - aut_data) != aut_data_len) {
|
||||
@@ -436,14 +572,14 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
memcpy(pa, clientDataHash.data, clientDataHash.len);
|
||||
uint8_t hash[64] = {0}, sig[MBEDTLS_ECDSA_MAX_LEN] = {0};
|
||||
const mbedtls_md_info_t *md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1) {
|
||||
if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1 || ekey.grp.id == MBEDTLS_ECP_DP_BP384R1) {
|
||||
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384);
|
||||
}
|
||||
else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) {
|
||||
else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1 || ekey.grp.id == MBEDTLS_ECP_DP_BP512R1) {
|
||||
md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
|
||||
}
|
||||
#ifdef MBEDTLS_EDDSA_C
|
||||
else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519) {
|
||||
else if (ekey.grp.id == MBEDTLS_ECP_DP_ED25519 || ekey.grp.id == MBEDTLS_ECP_DP_ED448) {
|
||||
md = NULL;
|
||||
}
|
||||
#endif
|
||||
@@ -483,7 +619,7 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
#ifndef ENABLE_EMULATION
|
||||
uint8_t *p = (uint8_t *)user.parent.name.data + 5;
|
||||
if (memcmp(p, "CommissionProfile", 17) == 0) {
|
||||
ret = phy_unserialize_data(user.id.data, user.id.len, &phy_data);
|
||||
ret = phy_unserialize_data(user.id.data, (uint16_t)user.id.len, &phy_data);
|
||||
if (ret == PICOKEY_OK) {
|
||||
ret = phy_save();
|
||||
}
|
||||
@@ -519,12 +655,12 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aut_data, aut_data_len));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
|
||||
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false || is_nitrokey ? 3 : 2));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, self_attestation == false || is_nk ? 3 : 2));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg"));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation || is_nitrokey ? -alg : -FIDO2_ALG_ES256));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, self_attestation || is_nk ? -alg : -FIDO2_ALG_ES256));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "sig"));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, sig, olen));
|
||||
if (self_attestation == false || is_nitrokey) {
|
||||
if (self_attestation == false || is_nk) {
|
||||
CborEncoder arrEncoder;
|
||||
file_t *ef_cert = NULL;
|
||||
if (enterpriseAttestation == 2) {
|
||||
@@ -569,6 +705,10 @@ err:
|
||||
CBOR_FREE_BYTE_STRING(user.id);
|
||||
CBOR_FREE_BYTE_STRING(user.displayName);
|
||||
CBOR_FREE_BYTE_STRING(user.parent.name);
|
||||
CBOR_FREE_BYTE_STRING(kax);
|
||||
CBOR_FREE_BYTE_STRING(kay);
|
||||
CBOR_FREE_BYTE_STRING(salt_enc);
|
||||
CBOR_FREE_BYTE_STRING(salt_auth);
|
||||
if (extensions.present == true) {
|
||||
CBOR_FREE_BYTE_STRING(extensions.credBlob);
|
||||
}
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "file.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#ifdef ESP_PLATFORM
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
|
||||
|
||||
@@ -15,13 +15,13 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "pico_keys.h"
|
||||
#include "random.h"
|
||||
#include "mbedtls/ecdh.h"
|
||||
#include "mbedtls/chachapoly.h"
|
||||
@@ -237,25 +237,14 @@ int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, buffer + sizeof(buffer) - ret, ret));
|
||||
}
|
||||
else if (vendorCmd == 0x02) {
|
||||
if (vendorParam.present == false) {
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
}
|
||||
file_t *ef_ee_ea = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
|
||||
if (ef_ee_ea) {
|
||||
file_put_data(ef_ee_ea, vendorParam.data, (uint16_t)vendorParam.len);
|
||||
}
|
||||
low_flash_available();
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
#ifndef ENABLE_EMULATION
|
||||
else if (cmd == CTAP_VENDOR_PHY_OPTS) {
|
||||
if (vendorCmd == 0x01) {
|
||||
uint16_t opts = 0;
|
||||
if (file_has_data(ef_phy)) {
|
||||
uint8_t *data = file_get_data(ef_phy);
|
||||
opts = get_uint16_t_be(data + PHY_OPTS);
|
||||
uint8_t *pdata = file_get_data(ef_phy);
|
||||
opts = get_uint16_t_be(pdata + PHY_OPTS);
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "apdu.h"
|
||||
#include "ctap.h"
|
||||
#include "random.h"
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "apdu.h"
|
||||
#include "ctap.h"
|
||||
#include "random.h"
|
||||
@@ -69,11 +69,7 @@ int cmd_register() {
|
||||
}
|
||||
if (memcmp(req->appId, bogus_firefox,
|
||||
CTAP_APPID_SIZE) == 0 || memcmp(req->appId, bogus_chrome, CTAP_APPID_SIZE) == 0)
|
||||
#ifndef ENABLE_EMULATION
|
||||
{ return ctap_error(CTAP1_ERR_CHANNEL_BUSY); }
|
||||
#else
|
||||
{ return SW_DATA_INVALID(); }
|
||||
#endif
|
||||
mbedtls_ecdsa_context key;
|
||||
mbedtls_ecdsa_init(&key);
|
||||
int ret = derive_key(req->appId, true, resp->keyHandleCertSig, MBEDTLS_ECP_DP_SECP256R1, &key);
|
||||
|
||||
@@ -15,10 +15,11 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "mbedtls/chachapoly.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
#include "credential.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include "hid/ctap_hid.h"
|
||||
@@ -26,7 +27,6 @@
|
||||
#include "ctap.h"
|
||||
#include "random.h"
|
||||
#include "files.h"
|
||||
#include "pico_keys.h"
|
||||
#include "otp.h"
|
||||
|
||||
int credential_derive_chacha_key(uint8_t *outk, const uint8_t *);
|
||||
@@ -93,7 +93,7 @@ int credential_create(CborCharString *rpId,
|
||||
int alg,
|
||||
int curve,
|
||||
uint8_t *cred_id,
|
||||
size_t *cred_id_len) {
|
||||
uint16_t *cred_id_len) {
|
||||
CborEncoder encoder, mapEncoder, mapEncoder2;
|
||||
CborError error = CborNoError;
|
||||
uint8_t rp_id_hash[32];
|
||||
@@ -150,7 +150,7 @@ int credential_create(CborCharString *rpId,
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
size_t rs = cbor_encoder_get_buffer_size(&encoder, cred_id);
|
||||
*cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN;
|
||||
*cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + (uint16_t)rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN;
|
||||
uint8_t key[32] = {0};
|
||||
credential_derive_chacha_key(key, (const uint8_t *)CRED_PROTO);
|
||||
uint8_t iv[CRED_IV_LEN] = {0};
|
||||
@@ -314,7 +314,7 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
|
||||
if (memcmp(file_get_data(ef), rp_id_hash, 32) != 0) {
|
||||
continue;
|
||||
}
|
||||
ret = credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, &rcred);
|
||||
ret = credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, &rcred);
|
||||
if (ret != 0) {
|
||||
credential_free(&rcred);
|
||||
continue;
|
||||
@@ -330,11 +330,14 @@ int credential_store(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *
|
||||
if (sloti == -1) {
|
||||
return -1;
|
||||
}
|
||||
uint8_t *data = (uint8_t *) calloc(1, cred_id_len + 32);
|
||||
uint8_t cred_idr[CRED_RESIDENT_LEN] = {0};
|
||||
credential_derive_resident(cred_id, cred_id_len, cred_idr);
|
||||
uint8_t *data = (uint8_t *) calloc(1, cred_id_len + 32 + CRED_RESIDENT_LEN);
|
||||
memcpy(data, rp_id_hash, 32);
|
||||
memcpy(data + 32, cred_id, cred_id_len);
|
||||
memcpy(data + 32, cred_idr, CRED_RESIDENT_LEN);
|
||||
memcpy(data + 32 + CRED_RESIDENT_LEN, cred_id, cred_id_len);
|
||||
file_t *ef = file_new((uint16_t)(EF_CRED + sloti));
|
||||
file_put_data(ef, data, (uint16_t)cred_id_len + 32);
|
||||
file_put_data(ef, data, (uint16_t)cred_id_len + 32 + CRED_RESIDENT_LEN);
|
||||
free(data);
|
||||
|
||||
if (new_record == true) { //increase rps
|
||||
@@ -421,3 +424,33 @@ int credential_derive_large_blob_key(const uint8_t *cred_id, size_t cred_id_len,
|
||||
mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int credential_derive_resident(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk) {
|
||||
memset(outk, 0, CRED_RESIDENT_LEN);
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
uint8_t *cred_idr = outk + CRED_RESIDENT_HEADER_LEN;
|
||||
mbedtls_md_hmac(md_info, cred_idr, 32, pico_serial.id, sizeof(pico_serial.id), outk);
|
||||
memcpy(outk + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN);
|
||||
outk[4 + CRED_PROTO_RESIDENT_LEN] = 0x00;
|
||||
outk[4 + CRED_PROTO_RESIDENT_LEN + 1] = 0x00;
|
||||
|
||||
mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "SLIP-0022", 9, cred_idr);
|
||||
mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) cred_id, CRED_PROTO_LEN, cred_idr);
|
||||
mbedtls_md_hmac(md_info, cred_idr, 32, (uint8_t *) "resident", 8, cred_idr);
|
||||
mbedtls_md_hmac(md_info, cred_idr, 32, cred_id, cred_id_len, cred_idr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool credential_is_resident(const uint8_t *cred_id, size_t cred_id_len) {
|
||||
if (cred_id_len < 4 + CRED_PROTO_RESIDENT_LEN) {
|
||||
return false;
|
||||
}
|
||||
return memcmp(cred_id + 4, CRED_PROTO_RESIDENT, CRED_PROTO_RESIDENT_LEN) == 0;
|
||||
}
|
||||
|
||||
int credential_load_resident(const file_t *ef, const uint8_t *rp_id_hash, Credential *cred) {
|
||||
if (credential_is_resident(file_get_data(ef) + 32, file_get_size(ef) - 32)) {
|
||||
return credential_load(file_get_data(ef) + 32 + CRED_RESIDENT_LEN, file_get_size(ef) - 32 - CRED_RESIDENT_LEN, rp_id_hash, cred);
|
||||
}
|
||||
return credential_load(file_get_data(ef) + 32, file_get_size(ef) - 32, rp_id_hash, cred);
|
||||
}
|
||||
|
||||
@@ -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_
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
|
||||
uint8_t PICO_PRODUCT = 2; // Pico FIDO
|
||||
|
||||
137
src/fido/fido.c
137
src/fido/fido.c
@@ -15,9 +15,9 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "kek.h"
|
||||
#include "pico_keys.h"
|
||||
#include "apdu.h"
|
||||
#include "ctap.h"
|
||||
#include "files.h"
|
||||
@@ -25,10 +25,10 @@
|
||||
#include "random.h"
|
||||
#include "mbedtls/x509_crt.h"
|
||||
#include "mbedtls/hkdf.h"
|
||||
#if defined(USB_ITF_CCID) || defined(ENABLE_EMULATION)
|
||||
#if defined(USB_ITF_CCID)
|
||||
#include "ccid/ccid.h"
|
||||
#endif
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#include <math.h>
|
||||
@@ -42,6 +42,7 @@ int fido_process_apdu();
|
||||
int fido_unload();
|
||||
|
||||
pinUvAuthToken_t paut = { 0 };
|
||||
persistentPinUvAuthToken_t ppaut = { 0 };
|
||||
|
||||
uint8_t keydev_dec[32];
|
||||
bool has_keydev_dec = false;
|
||||
@@ -118,6 +119,15 @@ mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve) {
|
||||
return MBEDTLS_ECP_DP_ED448;
|
||||
}
|
||||
#endif
|
||||
else if (curve == FIDO2_CURVE_BP256R1) {
|
||||
return MBEDTLS_ECP_DP_BP256R1;
|
||||
}
|
||||
else if (curve == FIDO2_CURVE_BP384R1) {
|
||||
return MBEDTLS_ECP_DP_BP384R1;
|
||||
}
|
||||
else if (curve == FIDO2_CURVE_BP512R1) {
|
||||
return MBEDTLS_ECP_DP_BP512R1;
|
||||
}
|
||||
return MBEDTLS_ECP_DP_NONE;
|
||||
}
|
||||
int mbedtls_curve_to_fido(mbedtls_ecp_group_id id) {
|
||||
@@ -158,7 +168,7 @@ int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key) {
|
||||
uint8_t key_path[KEY_PATH_LEN];
|
||||
memcpy(key_path, cred_id, KEY_PATH_LEN);
|
||||
*(uint32_t *) key_path = 0x80000000 | 10022;
|
||||
for (int i = 1; i < KEY_PATH_ENTRIES; i++) {
|
||||
for (size_t i = 1; i < KEY_PATH_ENTRIES; i++) {
|
||||
*(uint32_t *) (key_path + i * sizeof(uint32_t)) |= 0x80000000;
|
||||
}
|
||||
return derive_key(NULL, false, key_path, mbedtls_curve, key);
|
||||
@@ -194,7 +204,7 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
|
||||
return ret;
|
||||
}
|
||||
|
||||
int load_keydev(uint8_t *key) {
|
||||
int load_keydev(uint8_t key[32]) {
|
||||
if (has_keydev_dec == false && !file_has_data(ef_keydev)) {
|
||||
return PICOKEY_ERR_MEMORY_FATAL;
|
||||
}
|
||||
@@ -203,13 +213,39 @@ int load_keydev(uint8_t *key) {
|
||||
memcpy(key, keydev_dec, sizeof(keydev_dec));
|
||||
}
|
||||
else {
|
||||
memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev));
|
||||
|
||||
if (mkek_decrypt(key, 32) != PICOKEY_OK) {
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
uint16_t fid_size = file_get_size(ef_keydev);
|
||||
if (fid_size == 32) {
|
||||
memcpy(key, file_get_data(ef_keydev), 32);
|
||||
if (mkek_decrypt(key, 32) != PICOKEY_OK) {
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
}
|
||||
if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) {
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
}
|
||||
}
|
||||
if (otp_key_1 && aes_decrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32) != PICOKEY_OK) {
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
else if (fid_size == 33 || fid_size == 61) {
|
||||
uint8_t format = *file_get_data(ef_keydev);
|
||||
if (format == 0x01 || format == 0x02) { // Format indicator
|
||||
if (format == 0x02) {
|
||||
uint8_t tmp_key[61];
|
||||
memcpy(tmp_key, file_get_data(ef_keydev), sizeof(tmp_key));
|
||||
int ret = decrypt_with_aad(session_pin, tmp_key + 1, 60, key);
|
||||
if (ret != PICOKEY_OK) {
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
}
|
||||
}
|
||||
else {
|
||||
memcpy(key, file_get_data(ef_keydev) + 1, 32);
|
||||
}
|
||||
uint8_t kbase[32];
|
||||
derive_kbase(kbase);
|
||||
int ret = aes_decrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, key, 32);
|
||||
if (ret != PICOKEY_OK) {
|
||||
mbedtls_platform_zeroize(kbase, sizeof(kbase));
|
||||
return PICOKEY_EXEC_ERROR;
|
||||
}
|
||||
mbedtls_platform_zeroize(kbase, sizeof(kbase));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -217,7 +253,7 @@ int load_keydev(uint8_t *key) {
|
||||
}
|
||||
|
||||
int verify_key(const uint8_t *appId, const uint8_t *keyHandle, mbedtls_ecp_keypair *key) {
|
||||
for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
|
||||
for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) {
|
||||
uint32_t k = *(uint32_t *) &keyHandle[i * sizeof(uint32_t)];
|
||||
if (!(k & 0x80000000)) {
|
||||
return -1;
|
||||
@@ -258,7 +294,7 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
|
||||
return r;
|
||||
}
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
|
||||
for (int i = 0; i < KEY_PATH_ENTRIES; i++) {
|
||||
for (size_t i = 0; i < KEY_PATH_ENTRIES; i++) {
|
||||
if (new_key == true) {
|
||||
uint32_t val = 0;
|
||||
random_gen(NULL, (uint8_t *) &val, sizeof(val));
|
||||
@@ -305,6 +341,23 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
|
||||
return r;
|
||||
}
|
||||
|
||||
int encrypt_keydev_f1(const uint8_t keydev[32]) {
|
||||
uint8_t kdata[33] = {0};
|
||||
kdata[0] = 0x01; // Format indicator
|
||||
memcpy(kdata + 1, keydev, 32);
|
||||
uint8_t kbase[32];
|
||||
derive_kbase(kbase);
|
||||
int ret = aes_encrypt(kbase, pico_serial_hash, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata + 1, 32);
|
||||
mbedtls_platform_zeroize(kbase, sizeof(kbase));
|
||||
if (ret != PICOKEY_OK) {
|
||||
return ret;
|
||||
}
|
||||
ret = file_put_data(ef_keydev, kdata, 33);
|
||||
mbedtls_platform_zeroize(kdata, sizeof(kdata));
|
||||
low_flash_available();
|
||||
return ret;
|
||||
}
|
||||
|
||||
int scan_files_fido() {
|
||||
ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF);
|
||||
ef_keydev_enc = search_by_fid(EF_KEY_DEV_ENC, NULL, SPECIFY_EF);
|
||||
@@ -320,17 +373,16 @@ int scan_files_fido() {
|
||||
mbedtls_ecdsa_free(&ecdsa);
|
||||
return ret;
|
||||
}
|
||||
uint8_t kdata[64];
|
||||
uint8_t keydev[32] = {0};
|
||||
size_t key_size = 0;
|
||||
ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, kdata, sizeof(kdata));
|
||||
if (ret != PICOKEY_OK) {
|
||||
return ret;
|
||||
ret = mbedtls_ecp_write_key_ext(&ecdsa, &key_size, keydev, sizeof(keydev));
|
||||
if (ret != 0 || key_size != 32) {
|
||||
mbedtls_platform_zeroize(keydev, sizeof(keydev));
|
||||
mbedtls_ecdsa_free(&ecdsa);
|
||||
return ret != 0 ? ret : PICOKEY_EXEC_ERROR;
|
||||
}
|
||||
if (otp_key_1) {
|
||||
ret = aes_encrypt(otp_key_1, NULL, 32 * 8, PICO_KEYS_AES_MODE_CBC, kdata, 32);
|
||||
}
|
||||
ret = file_put_data(ef_keydev, kdata, (uint16_t)key_size);
|
||||
mbedtls_platform_zeroize(kdata, sizeof(kdata));
|
||||
encrypt_keydev_f1(keydev);
|
||||
mbedtls_platform_zeroize(keydev, sizeof(keydev));
|
||||
mbedtls_ecdsa_free(&ecdsa);
|
||||
if (ret != PICOKEY_OK) {
|
||||
return ret;
|
||||
@@ -341,21 +393,6 @@ int scan_files_fido() {
|
||||
else {
|
||||
printf("FATAL ERROR: KEY DEV not found in memory!\r\n");
|
||||
}
|
||||
if (ef_mkek) { // No encrypted MKEK
|
||||
if (!file_has_data(ef_mkek)) {
|
||||
uint8_t mkek[MKEK_IV_SIZE + MKEK_KEY_SIZE];
|
||||
random_gen(NULL, mkek, sizeof(mkek));
|
||||
file_put_data(ef_mkek, mkek, sizeof(mkek));
|
||||
int ret = aes_encrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), file_get_data(ef_keydev), 32);
|
||||
mbedtls_platform_zeroize(mkek, sizeof(mkek));
|
||||
if (ret != 0) {
|
||||
printf("FATAL ERROR: MKEK encryption failed!\r\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
printf("FATAL ERROR: MKEK not found in memory!\r\n");
|
||||
}
|
||||
ef_certdev = search_by_fid(EF_EE_DEV, NULL, SPECIFY_EF);
|
||||
if (ef_certdev) {
|
||||
if (!file_has_data(ef_certdev)) {
|
||||
@@ -399,13 +436,6 @@ int scan_files_fido() {
|
||||
printf("FATAL ERROR: Global counter not found in memory!\r\n");
|
||||
}
|
||||
ef_pin = search_by_fid(EF_PIN, NULL, SPECIFY_EF);
|
||||
if (file_get_size(ef_pin) == 18) { // Upgrade PIN storage
|
||||
uint8_t pin_data[34] = { 0 }, dhash[32];
|
||||
memcpy(pin_data, file_get_data(ef_pin), 18);
|
||||
double_hash_pin(pin_data + 2, 16, dhash);
|
||||
memcpy(pin_data + 2, dhash, 32);
|
||||
file_put_data(ef_pin, pin_data, 34);
|
||||
}
|
||||
ef_authtoken = search_by_fid(EF_AUTHTOKEN, NULL, SPECIFY_EF);
|
||||
if (ef_authtoken) {
|
||||
if (!file_has_data(ef_authtoken)) {
|
||||
@@ -419,6 +449,19 @@ int scan_files_fido() {
|
||||
else {
|
||||
printf("FATAL ERROR: Auth Token not found in memory!\r\n");
|
||||
}
|
||||
file_t *ef_pauthtoken = search_by_fid(EF_PAUTHTOKEN, NULL, SPECIFY_EF);
|
||||
if (ef_pauthtoken) {
|
||||
if (!file_has_data(ef_pauthtoken)) {
|
||||
uint8_t t[32];
|
||||
random_gen(NULL, t, sizeof(t));
|
||||
file_put_data(ef_pauthtoken, t, sizeof(t));
|
||||
}
|
||||
ppaut.data = file_get_data(ef_pauthtoken);
|
||||
ppaut.len = file_get_size(ef_pauthtoken);
|
||||
}
|
||||
else {
|
||||
printf("FATAL ERROR: Persistent Auth Token not found in memory!\r\n");
|
||||
}
|
||||
ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF);
|
||||
if (!file_has_data(ef_largeblob)) {
|
||||
file_put_data(ef_largeblob, (const uint8_t *) "\x80\x76\xbe\x8b\x52\x8d\x00\x75\xf7\xaa\xe9\x8d\x6f\xa5\x7a\x6d\x3c", 17);
|
||||
@@ -436,12 +479,14 @@ void scan_all() {
|
||||
extern void init_otp();
|
||||
void init_fido() {
|
||||
scan_all();
|
||||
#ifdef ENABLE_OTP_APP
|
||||
init_otp();
|
||||
#endif
|
||||
}
|
||||
|
||||
bool wait_button_pressed() {
|
||||
uint32_t val = EV_PRESS_BUTTON;
|
||||
#ifndef ENABLE_EMULATION
|
||||
#if defined(PICO_PLATFORM) || defined(ESP_PLATFORM)
|
||||
queue_try_add(&card_to_usb_q, &val);
|
||||
do {
|
||||
queue_remove_blocking(&usb_to_card_q, &val);
|
||||
@@ -485,11 +530,13 @@ extern int cmd_register();
|
||||
extern int cmd_authenticate();
|
||||
extern int cmd_version();
|
||||
extern int cbor_parse(int, uint8_t *, size_t);
|
||||
extern void driver_init_hid();
|
||||
|
||||
#define CTAP_CBOR 0x10
|
||||
|
||||
int cmd_cbor() {
|
||||
uint8_t *old_buf = res_APDU;
|
||||
driver_init_hid();
|
||||
int ret = cbor_parse(0x90, apdu.data, apdu.nc);
|
||||
if (ret != 0) {
|
||||
return SW_EXEC_ERROR();
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#ifndef _FIDO_H_
|
||||
#define _FIDO_H_
|
||||
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "pico/stdlib.h"
|
||||
#endif
|
||||
#ifndef ESP_PLATFORM
|
||||
@@ -31,11 +31,7 @@
|
||||
#ifdef MBEDTLS_EDDSA_C
|
||||
#include "mbedtls/eddsa.h"
|
||||
#endif
|
||||
#ifndef ENABLE_EMULATION
|
||||
#include "hid/ctap_hid.h"
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
#define CTAP_PUBKEY_LEN (65)
|
||||
#define KEY_PATH_LEN (32)
|
||||
@@ -55,20 +51,28 @@ extern void init_fido();
|
||||
extern mbedtls_ecp_group_id fido_curve_to_mbedtls(int curve);
|
||||
extern int mbedtls_curve_to_fido(mbedtls_ecp_group_id id);
|
||||
extern int fido_load_key(int curve, const uint8_t *cred_id, mbedtls_ecp_keypair *key);
|
||||
extern int load_keydev(uint8_t *key);
|
||||
extern int load_keydev(uint8_t key[32]);
|
||||
extern int encrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out);
|
||||
extern int decrypt(uint8_t protocol, const uint8_t *key, const uint8_t *in, uint16_t in_len, uint8_t *out);
|
||||
extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSecret);
|
||||
|
||||
#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256 P256
|
||||
#define FIDO2_ALG_ES256 -7 //ECDSA-SHA256
|
||||
#define FIDO2_ALG_EDDSA -8 //EdDSA
|
||||
#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384 P384
|
||||
#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512 P521
|
||||
#define FIDO2_ALG_ESP256 -9 //ECDSA-SHA256 P256
|
||||
#define FIDO2_ALG_ED25519 -19 //EDDSA Ed25519
|
||||
#define FIDO2_ALG_ES384 -35 //ECDSA-SHA384
|
||||
#define FIDO2_ALG_ES512 -36 //ECDSA-SHA512
|
||||
#define FIDO2_ALG_ECDH_ES_HKDF_256 -25 //ECDH-ES + HKDF-256
|
||||
#define FIDO2_ALG_ES256K -47
|
||||
#define FIDO2_ALG_ESP384 -51 //ECDSA-SHA384 P384
|
||||
#define FIDO2_ALG_ESP512 -52 //ECDSA-SHA512 P521
|
||||
#define FIDO2_ALG_ED448 -53 //EDDSA Ed448
|
||||
#define FIDO2_ALG_RS256 -257
|
||||
#define FIDO2_ALG_RS384 -258
|
||||
#define FIDO2_ALG_RS512 -259
|
||||
#define FIDO2_ALG_ESB256 -265 //ECDSA-SHA256 BP256r1
|
||||
#define FIDO2_ALG_ESB384 -267 //ECDSA-SHA384 BP384r1
|
||||
#define FIDO2_ALG_ESB512 -268 //ECDSA-SHA512 BP512r1
|
||||
|
||||
#define FIDO2_CURVE_P256 1
|
||||
#define FIDO2_CURVE_P384 2
|
||||
@@ -78,6 +82,9 @@ extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSec
|
||||
#define FIDO2_CURVE_ED25519 6
|
||||
#define FIDO2_CURVE_ED448 7
|
||||
#define FIDO2_CURVE_P256K1 8
|
||||
#define FIDO2_CURVE_BP256R1 9
|
||||
#define FIDO2_CURVE_BP384R1 10
|
||||
#define FIDO2_CURVE_BP512R1 11
|
||||
|
||||
#define FIDO2_AUT_FLAG_UP 0x1
|
||||
#define FIDO2_AUT_FLAG_UV 0x4
|
||||
@@ -129,9 +136,17 @@ typedef struct pinUvAuthToken {
|
||||
bool user_verified;
|
||||
} pinUvAuthToken_t;
|
||||
|
||||
typedef struct persistentPinUvAuthToken {
|
||||
uint8_t *data;
|
||||
size_t len;
|
||||
uint8_t permissions;
|
||||
} persistentPinUvAuthToken_t;
|
||||
|
||||
extern uint32_t user_present_time_limit;
|
||||
|
||||
extern pinUvAuthToken_t paut;
|
||||
extern persistentPinUvAuthToken_t ppaut;
|
||||
|
||||
extern int verify(uint8_t protocol, const uint8_t *key, const uint8_t *data, uint16_t len, uint8_t *sign);
|
||||
|
||||
extern uint8_t session_pin[32];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "stdlib.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "pico/stdlib.h"
|
||||
#endif
|
||||
#include "kek.h"
|
||||
@@ -85,46 +85,6 @@ void release_mkek(uint8_t *mkek) {
|
||||
mbedtls_platform_zeroize(mkek, MKEK_SIZE);
|
||||
}
|
||||
|
||||
int store_mkek(const uint8_t *mkek) {
|
||||
uint8_t tmp_mkek[MKEK_SIZE];
|
||||
if (mkek == NULL) {
|
||||
const uint8_t *rd = random_bytes_get(MKEK_IV_SIZE + MKEK_KEY_SIZE);
|
||||
memcpy(tmp_mkek, rd, MKEK_IV_SIZE + MKEK_KEY_SIZE);
|
||||
}
|
||||
else {
|
||||
memcpy(tmp_mkek, mkek, MKEK_SIZE);
|
||||
}
|
||||
if (otp_key_1) {
|
||||
mkek_masked(tmp_mkek, otp_key_1);
|
||||
}
|
||||
*(uint32_t *) MKEK_CHECKSUM(tmp_mkek) = crc32c(MKEK_KEY(tmp_mkek), MKEK_KEY_SIZE);
|
||||
uint8_t tmp_mkek_pin[MKEK_SIZE];
|
||||
memcpy(tmp_mkek_pin, tmp_mkek, MKEK_SIZE);
|
||||
file_t *tf = search_file(EF_MKEK);
|
||||
if (!tf) {
|
||||
release_mkek(tmp_mkek);
|
||||
release_mkek(tmp_mkek_pin);
|
||||
return PICOKEY_ERR_FILE_NOT_FOUND;
|
||||
}
|
||||
aes_encrypt_cfb_256(session_pin, MKEK_IV(tmp_mkek_pin), MKEK_KEY(tmp_mkek_pin), MKEK_KEY_SIZE + MKEK_KEY_CS_SIZE);
|
||||
file_put_data(tf, tmp_mkek_pin, MKEK_SIZE);
|
||||
release_mkek(tmp_mkek_pin);
|
||||
low_flash_available();
|
||||
release_mkek(tmp_mkek);
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
|
||||
int mkek_encrypt(uint8_t *data, uint16_t len) {
|
||||
int r;
|
||||
uint8_t mkek[MKEK_SIZE + 4];
|
||||
if ((r = load_mkek(mkek)) != PICOKEY_OK) {
|
||||
return r;
|
||||
}
|
||||
r = aes_encrypt_cfb_256(MKEK_KEY(mkek), MKEK_IV(mkek), data, len);
|
||||
release_mkek(mkek);
|
||||
return r;
|
||||
}
|
||||
|
||||
int mkek_decrypt(uint8_t *data, uint16_t len) {
|
||||
int r;
|
||||
uint8_t mkek[MKEK_SIZE + 4];
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "ctap2_cbor.h"
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "apdu.h"
|
||||
#include "version.h"
|
||||
#include "files.h"
|
||||
@@ -40,7 +40,9 @@ int man_select(app_t *a, uint8_t force) {
|
||||
apdu.ne = res_APDU_size;
|
||||
if (force) {
|
||||
scan_all();
|
||||
#ifdef ENABLE_OTP_APP
|
||||
init_otp();
|
||||
#endif
|
||||
}
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
@@ -114,7 +116,7 @@ int man_get_config() {
|
||||
if (!file_has_data(ef)) {
|
||||
res_APDU[res_APDU_size++] = TAG_USB_ENABLED;
|
||||
res_APDU[res_APDU_size++] = 2;
|
||||
uint16_t caps = 0;
|
||||
caps = 0;
|
||||
if (cap_supported(CAP_FIDO2)) {
|
||||
caps |= CAP_FIDO2;
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
#define _MANAGEMENT_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "pico/stdlib.h"
|
||||
#endif
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "apdu.h"
|
||||
#include "files.h"
|
||||
#include "random.h"
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "asn1.h"
|
||||
#include "crypto_utils.h"
|
||||
#include "management.h"
|
||||
extern bool is_nk;
|
||||
|
||||
#define MAX_OATH_CRED 255
|
||||
#define CHALLENGE_LEN 8
|
||||
@@ -44,6 +45,10 @@
|
||||
#define TAG_PASSWORD 0x80
|
||||
#define TAG_NEW_PASSWORD 0x81
|
||||
#define TAG_PIN_COUNTER 0x82
|
||||
#define TAG_PWS_LOGIN 0x83
|
||||
#define TAG_PWS_PASSWORD 0x84
|
||||
#define TAG_PWS_METADATA 0x85
|
||||
#define TAG_SERIAL_NUMBER 0x8F
|
||||
|
||||
#define ALG_HMAC_SHA1 0x01
|
||||
#define ALG_HMAC_SHA256 0x02
|
||||
@@ -56,6 +61,7 @@
|
||||
|
||||
#define PROP_INC 0x01
|
||||
#define PROP_TOUCH 0x02
|
||||
#define PROP_PIN 0x03
|
||||
|
||||
int oath_process_apdu();
|
||||
int oath_unload();
|
||||
@@ -99,6 +105,12 @@ int oath_select(app_t *a, uint8_t force) {
|
||||
res_APDU[res_APDU_size++] = TAG_ALGO;
|
||||
res_APDU[res_APDU_size++] = 1;
|
||||
res_APDU[res_APDU_size++] = ALG_HMAC_SHA1;
|
||||
if (is_nk) {
|
||||
res_APDU[res_APDU_size++] = TAG_SERIAL_NUMBER;
|
||||
res_APDU[res_APDU_size++] = 8;
|
||||
memcpy(res_APDU + res_APDU_size, pico_serial_str, 8);
|
||||
res_APDU_size += 8;
|
||||
}
|
||||
apdu.ne = res_APDU_size;
|
||||
return PICOKEY_OK;
|
||||
}
|
||||
@@ -270,16 +282,27 @@ int cmd_list() {
|
||||
if (validated == false) {
|
||||
return SW_SECURITY_STATUS_NOT_SATISFIED();
|
||||
}
|
||||
bool ext = (apdu.nc == 1 && apdu.data[0] == 0x01);
|
||||
for (int i = 0; i < MAX_OATH_CRED; i++) {
|
||||
file_t *ef = search_dynamic_file((uint16_t)(EF_OATH_CRED + i));
|
||||
if (file_has_data(ef)) {
|
||||
asn1_ctx_t ctxi, key = { 0 }, name = { 0 };
|
||||
asn1_ctx_t ctxi, key = { 0 }, name = { 0 }, pws = { 0 };
|
||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == true && asn1_find_tag(&ctxi, TAG_KEY, &key) == true) {
|
||||
res_APDU[res_APDU_size++] = TAG_NAME_LIST;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(name.len + 1);
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(name.len + 1 + (ext ? 1 : 0));
|
||||
res_APDU[res_APDU_size++] = key.data[0];
|
||||
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
|
||||
if (ext) {
|
||||
uint8_t props = 0x0;
|
||||
if (asn1_find_tag(&ctxi, TAG_PWS_LOGIN, &pws) == true || asn1_find_tag(&ctxi, TAG_PWS_PASSWORD, &pws) == true || asn1_find_tag(&ctxi, TAG_PWS_METADATA, &pws) == true) {
|
||||
props |= 0x4;
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_PROPERTY, &pws) == true && (pws.data[0] & PROP_TOUCH)) {
|
||||
props |= 0x1;
|
||||
}
|
||||
res_APDU[res_APDU_size++] = props;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -615,7 +638,7 @@ int cmd_rename() {
|
||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
uint8_t *new_data = (uint8_t *) calloc(sizeof(uint8_t), fsize + new_name.len - name.len);
|
||||
uint8_t *new_data = (uint8_t *) calloc(fsize + new_name.len - name.len, sizeof(uint8_t));
|
||||
memcpy(new_data, fdata, name.data - fdata);
|
||||
*(new_data + (name.data - fdata) - 1) = new_name.len;
|
||||
memcpy(new_data + (name.data - fdata), new_name.data, new_name.len);
|
||||
@@ -626,6 +649,53 @@ int cmd_rename() {
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
int cmd_get_credential() {
|
||||
asn1_ctx_t ctxi, name = { 0 };
|
||||
if (apdu.nc < 3) {
|
||||
return SW_INCORRECT_PARAMS();
|
||||
}
|
||||
if (apdu.data[0] != TAG_NAME) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
asn1_ctx_init(apdu.data, (uint16_t)apdu.nc, &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == false) {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
file_t *ef = find_oath_cred(name.data, name.len);
|
||||
if (file_has_data(ef) == false) {
|
||||
return SW_DATA_INVALID();
|
||||
}
|
||||
asn1_ctx_t login = { 0 }, pw = { 0 }, meta = { 0 }, prop = { 0 };
|
||||
asn1_ctx_init(file_get_data(ef), file_get_size(ef), &ctxi);
|
||||
if (asn1_find_tag(&ctxi, TAG_NAME, &name) == true) {
|
||||
res_APDU[res_APDU_size++] = TAG_NAME;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(name.len);
|
||||
memcpy(res_APDU + res_APDU_size, name.data, name.len); res_APDU_size += name.len;
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_PWS_LOGIN, &login) == true) {
|
||||
res_APDU[res_APDU_size++] = TAG_PWS_LOGIN;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(login.len);
|
||||
memcpy(res_APDU + res_APDU_size, login.data, login.len); res_APDU_size += login.len;
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_PWS_PASSWORD, &pw) == true) {
|
||||
res_APDU[res_APDU_size++] = TAG_PWS_PASSWORD;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(pw.len);
|
||||
memcpy(res_APDU + res_APDU_size, pw.data, pw.len); res_APDU_size += pw.len;
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_PWS_METADATA, &meta) == true) {
|
||||
res_APDU[res_APDU_size++] = TAG_PWS_METADATA;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(meta.len);
|
||||
memcpy(res_APDU + res_APDU_size, meta.data, meta.len); res_APDU_size += meta.len;
|
||||
}
|
||||
if (asn1_find_tag(&ctxi, TAG_PROPERTY, &prop) == true) {
|
||||
res_APDU[res_APDU_size++] = TAG_PROPERTY;
|
||||
res_APDU[res_APDU_size++] = (uint8_t)(prop.len);
|
||||
memcpy(res_APDU + res_APDU_size, prop.data, prop.len); res_APDU_size += prop.len;
|
||||
}
|
||||
apdu.ne = res_APDU_size;
|
||||
return SW_OK();
|
||||
}
|
||||
|
||||
#define INS_PUT 0x01
|
||||
#define INS_DELETE 0x02
|
||||
#define INS_SET_CODE 0x03
|
||||
@@ -640,6 +710,7 @@ int cmd_rename() {
|
||||
#define INS_VERIFY_PIN 0xb2
|
||||
#define INS_CHANGE_PIN 0xb3
|
||||
#define INS_SET_PIN 0xb4
|
||||
#define INS_GET_CREDENTIAL 0xb5
|
||||
|
||||
static const cmd_t cmds[] = {
|
||||
{ INS_PUT, cmd_put },
|
||||
@@ -656,6 +727,7 @@ static const cmd_t cmds[] = {
|
||||
{ INS_CHANGE_PIN, cmd_change_otp_pin },
|
||||
{ INS_VERIFY_PIN, cmd_verify_otp_pin },
|
||||
{ INS_VERIFY_CODE, cmd_verify_hotp },
|
||||
{ INS_GET_CREDENTIAL, cmd_get_credential },
|
||||
{ 0x00, 0x0 }
|
||||
};
|
||||
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "fido.h"
|
||||
#include "pico_keys.h"
|
||||
#include "fido.h"
|
||||
#include "apdu.h"
|
||||
#include "files.h"
|
||||
#include "random.h"
|
||||
@@ -24,14 +24,17 @@
|
||||
#include "asn1.h"
|
||||
#include "hid/ctap_hid.h"
|
||||
#include "usb.h"
|
||||
#if !defined(ENABLE_EMULATION) && !defined(ESP_PLATFORM)
|
||||
#if defined(PICO_PLATFORM)
|
||||
#include "bsp/board.h"
|
||||
#endif
|
||||
#ifdef ENABLE_EMULATION
|
||||
void add_keyboard_buffer(const uint8_t *buf, size_t len, bool press_enter) {}
|
||||
void append_keyboard_buffer(const uint8_t *buf, size_t len) {}
|
||||
#else
|
||||
#include "tusb.h"
|
||||
#endif
|
||||
#include "mbedtls/aes.h"
|
||||
#include "management.h"
|
||||
#ifndef ENABLE_EMULATION
|
||||
#include "tusb.h"
|
||||
#endif
|
||||
|
||||
#define FIXED_SIZE 16
|
||||
#define KEY_SIZE 16
|
||||
@@ -116,12 +119,10 @@ uint16_t otp_status(bool is_otp);
|
||||
int otp_process_apdu();
|
||||
int otp_unload();
|
||||
|
||||
#ifndef ENABLE_EMULATION
|
||||
extern int (*hid_set_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
|
||||
extern uint16_t (*hid_get_report_cb)(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);
|
||||
int otp_hid_set_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t const *, uint16_t);
|
||||
uint16_t otp_hid_get_report_cb(uint8_t, uint8_t, hid_report_type_t, uint8_t *, uint16_t);
|
||||
#endif
|
||||
|
||||
const uint8_t otp_aid[] = {
|
||||
7,
|
||||
@@ -200,15 +201,12 @@ uint16_t calculate_crc(const uint8_t *data, size_t data_len) {
|
||||
return crc & 0xFFFF;
|
||||
}
|
||||
|
||||
#ifndef ENABLE_EMULATION
|
||||
static uint8_t session_counter[2] = { 0 };
|
||||
#endif
|
||||
int otp_button_pressed(uint8_t slot) {
|
||||
init_otp();
|
||||
if (!cap_supported(CAP_OTP)) {
|
||||
return 3;
|
||||
}
|
||||
#ifndef ENABLE_EMULATION
|
||||
file_t *ef = search_dynamic_file(slot == 1 ? EF_OTP_SLOT1 : EF_OTP_SLOT2);
|
||||
const uint8_t *data = file_get_data(ef);
|
||||
otp_config_t *otp_config = (otp_config_t *) data;
|
||||
@@ -218,6 +216,7 @@ int otp_button_pressed(uint8_t slot) {
|
||||
if (otp_config->cfg_flags & CHAL_YUBICO && otp_config->tkt_flags & CHAL_RESP) {
|
||||
return 2;
|
||||
}
|
||||
#ifdef ENABLE_OATH_APP
|
||||
if (otp_config->tkt_flags & OATH_HOTP) {
|
||||
uint8_t tmp_key[KEY_SIZE + 2];
|
||||
tmp_key[0] = 0x01;
|
||||
@@ -259,6 +258,7 @@ int otp_button_pressed(uint8_t slot) {
|
||||
append_keyboard_buffer((const uint8_t *) "\r", 1);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if (otp_config->cfg_flags & SHORT_TICKET || otp_config->cfg_flags & STATIC_TICKET) {
|
||||
uint8_t fixed_size = FIXED_SIZE + UID_SIZE + KEY_SIZE;
|
||||
if (otp_config->cfg_flags & SHORT_TICKET) { // Not clear which is the purpose of SHORT_TICKET
|
||||
@@ -317,19 +317,15 @@ int otp_button_pressed(uint8_t slot) {
|
||||
low_flash_available();
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void) slot;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
INITIALIZER( otp_ctor ) {
|
||||
register_app(otp_select, otp_aid);
|
||||
button_pressed_cb = otp_button_pressed;
|
||||
#ifndef ENABLE_EMULATION
|
||||
hid_set_report_cb = otp_hid_set_report_cb;
|
||||
hid_get_report_cb = otp_hid_get_report_cb;
|
||||
#endif
|
||||
}
|
||||
|
||||
int otp_unload() {
|
||||
@@ -490,20 +486,20 @@ int cmd_otp() {
|
||||
return SW_WRONG_DATA();
|
||||
}
|
||||
int ret = 0;
|
||||
#ifndef ENABLE_EMULATION
|
||||
uint8_t *rdata_bk = apdu.rdata;
|
||||
if (otp_config->cfg_flags & CHAL_BTN_TRIG) {
|
||||
status_byte = 0x20;
|
||||
otp_status(_is_otp);
|
||||
#ifndef ENABLE_EMULATION
|
||||
if (wait_button() == true) {
|
||||
status_byte = 0x00;
|
||||
otp_status(_is_otp);
|
||||
return SW_CONDITIONS_NOT_SATISFIED();
|
||||
}
|
||||
#endif
|
||||
status_byte = 0x10;
|
||||
apdu.rdata = rdata_bk;
|
||||
}
|
||||
#endif
|
||||
if (p1 == 0x30 || p1 == 0x38) {
|
||||
if (!(otp_config->cfg_flags & CHAL_HMAC)) {
|
||||
return SW_WRONG_DATA();
|
||||
@@ -568,8 +564,6 @@ int otp_process_apdu() {
|
||||
return SW_INS_NOT_SUPPORTED();
|
||||
}
|
||||
|
||||
#ifndef ENABLE_EMULATION
|
||||
|
||||
uint8_t otp_frame_rx[70] = {0};
|
||||
uint8_t otp_frame_tx[70] = {0};
|
||||
uint8_t otp_exp_seq = 0, otp_curr_seq = 0;
|
||||
@@ -671,5 +665,3 @@ uint16_t otp_hid_get_report_cb(uint8_t itf,
|
||||
|
||||
return reqlen;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
@@ -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")
|
||||
|
||||
32
tests/docker/bookworm/Dockerfile
Normal file
32
tests/docker/bookworm/Dockerfile
Normal file
@@ -0,0 +1,32 @@
|
||||
FROM debian:bookworm
|
||||
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt update && apt upgrade -y
|
||||
RUN apt install -y apt-utils
|
||||
RUN apt install -y libccid \
|
||||
libpcsclite-dev \
|
||||
git \
|
||||
autoconf \
|
||||
pkg-config \
|
||||
libtool \
|
||||
help2man \
|
||||
automake \
|
||||
gcc \
|
||||
make \
|
||||
build-essential \
|
||||
opensc \
|
||||
python3 \
|
||||
python3-pip \
|
||||
swig \
|
||||
cmake \
|
||||
libfuse-dev \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
RUN pip3 install pytest pycvc cryptography pyscard inputimeout fido2==2.0.0 --break-system-packages
|
||||
WORKDIR /
|
||||
RUN git clone https://github.com/frankmorgner/vsmartcard.git
|
||||
WORKDIR /vsmartcard/virtualsmartcard
|
||||
RUN autoreconf --verbose --install
|
||||
RUN ./configure --sysconfdir=/etc
|
||||
RUN make && make install
|
||||
WORKDIR /
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}"
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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"}
|
||||
]
|
||||
)
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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']
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user