Compare commits
97 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e0b0bfe38 | ||
|
|
9ac5200792 | ||
|
|
7542fad31c | ||
|
|
fe95093484 | ||
|
|
483a0931a6 | ||
|
|
6644b500fa | ||
|
|
c8775ec69f | ||
|
|
9160bbb8fe | ||
|
|
19dd52f944 | ||
|
|
4c724d0e8b | ||
|
|
81d3da2645 | ||
|
|
9bcfacfe08 | ||
|
|
641c2fb880 | ||
|
|
cb24927a80 | ||
|
|
4ddd45f16e | ||
|
|
f39a51afca | ||
|
|
aa4255b875 | ||
|
|
a4d82136c2 | ||
|
|
4a3f957fdf | ||
|
|
24b66dcffc | ||
|
|
703e4697ec | ||
|
|
839562130a | ||
|
|
e87ae34ab5 | ||
|
|
2431812a18 | ||
|
|
81717135f5 | ||
|
|
1d1d8ce5c3 | ||
|
|
6030f33977 | ||
|
|
360b8eadaa | ||
|
|
0d51d3c727 | ||
|
|
d0924f5ecc | ||
|
|
866d69a82d | ||
|
|
00ba0db87a | ||
|
|
c5644d14b0 | ||
|
|
2cf211cbd0 | ||
|
|
50418113a9 | ||
|
|
3a92238c0c | ||
|
|
270a54f3b7 | ||
|
|
a8364c281b | ||
|
|
5dcf89cd66 | ||
|
|
5c7be811e8 | ||
|
|
1707430593 | ||
|
|
a151dc72e4 | ||
|
|
315f01372e | ||
|
|
860cca53e0 | ||
|
|
d7016f6065 | ||
|
|
8e9eafaec5 | ||
|
|
1376c51528 | ||
|
|
fa5926a3cc | ||
|
|
710e03f5a6 | ||
|
|
9d79505c5a | ||
|
|
4cb0af5045 | ||
|
|
196430517f | ||
|
|
083f9bc787 | ||
|
|
eb66ec3064 | ||
|
|
e5834ff7c4 | ||
|
|
3f1aba889e | ||
|
|
58fbea8929 | ||
|
|
8bf53a6497 | ||
|
|
c89b044825 | ||
|
|
004073c3dd | ||
|
|
bae8450a8d | ||
|
|
a355f87f82 | ||
|
|
b023668788 | ||
|
|
1b6d1e4b7f | ||
|
|
3bea6adf7a | ||
|
|
54c2df3570 | ||
|
|
ae42e28384 | ||
|
|
71c0e865dc | ||
|
|
052ff2d60a | ||
|
|
8b70c864a4 | ||
|
|
765db0e98b | ||
|
|
6b2e95deb0 | ||
|
|
d45fa9aae0 | ||
|
|
23c7e16e6e | ||
|
|
5923f435fe | ||
|
|
04868f2d7b | ||
|
|
54c0769dbd | ||
|
|
0bbcba2f60 | ||
|
|
723648173d | ||
|
|
e6c128fe0d | ||
|
|
2174b516c3 | ||
|
|
4577e4430c | ||
|
|
9a8f4c0f4d | ||
|
|
e21d985344 | ||
|
|
43cd8869f9 | ||
|
|
a42131876f | ||
|
|
e1f4e3035d | ||
|
|
71ecb23af6 | ||
|
|
8c21a2bbcd | ||
|
|
53cc16ab6d | ||
|
|
f213854f8b | ||
|
|
2c125e76eb | ||
|
|
19d8f16056 | ||
|
|
40065217fd | ||
|
|
32c938674a | ||
|
|
4425722a71 | ||
|
|
69eef7651c |
@@ -62,6 +62,9 @@ target_sources(pico_fido PUBLIC
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_get_assertion.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_selection.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_cred_mgmt.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_config.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_vendor.c
|
||||
${CMAKE_CURRENT_LIST_DIR}/src/fido/cbor_large_blobs.c
|
||||
)
|
||||
set(HSM_DRIVER "hid")
|
||||
include(pico-hsm-sdk/pico_hsm_sdk_import.cmake)
|
||||
|
||||
17
README.md
17
README.md
@@ -16,6 +16,17 @@ Pico FIDO has implemented the following features:
|
||||
- ECDSA authentication
|
||||
- App registration and login
|
||||
- Device selection
|
||||
- Support for vendor Config
|
||||
- Backup with 24 words
|
||||
- Secure lock to protect the device from flash dumpings
|
||||
- Permissions support (MC, GA, CM, ACFG, LBW)
|
||||
- Authenticator configuration
|
||||
- minPinLength extension
|
||||
- Self attestation
|
||||
- Enterprise attestation
|
||||
- credBlobs extension
|
||||
- largeBlobKey extension
|
||||
- largeBlobs support (2048 bytes máx.)
|
||||
|
||||
All these features are compliant with the specification. Therefore, if you detect some behaviour that is not expected or it does not follow the rules of specs, please open an issue.
|
||||
|
||||
@@ -27,10 +38,6 @@ If the Pico is stolen the contents of private and secret keys can be read.
|
||||
## Download
|
||||
Please, go to the [Release page](https://github.com/polhenarejos/pico-fido/releases "Release page") and download the UF2 file for your board.
|
||||
|
||||
Note that UF2 files are shiped with a dummy VID/PID to avoid license issues (FEFF:FCFD). If you are planning to use it with OpenSC or similar, you should modify Info.plist of CCID driver to add these VID/PID or use the VID/PID patcher as follows: `./pico-fido-patch-vidpid.sh VID:PID input_fido_file.uf2 output_fido_file.uf2`
|
||||
|
||||
You can use whatever VID/PID, but remember that you are not authorized to distribute the binary with a VID/PID that you do not own.
|
||||
|
||||
## Build
|
||||
Before building, ensure you have installed the toolchain for the Pico and the Pico SDK is properly located in your drive.
|
||||
|
||||
@@ -45,6 +52,8 @@ Note that PICO_BOARD, USB_VID and USB_PID are optional. If not provided, pico bo
|
||||
|
||||
After make ends, the binary file pico_fido.uf2 will be generated. Put your pico board into loading mode, by pushing BOOTSEL button while pluging on, and copy the UF2 to the new fresh usb mass storage Pico device. Once copied, the pico mass storage will be disconnected automatically and the pico board will reset with the new firmware. A blinking led will indicate the device is ready to work.
|
||||
|
||||
**Remark:** Pico Fido uses HID interface and thus, VID/PID values are irrelevant in terms of operativity. You can safely use any arbitrary value or the default ones.
|
||||
|
||||
## Led blink
|
||||
Pico FIDO uses the led to indicate the current status. Four states are available:
|
||||
### Press to confirm
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
VERSION_MAJOR="2"
|
||||
VERSION_MINOR="2"
|
||||
VERSION_MINOR="10"
|
||||
|
||||
rm -rf release/*
|
||||
cd build_release
|
||||
|
||||
Submodule pico-hsm-sdk updated: 8f14db677e...fa54da973c
@@ -27,7 +27,6 @@
|
||||
|
||||
const bool _btrue = true, _bfalse = false;
|
||||
|
||||
extern int cbor_process(const uint8_t *data, size_t len);
|
||||
int cbor_reset();
|
||||
int cbor_get_info();
|
||||
int cbor_make_credential(const uint8_t *data, size_t len);
|
||||
@@ -36,33 +35,46 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next);
|
||||
int cbor_get_next_assertion(const uint8_t *data, size_t len);
|
||||
int cbor_selection();
|
||||
int cbor_cred_mgmt(const uint8_t *data, size_t len);
|
||||
int cbor_config(const uint8_t *data, size_t len);
|
||||
int cbor_vendor(const uint8_t *data, size_t len);
|
||||
int cbor_large_blobs(const uint8_t *data, size_t len);
|
||||
|
||||
const uint8_t aaguid[16] = {0x89, 0xFB, 0x94, 0xB7, 0x06, 0xC9, 0x36, 0x73, 0x9B, 0x7E, 0x30, 0x52, 0x6D, 0x96, 0x81, 0x45}; // First 16 bytes of SHA256("Pico FIDO2")
|
||||
|
||||
const uint8_t *cbor_data = NULL;
|
||||
size_t cbor_len = 0;
|
||||
static const uint8_t *cbor_data = NULL;
|
||||
static size_t cbor_len = 0;
|
||||
static uint8_t cmd = 0;
|
||||
|
||||
int cbor_parse(const uint8_t *data, size_t len) {
|
||||
int cbor_parse(uint8_t cmd, const uint8_t *data, size_t len) {
|
||||
if (len == 0)
|
||||
return CTAP1_ERR_INVALID_LEN;
|
||||
DEBUG_DATA(data+1,len-1);
|
||||
driver_prepare_response();
|
||||
if (data[0] == CTAP_MAKE_CREDENTIAL)
|
||||
return cbor_make_credential(data + 1, len - 1);
|
||||
if (data[0] == CTAP_GET_INFO)
|
||||
return cbor_get_info();
|
||||
else if (data[0] == CTAP_RESET)
|
||||
return cbor_reset();
|
||||
else if (data[0] == CTAP_CLIENT_PIN)
|
||||
return cbor_client_pin(data + 1, len - 1);
|
||||
else if (data[0] == CTAP_GET_ASSERTION)
|
||||
return cbor_get_assertion(data + 1, len - 1, false);
|
||||
else if (data[0] == CTAP_GET_NEXT_ASSERTION)
|
||||
return cbor_get_next_assertion(data + 1, len - 1);
|
||||
else if (data[0] == CTAP_SELECTION)
|
||||
return cbor_selection();
|
||||
else if (data[0] == CTAP_CREDENTIAL_MGMT)
|
||||
return cbor_cred_mgmt(data + 1, len - 1);
|
||||
if (cmd == CTAPHID_CBOR) {
|
||||
if (data[0] == CTAP_MAKE_CREDENTIAL)
|
||||
return cbor_make_credential(data + 1, len - 1);
|
||||
if (data[0] == CTAP_GET_INFO)
|
||||
return cbor_get_info();
|
||||
else if (data[0] == CTAP_RESET)
|
||||
return cbor_reset();
|
||||
else if (data[0] == CTAP_CLIENT_PIN)
|
||||
return cbor_client_pin(data + 1, len - 1);
|
||||
else if (data[0] == CTAP_GET_ASSERTION)
|
||||
return cbor_get_assertion(data + 1, len - 1, false);
|
||||
else if (data[0] == CTAP_GET_NEXT_ASSERTION)
|
||||
return cbor_get_next_assertion(data + 1, len - 1);
|
||||
else if (data[0] == CTAP_SELECTION)
|
||||
return cbor_selection();
|
||||
else if (data[0] == CTAP_CREDENTIAL_MGMT || data[0] == 0x41)
|
||||
return cbor_cred_mgmt(data + 1, len - 1);
|
||||
else if (data[0] == CTAP_CONFIG)
|
||||
return cbor_config(data + 1, len - 1);
|
||||
else if (data[0] == CTAP_LARGE_BLOBS)
|
||||
return cbor_large_blobs(data + 1, len - 1);
|
||||
}
|
||||
else if (cmd == CTAP_VENDOR_CBOR) {
|
||||
return cbor_vendor(data, len);
|
||||
}
|
||||
return CTAP2_ERR_INVALID_CBOR;
|
||||
}
|
||||
|
||||
@@ -78,7 +90,7 @@ void cbor_thread() {
|
||||
break;
|
||||
}
|
||||
|
||||
apdu.sw = cbor_parse(cbor_data, cbor_len);
|
||||
apdu.sw = cbor_parse(cmd, cbor_data, cbor_len);
|
||||
if (apdu.sw == 0)
|
||||
DEBUG_DATA(res_APDU + 1, res_APDU_size);
|
||||
|
||||
@@ -88,9 +100,10 @@ void cbor_thread() {
|
||||
}
|
||||
}
|
||||
|
||||
int cbor_process(const uint8_t *data, size_t len) {
|
||||
int cbor_process(uint8_t last_cmd, const uint8_t *data, size_t len) {
|
||||
cbor_data = data;
|
||||
cbor_len = len;
|
||||
cmd = last_cmd;
|
||||
res_APDU = ctap_resp->init.data + 1;
|
||||
res_APDU_size = 0;
|
||||
return 1;
|
||||
|
||||
@@ -31,12 +31,11 @@
|
||||
#include "hsm.h"
|
||||
#include "apdu.h"
|
||||
|
||||
uint8_t permissions_rp_id = 0, permission_set = 0;
|
||||
uint32_t usage_timer = 0, initial_usage_time_limit = 0;
|
||||
uint32_t max_usage_time_period = 600*1000;
|
||||
bool needs_power_cycle = false;
|
||||
mbedtls_ecdh_context hkey;
|
||||
bool hkey_init = false;
|
||||
static mbedtls_ecdh_context hkey;
|
||||
static bool hkey_init = false;
|
||||
|
||||
int beginUsingPinUvAuthToken(bool userIsPresent) {
|
||||
paut.user_present = userIsPresent;
|
||||
@@ -59,15 +58,15 @@ void clearUserVerifiedFlag() {
|
||||
|
||||
void clearPinUvAuthTokenPermissionsExceptLbw() {
|
||||
if (paut.in_use == true)
|
||||
paut.permissions = FIDO2_PERMISSION_LBW;
|
||||
paut.permissions = CTAP_PERMISSION_LBW;
|
||||
}
|
||||
|
||||
void stopUsingPinUvAuthToken() {
|
||||
permissions_rp_id = 0;
|
||||
paut.permissions = 0;
|
||||
usage_timer = 0;
|
||||
paut.in_use = false;
|
||||
memset(paut.rp_id_hash, 0, sizeof(paut.rp_id_hash));
|
||||
paut.has_rp_id = false;
|
||||
initial_usage_time_limit = 0;
|
||||
paut.user_present = paut.user_verified = false;
|
||||
user_present_time_limit = 0;
|
||||
@@ -369,12 +368,17 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
uint8_t pin_len = 0;
|
||||
while (paddedNewPin[pin_len] != 0 && pin_len < sizeof(paddedNewPin))
|
||||
pin_len++;
|
||||
if (pin_len < 4)
|
||||
uint8_t minPin = 4;
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_minpin))
|
||||
minPin = *file_get_data(ef_minpin);
|
||||
if (pin_len < minPin)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
uint8_t hsh[33];
|
||||
uint8_t hsh[34];
|
||||
hsh[0] = MAX_PIN_RETRIES;
|
||||
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 1);
|
||||
flash_write_data_to_file(ef_pin, hsh, 1+16);
|
||||
hsh[1] = pin_len;
|
||||
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 2);
|
||||
flash_write_data_to_file(ef_pin, hsh, 2+16);
|
||||
low_flash_available();
|
||||
goto err; //No return
|
||||
}
|
||||
@@ -408,16 +412,19 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
uint8_t retries = *file_get_data(ef_pin) - 1;
|
||||
flash_write_data_to_file(ef_pin, &retries, 1);
|
||||
uint8_t pin_data[18];
|
||||
memcpy(pin_data, file_get_data(ef_pin), 18);
|
||||
pin_data[0] -= 1;
|
||||
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
|
||||
low_flash_available();
|
||||
uint8_t retries = pin_data[0];
|
||||
uint8_t paddedNewPin[64];
|
||||
ret = decrypt(pinUvAuthProtocol, sharedSecret, pinHashEnc.data, pinHashEnc.len, paddedNewPin);
|
||||
if (ret != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
low_flash_available();
|
||||
if (memcmp(paddedNewPin, file_get_data(ef_pin)+1, 16) != 0) {
|
||||
if (memcmp(paddedNewPin, file_get_data(ef_pin)+2, 16) != 0) {
|
||||
regenerate();
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
if (retries == 0) {
|
||||
@@ -430,9 +437,10 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
else
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
retries = MAX_PIN_RETRIES;
|
||||
pin_data[0] = MAX_PIN_RETRIES;
|
||||
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
|
||||
low_flash_available();
|
||||
new_pin_mismatches = 0;
|
||||
flash_write_data_to_file(ef_pin, &retries, 1);
|
||||
ret = decrypt(pinUvAuthProtocol, sharedSecret, newPinEnc.data, newPinEnc.len, paddedNewPin);
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
if (ret != 0) {
|
||||
@@ -443,23 +451,44 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
uint8_t pin_len = 0;
|
||||
while (paddedNewPin[pin_len] != 0 && pin_len < sizeof(paddedNewPin))
|
||||
pin_len++;
|
||||
if (pin_len < 4)
|
||||
uint8_t minPin = 4;
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_minpin))
|
||||
minPin = *file_get_data(ef_minpin);
|
||||
if (pin_len < minPin)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
uint8_t hsh[33];
|
||||
hsh[0] = MAX_PIN_RETRIES;
|
||||
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 1);
|
||||
flash_write_data_to_file(ef_pin, hsh, 1+16);
|
||||
hsh[1] = pin_len;
|
||||
mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), paddedNewPin, pin_len, hsh + 2);
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1 && memcmp(hsh+2, file_get_data(ef_pin)+2, 16) == 0)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
flash_write_data_to_file(ef_pin, hsh, 2+16);
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1) {
|
||||
uint8_t *tmp = (uint8_t *)calloc(1, file_get_size(ef_minpin));
|
||||
memcpy(tmp, file_get_data(ef_minpin), file_get_size(ef_minpin));
|
||||
tmp[1] = 0;
|
||||
flash_write_data_to_file(ef_minpin, tmp, file_get_size(ef_minpin));
|
||||
free(tmp);
|
||||
}
|
||||
low_flash_available();
|
||||
resetPinUvAuthToken();
|
||||
goto err; // No return
|
||||
}
|
||||
else if (subcommand == 0x9 || subcommand == 0x5) { //getUVRgetPinUvAuthTokenUsingPinWithPermissionsetries
|
||||
else if (subcommand == 0x9 || subcommand == 0x5) { //getPinUvAuthTokenUsingPinWithPermissions
|
||||
if (kax.present == false || kay.present == false || pinUvAuthProtocol == 0 || alg == 0 || pinHashEnc.present == false)
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (pinUvAuthProtocol != 1 && pinUvAuthProtocol != 2)
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
if ((subcommand == 0x9 && permissions == 0) || (subcommand == 0x5 && (permissions != 0 || rpId.present == true)))
|
||||
if (subcommand == 0x5 && (permissions != 0 || rpId.present == true))
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
if (subcommand == 0x9) {
|
||||
if (permissions == 0)
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
if ((permissions & CTAP_PERMISSION_BE)) // Not supported yet
|
||||
CBOR_ERROR(CTAP2_ERR_UNAUTHORIZED_PERMISSION);
|
||||
|
||||
}
|
||||
if (!file_has_data(ef_pin))
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
|
||||
if (*file_get_data(ef_pin) == 0)
|
||||
@@ -476,16 +505,19 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
uint8_t retries = *file_get_data(ef_pin) - 1;
|
||||
flash_write_data_to_file(ef_pin, &retries, 1);
|
||||
uint8_t pin_data[18];
|
||||
memcpy(pin_data, file_get_data(ef_pin), 18);
|
||||
pin_data[0] -= 1;
|
||||
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
|
||||
low_flash_available();
|
||||
uint8_t retries = pin_data[0];
|
||||
uint8_t paddedNewPin[64], poff = (pinUvAuthProtocol-1)*IV_SIZE;
|
||||
ret = decrypt(pinUvAuthProtocol, sharedSecret, pinHashEnc.data, pinHashEnc.len, paddedNewPin);
|
||||
if (ret != 0) {
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
}
|
||||
low_flash_available();
|
||||
if (memcmp(paddedNewPin, file_get_data(ef_pin)+1, 16) != 0) {
|
||||
if (memcmp(paddedNewPin, file_get_data(ef_pin)+2, 16) != 0) {
|
||||
regenerate();
|
||||
mbedtls_platform_zeroize(sharedSecret, sizeof(sharedSecret));
|
||||
if (retries == 0) {
|
||||
@@ -498,17 +530,25 @@ int cbor_client_pin(const uint8_t *data, size_t len) {
|
||||
else
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
}
|
||||
retries = MAX_PIN_RETRIES;
|
||||
pin_data[0] = MAX_PIN_RETRIES;
|
||||
new_pin_mismatches = 0;
|
||||
flash_write_data_to_file(ef_pin, &retries, 1);
|
||||
flash_write_data_to_file(ef_pin, pin_data, sizeof(pin_data));
|
||||
low_flash_available();
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_INVALID);
|
||||
resetPinUvAuthToken();
|
||||
beginUsingPinUvAuthToken(false);
|
||||
if (subcommand == 0x05)
|
||||
permissions = CTAP_PERMISSION_MC | CTAP_PERMISSION_GA;
|
||||
paut.permissions = permissions;
|
||||
if (rpId.present == true)
|
||||
memcpy(paut.rp_id_hash, rpId.data, 32);
|
||||
if (rpId.present == true) {
|
||||
mbedtls_sha256((uint8_t *)rpId.data, rpId.len, paut.rp_id_hash, 0);
|
||||
paut.has_rp_id = true;
|
||||
}
|
||||
else
|
||||
memset(paut.rp_id_hash, 0, sizeof(paut.rp_id_hash));
|
||||
uint8_t pinUvAuthToken_enc[32+IV_SIZE];
|
||||
paut.has_rp_id = false;
|
||||
uint8_t pinUvAuthToken_enc[32 + IV_SIZE];
|
||||
encrypt(pinUvAuthProtocol, sharedSecret, paut.data, 32, pinUvAuthToken_enc);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
||||
|
||||
214
src/fido/cbor_config.c
Normal file
214
src/fido/cbor_config.c
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "bsp/board.h"
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "credential.h"
|
||||
#include "hsm.h"
|
||||
#include "random.h"
|
||||
#include "mbedtls/ecdh.h"
|
||||
#include "mbedtls/chachapoly.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
|
||||
extern uint8_t keydev_dec[32];
|
||||
extern bool has_keydev_dec;
|
||||
|
||||
int cbor_config(const uint8_t *data, size_t len) {
|
||||
CborParser parser;
|
||||
CborValue map;
|
||||
CborError error = CborNoError;
|
||||
uint64_t subcommand = 0, pinUvAuthProtocol = 0, vendorCommandId = 0, newMinPinLength = 0;
|
||||
CborByteString pinUvAuthParam = {0}, vendorAutCt = {0};
|
||||
CborCharString minPinLengthRPIDs[32] = {0};
|
||||
size_t resp_size = 0, raw_subpara_len = 0, minPinLengthRPIDs_len = 0;
|
||||
CborEncoder encoder, mapEncoder;
|
||||
uint8_t *raw_subpara = NULL;
|
||||
const bool *forceChangePin = NULL;
|
||||
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
CBOR_PARSE_MAP_START(map, 1) {
|
||||
uint64_t val_u = 0;
|
||||
CBOR_FIELD_GET_UINT(val_u, 1);
|
||||
if (val_c <= 1 && val_c != val_u)
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (val_u < val_c)
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
|
||||
val_c = val_u + 1;
|
||||
if (val_u == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(subcommand, 1);
|
||||
}
|
||||
else if (val_u == 0x02) {
|
||||
uint64_t subpara = 0;
|
||||
raw_subpara = (uint8_t *)cbor_value_get_next_byte(&_f1);
|
||||
CBOR_PARSE_MAP_START(_f1, 2) {
|
||||
if (subcommand == 0xff) {
|
||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||
if (subpara == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(vendorCommandId, 2);
|
||||
}
|
||||
else if (subpara == 0x02) {
|
||||
CBOR_FIELD_GET_BYTES(vendorAutCt, 2);
|
||||
}
|
||||
}
|
||||
else if (subcommand == 0x03) {
|
||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||
if (subpara == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(newMinPinLength, 2);
|
||||
}
|
||||
else if (subpara == 0x02) {
|
||||
CBOR_PARSE_ARRAY_START(_f2, 3) {
|
||||
CBOR_FIELD_GET_TEXT(minPinLengthRPIDs[minPinLengthRPIDs_len], 3);
|
||||
minPinLengthRPIDs_len++;
|
||||
if (minPinLengthRPIDs_len >= 32)
|
||||
CBOR_ERROR(CTAP2_ERR_KEY_STORE_FULL);
|
||||
}
|
||||
CBOR_PARSE_ARRAY_END(_f2, 3);
|
||||
}
|
||||
else if (subpara == 0x03) {
|
||||
CBOR_FIELD_GET_BOOL(forceChangePin, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
raw_subpara_len = cbor_value_get_next_byte(&_f1) - raw_subpara;
|
||||
}
|
||||
else if (val_u == 0x03) {
|
||||
CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1);
|
||||
}
|
||||
else if (val_u == 0x04) {
|
||||
CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(map, 1);
|
||||
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
|
||||
|
||||
if (pinUvAuthParam.present == false)
|
||||
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
|
||||
if (pinUvAuthProtocol == 0)
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
|
||||
uint8_t *verify_payload = (uint8_t *)calloc(1, 32 + 1 + 1 + raw_subpara_len);
|
||||
memset(verify_payload, 0xff, 32);
|
||||
verify_payload[32] = 0x0d;
|
||||
verify_payload[33] = subcommand;
|
||||
memcpy(verify_payload + 34, raw_subpara, raw_subpara_len);
|
||||
error = verify(pinUvAuthProtocol, paut.data, verify_payload, 32 + 1 + 1 + raw_subpara_len, pinUvAuthParam.data);
|
||||
free(verify_payload);
|
||||
if (error != CborNoError)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
|
||||
if (!(paut.permissions & CTAP_PERMISSION_ACFG))
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
|
||||
if (subcommand == 0xff) {
|
||||
if (vendorCommandId == CTAP_CONFIG_AUT_DISABLE) {
|
||||
if (!file_has_data(ef_keydev_enc))
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
if (has_keydev_dec == false)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
flash_write_data_to_file(ef_keydev, keydev_dec, sizeof(keydev_dec));
|
||||
mbedtls_platform_zeroize(keydev_dec, sizeof(keydev_dec));
|
||||
flash_write_data_to_file(ef_keydev_enc, NULL, 0); // Set ef to 0 bytes
|
||||
low_flash_available();
|
||||
}
|
||||
else if (vendorCommandId == CTAP_CONFIG_AUT_ENABLE) {
|
||||
if (!file_has_data(ef_keydev))
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
if (mse.init == false)
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
|
||||
mbedtls_chachapoly_context chatx;
|
||||
int ret = mse_decrypt_ct(vendorAutCt.data, vendorAutCt.len);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
uint8_t key_dev_enc[12+32+16];
|
||||
random_gen(NULL, key_dev_enc, 12);
|
||||
mbedtls_chachapoly_init(&chatx);
|
||||
mbedtls_chachapoly_setkey(&chatx, vendorAutCt.data);
|
||||
ret = mbedtls_chachapoly_encrypt_and_tag(&chatx, file_get_size(ef_keydev), key_dev_enc, NULL, 0, file_get_data(ef_keydev), key_dev_enc + 12, key_dev_enc + 12 + file_get_size(ef_keydev));
|
||||
mbedtls_chachapoly_free(&chatx);
|
||||
if (ret != 0){
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
flash_write_data_to_file(ef_keydev_enc, key_dev_enc, sizeof(key_dev_enc));
|
||||
mbedtls_platform_zeroize(key_dev_enc, sizeof(key_dev_enc));
|
||||
flash_write_data_to_file(ef_keydev, key_dev_enc, file_get_size(ef_keydev)); // Overwrite ef with 0
|
||||
flash_write_data_to_file(ef_keydev, NULL, 0); // Set ef to 0 bytes
|
||||
low_flash_available();
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
|
||||
}
|
||||
goto err;
|
||||
}
|
||||
else if (subcommand == 0x03) {
|
||||
uint8_t currentMinPinLen = 4;
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_minpin))
|
||||
currentMinPinLen = *file_get_data(ef_minpin);
|
||||
if (newMinPinLength == 0)
|
||||
newMinPinLength = currentMinPinLen;
|
||||
else if (newMinPinLength > 0 && newMinPinLength < currentMinPinLen)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_POLICY_VIOLATION);
|
||||
if (forceChangePin == ptrue && !file_has_data(ef_pin))
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_NOT_SET);
|
||||
if (file_has_data(ef_pin) && file_get_data(ef_pin)[1] < newMinPinLength)
|
||||
forceChangePin = ptrue;
|
||||
uint8_t *data = (uint8_t *)calloc(1, 2 + minPinLengthRPIDs_len * 32);
|
||||
data[0] = newMinPinLength;
|
||||
data[1] = forceChangePin == ptrue ? 1 : 0;
|
||||
for (int m = 0; m < minPinLengthRPIDs_len; m++) {
|
||||
mbedtls_sha256((uint8_t *)minPinLengthRPIDs[m].data, minPinLengthRPIDs[m].len, data + 2 + m*32, 0);
|
||||
}
|
||||
flash_write_data_to_file(ef_minpin, data, 2 + minPinLengthRPIDs_len * 32);
|
||||
low_flash_available();
|
||||
goto err; //No return
|
||||
}
|
||||
else if (subcommand == 0x01) {
|
||||
set_opts(get_opts() | FIDO2_OPT_EA);
|
||||
goto err;
|
||||
}
|
||||
else
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
||||
|
||||
err:
|
||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||
CBOR_FREE_BYTE_STRING(vendorAutCt);
|
||||
for (int i = 0; i < minPinLengthRPIDs_len; i++) {
|
||||
CBOR_FREE_BYTE_STRING(minPinLengthRPIDs[i]);
|
||||
}
|
||||
|
||||
if (error != CborNoError) {
|
||||
if (error == CborErrorImproperValue)
|
||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||
return error;
|
||||
}
|
||||
res_APDU_size = resp_size;
|
||||
return 0;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
/*
|
||||
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
@@ -44,7 +43,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
CborEncoder encoder, mapEncoder, mapEncoder2;
|
||||
uint8_t *raw_subpara = NULL;
|
||||
size_t raw_subpara_len = 0;
|
||||
bool asserted = false;
|
||||
bool asserted = false, is_preview = *(data - 1) == 0x41; // Backwards compatibility
|
||||
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
@@ -116,6 +115,8 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
if(subcommand == 0x01) {
|
||||
if (verify(pinUvAuthProtocol, paut.data, (const uint8_t *)"\x01", 1, pinUvAuthParam.data) != CborNoError)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (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++) {
|
||||
if (file_has_data(search_dynamic_file(EF_CRED + i)))
|
||||
@@ -132,6 +133,8 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
if (subcommand == 0x02) {
|
||||
if (verify(pinUvAuthProtocol, paut.data, (const uint8_t *)"\x02", 1, pinUvAuthParam.data) != CborNoError)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (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;
|
||||
}
|
||||
@@ -153,14 +156,14 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
rp_total++;
|
||||
}
|
||||
}
|
||||
if (rp_ef == NULL) // should not happen
|
||||
CBOR_ERROR(CTAP2_ERR_OPERATION_DENIED);
|
||||
if (rp_ef == NULL)
|
||||
CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS);
|
||||
rp_counter++;
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, subcommand == 0x02 ? 3 : 2));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 1));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id"));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, file_get_data(rp_ef)+33, file_get_size(rp_ef)-33));
|
||||
CBOR_CHECK(cbor_encode_text_string(&mapEncoder2, (char *)file_get_data(rp_ef)+33, file_get_size(rp_ef)-33));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(rp_ef)+1, 32));
|
||||
@@ -176,6 +179,8 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
*(raw_subpara-1) = 0x04;
|
||||
if (verify(pinUvAuthProtocol, paut.data, raw_subpara-1, raw_subpara_len+1, pinUvAuthParam.data) != CborNoError)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (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;
|
||||
}
|
||||
@@ -216,10 +221,20 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
}
|
||||
|
||||
cred_counter++;
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, subcommand == 0x04 ? 5 : 4));
|
||||
|
||||
uint8_t l = 4;
|
||||
if (subcommand == 0x04)
|
||||
l++;
|
||||
if (cred.extensions.present == true) {
|
||||
if (cred.extensions.credProtect > 0)
|
||||
l++;
|
||||
if (cred.extensions.largeBlobKey == ptrue)
|
||||
l++;
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x06));
|
||||
uint8_t l = 0;
|
||||
l = 0;
|
||||
if (cred.userId.present == true)
|
||||
l++;
|
||||
if (cred.userName.present == true)
|
||||
@@ -254,7 +269,7 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 2));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 3));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, cred.alg));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -cred.alg));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, cred.curve));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 2));
|
||||
@@ -274,8 +289,22 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
asserted = true;
|
||||
rpIdHashx = rpIdHash;
|
||||
}
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, cred.extensions.credProtect));
|
||||
if (cred.extensions.present == true) {
|
||||
if (cred.extensions.credProtect > 0) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, cred.extensions.credProtect));
|
||||
}
|
||||
if (cred.extensions.largeBlobKey == ptrue) {
|
||||
uint8_t largeBlobKey[32];
|
||||
int ret = credential_derive_large_blob_key(cred.id.data, cred.id.len, largeBlobKey);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey, sizeof(largeBlobKey)));
|
||||
mbedtls_platform_zeroize(largeBlobKey, sizeof(largeBlobKey));
|
||||
}
|
||||
}
|
||||
credential_free(&cred);
|
||||
mbedtls_ecdsa_free(&key);
|
||||
}
|
||||
@@ -285,6 +314,8 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
*(raw_subpara - 1) = 0x06;
|
||||
if (verify(pinUvAuthProtocol, paut.data, raw_subpara-1, raw_subpara_len+1, pinUvAuthParam.data) != CborNoError)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (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);
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
file_t *ef = search_dynamic_file(EF_CRED + i);
|
||||
if (file_has_data(ef) && memcmp(file_get_data(ef)+32, credentialId.id.data, MIN(file_get_size(ef)-32, credentialId.id.len)) == 0) {
|
||||
@@ -317,6 +348,8 @@ int cbor_cred_mgmt(const uint8_t *data, size_t len) {
|
||||
*(raw_subpara - 1) = 0x07;
|
||||
if (verify(pinUvAuthProtocol, paut.data, raw_subpara-1, raw_subpara_len+1, pinUvAuthParam.data) != CborNoError)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (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);
|
||||
for (int i = 0; i < MAX_RESIDENT_CREDENTIALS; i++) {
|
||||
file_t *ef = search_dynamic_file(EF_CRED + i);
|
||||
if (file_has_data(ef) && memcmp(file_get_data(ef)+32, credentialId.id.data, MIN(file_get_size(ef)-32, credentialId.id.len)) == 0) {
|
||||
|
||||
@@ -85,9 +85,10 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
Credential creds[MAX_CREDENTIAL_COUNT_IN_LIST] = {0};
|
||||
size_t allowList_len = 0, creds_len = 0;
|
||||
uint8_t *aut_data = NULL;
|
||||
bool asserted = false;
|
||||
bool asserted = false, up = true, uv = false;
|
||||
int64_t kty = 2, alg = 0, crv = 0;
|
||||
CborByteString kax = {0}, kay = {0}, salt_enc = {0}, salt_auth = {0};
|
||||
const bool *credBlob = NULL;
|
||||
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
@@ -173,7 +174,8 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
CBOR_PARSE_MAP_END(_f2, 3);
|
||||
continue;
|
||||
}
|
||||
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "credBlob", credBlob);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey);
|
||||
CBOR_ADVANCE(2);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
@@ -237,6 +239,10 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
}
|
||||
//else if (options.up == NULL) //5.7
|
||||
//rup = ptrue;
|
||||
if (options.uv != NULL)
|
||||
uv = *options.uv;
|
||||
if (options.up != NULL)
|
||||
up = *options.up;
|
||||
}
|
||||
|
||||
if (pinUvAuthParam.present == true) { //6.1
|
||||
@@ -245,6 +251,10 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (getUserVerifiedFlagValue() == false)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (!(paut.permissions & CTAP_PERMISSION_GA))
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (paut.has_rp_id == true && memcmp(paut.rp_id_hash, rp_id_hash, 32) != 0)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
flags |= FIDO2_AUT_FLAG_UV;
|
||||
// Check pinUvAuthToken permissions. See 6.2.2.4
|
||||
}
|
||||
@@ -328,7 +338,11 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
clearPinUvAuthTokenPermissionsExceptLbw();
|
||||
}
|
||||
|
||||
if (!(flags & FIDO2_AUT_FLAG_UP) && !(flags & FIDO2_AUT_FLAG_UV)) {
|
||||
if (extensions.largeBlobKey == pfalse) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
||||
}
|
||||
|
||||
if (up == false && uv == false) {
|
||||
selcred = &creds[0];
|
||||
}
|
||||
else {
|
||||
@@ -364,6 +378,14 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t largeBlobKey[32];
|
||||
if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) {
|
||||
ret = credential_derive_large_blob_key(selcred->id.data, selcred->id.len, largeBlobKey);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
}
|
||||
|
||||
size_t ext_len = 0;
|
||||
uint8_t ext [512];
|
||||
if (extensions.present == true) {
|
||||
@@ -373,12 +395,15 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
extensions.hmac_secret = NULL;
|
||||
if (extensions.hmac_secret != NULL)
|
||||
l++;
|
||||
if (extensions.credProtect != 0)
|
||||
if (credBlob == ptrue)
|
||||
l++;
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
||||
if (extensions.credProtect != 0) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect"));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
|
||||
if (credBlob == ptrue) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob"));
|
||||
if (selcred->extensions.credBlob.present == true)
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, selcred->extensions.credBlob.data, selcred->extensions.credBlob.len));
|
||||
else
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, NULL, 0));
|
||||
}
|
||||
if (extensions.hmac_secret != NULL) {
|
||||
|
||||
@@ -460,7 +485,9 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
uint8_t lfields = 3;
|
||||
if (selcred->opts.present == true && selcred->opts.rk == ptrue)
|
||||
lfields++;
|
||||
if (numberOfCredentials > 1 && next == false && !(flags & FIDO2_AUT_FLAG_UP) && !(flags & FIDO2_AUT_FLAG_UV))
|
||||
if (numberOfCredentials > 1 && next == false)
|
||||
lfields++;
|
||||
if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue)
|
||||
lfields++;
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, lfields));
|
||||
@@ -502,10 +529,15 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) {
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
}
|
||||
if (numberOfCredentials > 1 && next == false && !(flags & FIDO2_AUT_FLAG_UP) && !(flags & FIDO2_AUT_FLAG_UV)) {
|
||||
if (numberOfCredentials > 1 && next == false) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, numberOfCredentials));
|
||||
}
|
||||
if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey, sizeof(largeBlobKey)));
|
||||
}
|
||||
mbedtls_platform_zeroize(largeBlobKey, sizeof(largeBlobKey));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
||||
ctr++;
|
||||
|
||||
@@ -23,10 +23,10 @@
|
||||
#include "version.h"
|
||||
|
||||
int cbor_get_info() {
|
||||
CborEncoder encoder, mapEncoder, arrayEncoder;
|
||||
CborEncoder encoder, mapEncoder, arrayEncoder, mapEncoder2;
|
||||
CborError error = CborNoError;
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 9));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 15));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3));
|
||||
@@ -36,16 +36,21 @@ int cbor_get_info() {
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 5));
|
||||
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_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, aaguid, sizeof(aaguid)));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &arrayEncoder, 5));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &arrayEncoder, 8));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "ep"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, get_opts() & FIDO2_OPT_EA));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "rk"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "credMgmt"));
|
||||
@@ -57,10 +62,17 @@ int cbor_get_info() {
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||
else
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, false));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "largeBlobs"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "pinUvAuthToken"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&arrayEncoder, "setMinPINLength"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&arrayEncoder, true));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_MSG_SIZE));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x06));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, 1)); // PIN protocols
|
||||
@@ -73,12 +85,55 @@ int cbor_get_info() {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x08));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CRED_ID_LENGTH)); // MAX_CRED_ID_MAX_LENGTH
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 3));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&arrayEncoder, &mapEncoder2, 2));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg"));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -FIDO2_ALG_ES256));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&arrayEncoder, &mapEncoder2));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&arrayEncoder, &mapEncoder2, 2));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg"));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -FIDO2_ALG_ES384));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&arrayEncoder, &mapEncoder2));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&arrayEncoder, &mapEncoder2, 2));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "alg"));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -FIDO2_ALG_ES512));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type"));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key"));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&arrayEncoder, &mapEncoder2));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_LARGE_BLOB_SIZE)); // maxSerializedLargeBlobArray
|
||||
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C));
|
||||
if (file_has_data(ef_minpin) && file_get_data(ef_minpin)[1] == 1)
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, true));
|
||||
else
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, false));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0D));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 4)); // minPINLength
|
||||
if (file_has_data(ef_minpin))
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, *file_get_data(ef_minpin))); // minPINLength
|
||||
else
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 4)); // minPINLength
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0E));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, PICO_FIDO_VERSION)); // firmwareVersion
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0F));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, MAX_CREDBLOB_LENGTH)); // maxCredBlobLength
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x15));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder, &arrayEncoder, 2));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_ENABLE));
|
||||
CBOR_CHECK(cbor_encode_uint(&arrayEncoder, CTAP_CONFIG_AUT_DISABLE));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &arrayEncoder));
|
||||
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
err:
|
||||
if (error != CborNoError)
|
||||
|
||||
150
src/fido/cbor_large_blobs.c
Normal file
150
src/fido/cbor_large_blobs.c
Normal file
@@ -0,0 +1,150 @@
|
||||
/*
|
||||
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "ctap2_cbor.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "version.h"
|
||||
#include "hsm.h"
|
||||
#include "mbedtls/sha256.h"
|
||||
|
||||
static uint64_t expectedLength = 0, expectedNextOffset = 0;
|
||||
uint8_t temp_lba[MAX_LARGE_BLOB_SIZE];
|
||||
|
||||
int cbor_large_blobs(const uint8_t *data, size_t len) {
|
||||
CborParser parser;
|
||||
CborValue map;
|
||||
CborEncoder encoder, mapEncoder;
|
||||
CborError error = CborNoError;
|
||||
uint64_t get = 0, offset = UINT64_MAX, length = 0, pinUvAuthProtocol = 0;
|
||||
CborByteString set = {0}, pinUvAuthParam = {0};
|
||||
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
CBOR_PARSE_MAP_START(map, 1) {
|
||||
uint64_t val_u = 0;
|
||||
CBOR_FIELD_GET_UINT(val_u, 1);
|
||||
if (val_c <= 0 && val_c != val_u)
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (val_u < val_c)
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
|
||||
val_c = val_u + 1;
|
||||
if (val_u == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(get, 1);
|
||||
}
|
||||
else if (val_u == 0x02) {
|
||||
CBOR_FIELD_GET_BYTES(set, 1);
|
||||
}
|
||||
else if (val_u == 0x03) {
|
||||
CBOR_FIELD_GET_UINT(offset, 1);
|
||||
}
|
||||
else if (val_u == 0x04) {
|
||||
CBOR_FIELD_GET_UINT(length, 1);
|
||||
}
|
||||
else if (val_u == 0x05) {
|
||||
CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1);
|
||||
}
|
||||
else if (val_u == 0x06) {
|
||||
CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(map, 1);
|
||||
|
||||
if (offset == UINT64_MAX)
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
if (get == 0 && set.present == false)
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
if (get != 0 && set.present == true)
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
|
||||
if (get > 0) {
|
||||
if (length != 0)
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
if (length > MAX_FRAGMENT_LENGTH)
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_LEN);
|
||||
if (offset > file_get_size(ef_largeblob))
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_largeblob)+offset, MIN(get, file_get_size(ef_largeblob)-offset)));
|
||||
}
|
||||
else {
|
||||
if (set.len > MAX_FRAGMENT_LENGTH)
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_LEN);
|
||||
if (offset == 0) {
|
||||
if (length == 0)
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
if (length > MAX_LARGE_BLOB_SIZE) {
|
||||
CBOR_ERROR(CTAP2_ERR_LARGE_BLOB_STORAGE_FULL);
|
||||
}
|
||||
if (length < 17) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
expectedLength = length;
|
||||
expectedNextOffset = 0;
|
||||
}
|
||||
else {
|
||||
if (length != 0)
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (offset != expectedNextOffset)
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_SEQ);
|
||||
if (pinUvAuthParam.present == false)
|
||||
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
|
||||
if (pinUvAuthProtocol == 0)
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
uint8_t verify_data[70] = {0};
|
||||
memset(verify_data, 0xff, 32);
|
||||
verify_data[32] = 0x0C;
|
||||
verify_data[34] = offset & 0xff;
|
||||
verify_data[35] = offset >> 8;
|
||||
verify_data[36] = offset >> 16;
|
||||
verify_data[37] = offset >> 24;
|
||||
mbedtls_sha256(set.data, set.len, verify_data+38, 0);
|
||||
if (verify(pinUvAuthProtocol, paut.data, verify_data, sizeof(verify_data), pinUvAuthParam.data) != 0)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (!(paut.permissions & CTAP_PERMISSION_LBW))
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (offset+set.len > expectedLength)
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
if (offset == 0)
|
||||
memset(temp_lba, 0, sizeof(temp_lba));
|
||||
memcpy(temp_lba+expectedNextOffset, set.data, set.len);
|
||||
expectedNextOffset += set.len;
|
||||
if (expectedNextOffset == expectedLength) {
|
||||
uint8_t sha[32];
|
||||
mbedtls_sha256(temp_lba, expectedLength-16, sha, 0);
|
||||
if (expectedLength > 17 && memcmp(sha, temp_lba+expectedLength-16, 16) != 0)
|
||||
CBOR_ERROR(CTAP2_ERR_INTEGRITY_FAILURE);
|
||||
flash_write_data_to_file(ef_largeblob, temp_lba, expectedLength);
|
||||
low_flash_available();
|
||||
}
|
||||
goto err;
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
|
||||
err:
|
||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||
CBOR_FREE_BYTE_STRING(set);
|
||||
if (error != CborNoError)
|
||||
return -CTAP2_ERR_INVALID_CBOR;
|
||||
res_APDU_size = cbor_encoder_get_buffer_size(&encoder, res_APDU + 1);
|
||||
return 0;
|
||||
}
|
||||
@@ -118,6 +118,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_FIELD_GET_KEY_TEXT(2);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", extensions.hmac_secret);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", extensions.credProtect);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "minPinLength", extensions.minPinLength);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", extensions.credBlob);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", extensions.largeBlobKey);
|
||||
CBOR_ADVANCE(2);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
@@ -206,6 +209,9 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_ERROR(CTAP2_ERR_PUAT_REQUIRED);
|
||||
}
|
||||
if (enterpriseAttestation > 0) {
|
||||
if (!(get_opts() & FIDO2_OPT_EA)) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (enterpriseAttestation != 1 && enterpriseAttestation != 2) { //9.2.1
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
||||
}
|
||||
@@ -215,10 +221,17 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
int ret = verify(pinUvAuthProtocol, paut.data, clientDataHash.data, clientDataHash.len, pinUvAuthParam.data);
|
||||
if (ret != CborNoError)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (!(paut.permissions & CTAP_PERMISSION_MC))
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (paut.has_rp_id == true && memcmp(paut.rp_id_hash, rp_id_hash, 32) != 0)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
if (getUserVerifiedFlagValue() == false)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
flags |= FIDO2_AUT_FLAG_UV;
|
||||
// Check pinUvAuthToken permissions. See 6.1.2.11
|
||||
if (paut.has_rp_id == false) {
|
||||
memcpy(paut.rp_id_hash, rp_id_hash, 32);
|
||||
paut.has_rp_id = true;
|
||||
}
|
||||
}
|
||||
|
||||
for (int e = 0; e < excludeList_len; e++) { //12.1
|
||||
@@ -227,10 +240,14 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
if (strcmp(excludeList[e].type.data, "public-key") != 0)
|
||||
continue;
|
||||
Credential ecred;
|
||||
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_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)))
|
||||
CBOR_ERROR(CTAP2_ERR_CREDENTIAL_EXCLUDED);
|
||||
}
|
||||
|
||||
if (extensions.largeBlobKey == pfalse || (extensions.largeBlobKey == ptrue && options.rk != ptrue)) {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_OPTION);
|
||||
}
|
||||
|
||||
if (options.up == ptrue || options.up == NULL) { //14.1
|
||||
if (pinUvAuthParam.present == true) {
|
||||
if (getUserPresentFlagValue() == false) {
|
||||
@@ -251,14 +268,6 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
|
||||
CBOR_CHECK(credential_create(&rp.id, &user.id, &user.parent.name, &user.displayName, &options, &extensions, (!ka || ka->use_sign_count == ptrue), alg, curve, cred_id, &cred_id_len));
|
||||
|
||||
mbedtls_ecdsa_context ekey;
|
||||
mbedtls_ecdsa_init(&ekey);
|
||||
int ret = fido_load_key(curve, cred_id, &ekey);
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||
}
|
||||
|
||||
if (getUserVerifiedFlagValue())
|
||||
flags |= FIDO2_AUT_FLAG_UV;
|
||||
size_t ext_len = 0;
|
||||
@@ -267,11 +276,32 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
if (extensions.present == true) {
|
||||
cbor_encoder_init(&encoder, ext, sizeof(ext), 0);
|
||||
int l = 0;
|
||||
uint8_t minPinLen = 0;
|
||||
if (extensions.hmac_secret != NULL)
|
||||
l++;
|
||||
if (extensions.credProtect != 0)
|
||||
l++;
|
||||
if (extensions.minPinLength != NULL) {
|
||||
file_t *ef_minpin = search_by_fid(EF_MINPINLEN, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef_minpin)) {
|
||||
uint8_t *minpin_data = file_get_data(ef_minpin);
|
||||
for (int o = 2; o < file_get_size(ef_minpin); o += 32) {
|
||||
if (memcmp(minpin_data + o, rp_id_hash, 32) == 0) {
|
||||
minPinLen = minpin_data[0];
|
||||
if (minPinLen > 0)
|
||||
l++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (extensions.credBlob.present == true)
|
||||
l++;
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, l));
|
||||
if (extensions.credBlob.present == true) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credBlob"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, extensions.credBlob.len < MAX_CREDBLOB_LENGTH));
|
||||
}
|
||||
if (extensions.credProtect != 0) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "credProtect"));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, extensions.credProtect));
|
||||
@@ -281,15 +311,29 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "hmac-secret"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, *extensions.hmac_secret));
|
||||
}
|
||||
if (minPinLen > 0) {
|
||||
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "minPinLength"));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, minPinLen));
|
||||
}
|
||||
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
ext_len = cbor_encoder_get_buffer_size(&encoder, ext);
|
||||
flags |= FIDO2_AUT_FLAG_ED;
|
||||
}
|
||||
uint8_t pkey[66];
|
||||
const mbedtls_ecp_curve_info *cinfo = mbedtls_ecp_curve_info_from_grp_id(ekey.grp.id);
|
||||
if (cinfo == NULL)
|
||||
mbedtls_ecdsa_context ekey;
|
||||
mbedtls_ecdsa_init(&ekey);
|
||||
int ret = fido_load_key(curve, cred_id, &ekey);
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||
}
|
||||
const mbedtls_ecp_curve_info *cinfo = mbedtls_ecp_curve_info_from_grp_id(ekey.grp.id);
|
||||
if (cinfo == NULL) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||
}
|
||||
size_t olen = 0;
|
||||
uint32_t ctr = get_sign_counter();
|
||||
uint8_t cbor_buf[1024];
|
||||
@@ -326,15 +370,17 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
memcpy(pa, cred_id, cred_id_len); pa += cred_id_len;
|
||||
memcpy(pa, cbor_buf, rs); pa += rs;
|
||||
memcpy(pa, ext, ext_len); pa += ext_len;
|
||||
if (pa-aut_data != aut_data_len)
|
||||
if (pa-aut_data != aut_data_len) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP1_ERR_OTHER);
|
||||
}
|
||||
|
||||
memcpy(pa, clientDataHash.data, clientDataHash.len);
|
||||
uint8_t hash[32], sig[MBEDTLS_ECDSA_MAX_LEN];
|
||||
ret = mbedtls_md(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), aut_data, aut_data_len+clientDataHash.len, hash);
|
||||
|
||||
bool self_attestation = true;
|
||||
if (ka && ka->use_self_attestation == pfalse) {
|
||||
if (enterpriseAttestation == 2 || (ka && ka->use_self_attestation == pfalse)) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
mbedtls_ecdsa_init(&ekey);
|
||||
ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, file_get_data(ef_keydev), 32);
|
||||
@@ -343,8 +389,16 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
ret = mbedtls_ecdsa_write_signature(&ekey, MBEDTLS_MD_SHA256, hash, 32, sig, sizeof(sig), &olen, random_gen, NULL);
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
|
||||
uint8_t largeBlobKey[32];
|
||||
if (extensions.largeBlobKey == ptrue && options.rk == ptrue) {
|
||||
ret = credential_derive_large_blob_key(cred_id, cred_id_len, largeBlobKey);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
}
|
||||
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 3));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, extensions.largeBlobKey == ptrue && options.rk == ptrue ? 5 : 4));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder, "packed"));
|
||||
@@ -359,13 +413,26 @@ int cbor_make_credential(const uint8_t *data, size_t len) {
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, sig, olen));
|
||||
if (self_attestation == false) {
|
||||
CborEncoder arrEncoder;
|
||||
file_t *ef_cert = NULL;
|
||||
if (enterpriseAttestation == 2)
|
||||
ef_cert = search_by_fid(EF_EE_DEV_EA, NULL, SPECIFY_EF);
|
||||
if (!file_has_data(ef_cert))
|
||||
ef_cert = ef_certdev;
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "x5c"));
|
||||
CBOR_CHECK(cbor_encoder_create_array(&mapEncoder2, &arrEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&arrEncoder, file_get_data(ef_certdev), file_get_size(ef_certdev)));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&arrEncoder, file_get_data(ef_cert), file_get_size(ef_cert)));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder2, &arrEncoder));
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder, enterpriseAttestation == 2));
|
||||
|
||||
if (extensions.largeBlobKey == ptrue && options.rk == ptrue) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey, sizeof(largeBlobKey)));
|
||||
}
|
||||
mbedtls_platform_zeroize(largeBlobKey, sizeof(largeBlobKey));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
||||
|
||||
|
||||
302
src/fido/cbor_vendor.c
Normal file
302
src/fido/cbor_vendor.c
Normal file
@@ -0,0 +1,302 @@
|
||||
/*
|
||||
* This file is part of the Pico FIDO distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "common.h"
|
||||
#include "ctap2_cbor.h"
|
||||
#include "fido.h"
|
||||
#include "ctap.h"
|
||||
#include "files.h"
|
||||
#include "apdu.h"
|
||||
#include "hsm.h"
|
||||
#include "random.h"
|
||||
#include "mbedtls/ecdh.h"
|
||||
#include "mbedtls/chachapoly.h"
|
||||
#include "mbedtls/hkdf.h"
|
||||
#include "mbedtls/x509_csr.h"
|
||||
|
||||
extern uint8_t keydev_dec[32];
|
||||
extern bool has_keydev_dec;
|
||||
|
||||
mse_t mse = {.init = false};
|
||||
|
||||
int mse_decrypt_ct(uint8_t *data, size_t len) {
|
||||
mbedtls_chachapoly_context chatx;
|
||||
mbedtls_chachapoly_init(&chatx);
|
||||
mbedtls_chachapoly_setkey(&chatx, mse.key_enc + 12);
|
||||
int ret = mbedtls_chachapoly_auth_decrypt(&chatx, len - 16, mse.key_enc, mse.Qpt, 65, data + len - 16, data, data);
|
||||
mbedtls_chachapoly_free(&chatx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) {
|
||||
CborParser parser;
|
||||
CborValue map;
|
||||
CborError error = CborNoError;
|
||||
CborByteString pinUvAuthParam = {0}, vendorParam = {0}, kax = {0}, kay = {0};
|
||||
size_t resp_size = 0;
|
||||
uint64_t vendorCmd = 0, pinUvAuthProtocol = 0;
|
||||
int64_t kty = 0, alg = 0, crv = 0;
|
||||
CborEncoder encoder, mapEncoder, mapEncoder2;
|
||||
|
||||
CBOR_CHECK(cbor_parser_init(data, len, 0, &parser, &map));
|
||||
uint64_t val_c = 1;
|
||||
CBOR_PARSE_MAP_START(map, 1) {
|
||||
uint64_t val_u = 0;
|
||||
CBOR_FIELD_GET_UINT(val_u, 1);
|
||||
if (val_c <= 1 && val_c != val_u)
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
if (val_u < val_c)
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_CBOR);
|
||||
val_c = val_u + 1;
|
||||
if (val_u == 0x01) {
|
||||
CBOR_FIELD_GET_UINT(vendorCmd, 1);
|
||||
}
|
||||
else if (val_u == 0x02) {
|
||||
uint64_t subpara = 0;
|
||||
CBOR_PARSE_MAP_START(_f1, 2) {
|
||||
CBOR_FIELD_GET_UINT(subpara, 2);
|
||||
if (subpara == 0x01) {
|
||||
CBOR_FIELD_GET_BYTES(vendorParam, 2);
|
||||
}
|
||||
else if (subpara == 0x02) {
|
||||
int64_t key = 0;
|
||||
CBOR_PARSE_MAP_START(_f2, 3) {
|
||||
CBOR_FIELD_GET_INT(key, 3);
|
||||
if (key == 1) {
|
||||
CBOR_FIELD_GET_INT(kty, 3);
|
||||
}
|
||||
else if (key == 3) {
|
||||
CBOR_FIELD_GET_INT(alg, 3);
|
||||
}
|
||||
else if (key == -1) {
|
||||
CBOR_FIELD_GET_INT(crv, 3);
|
||||
}
|
||||
else if (key == -2) {
|
||||
CBOR_FIELD_GET_BYTES(kax, 3);
|
||||
}
|
||||
else if (key == -3) {
|
||||
CBOR_FIELD_GET_BYTES(kay, 3);
|
||||
}
|
||||
else
|
||||
CBOR_ADVANCE(3);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f2, 3);
|
||||
}
|
||||
else
|
||||
CBOR_ADVANCE(2);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
}
|
||||
else if (val_u == 0x03) {
|
||||
CBOR_FIELD_GET_UINT(pinUvAuthProtocol, 1);
|
||||
}
|
||||
else if (val_u == 0x04) {
|
||||
CBOR_FIELD_GET_BYTES(pinUvAuthParam, 1);
|
||||
}
|
||||
}
|
||||
CBOR_PARSE_MAP_END(map, 1);
|
||||
|
||||
cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_PACKET_SIZE, 0);
|
||||
|
||||
if (cmd == CTAP_VENDOR_BACKUP) {
|
||||
if (vendorCmd == 0x01) {
|
||||
if (has_keydev_dec == false)
|
||||
CBOR_ERROR(CTAP2_ERR_PIN_AUTH_INVALID);
|
||||
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, file_get_data(ef_keydev_enc), file_get_size(ef_keydev_enc)));
|
||||
}
|
||||
else if (vendorCmd == 0x02) {
|
||||
if (vendorParam.present == false)
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
uint8_t zeros[32];
|
||||
memset(zeros, 0, sizeof(zeros));
|
||||
flash_write_data_to_file(ef_keydev_enc, vendorParam.data, vendorParam.len);
|
||||
flash_write_data_to_file(ef_keydev, zeros, file_get_size(ef_keydev)); // Overwrite ef with 0
|
||||
flash_write_data_to_file(ef_keydev, NULL, 0); // Set ef to 0 bytes
|
||||
low_flash_available();
|
||||
goto err;
|
||||
}
|
||||
else {
|
||||
CBOR_ERROR(CTAP2_ERR_INVALID_SUBCOMMAND);
|
||||
}
|
||||
}
|
||||
else if (cmd == CTAP_VENDOR_MSE) {
|
||||
if (vendorCmd == 0x01) { // KeyAgreement
|
||||
if (kax.present == false || kay.present == false || alg == 0)
|
||||
CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER);
|
||||
|
||||
mbedtls_ecdh_context hkey;
|
||||
mbedtls_ecdh_init(&hkey);
|
||||
mbedtls_ecdh_setup(&hkey, MBEDTLS_ECP_DP_SECP256R1);
|
||||
int ret = mbedtls_ecdh_gen_public(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.d, &hkey.ctx.mbed_ecdh.Q, random_gen, NULL);
|
||||
mbedtls_mpi_lset(&hkey.ctx.mbed_ecdh.Qp.Z, 1);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.X, kax.data, kax.len) != 0) {
|
||||
mbedtls_ecdh_free(&hkey);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
if (mbedtls_mpi_read_binary(&hkey.ctx.mbed_ecdh.Qp.Y, kay.data, kay.len) != 0) {
|
||||
mbedtls_ecdh_free(&hkey);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
uint8_t buf[MBEDTLS_ECP_MAX_BYTES];
|
||||
size_t olen = 0;
|
||||
ret = mbedtls_ecp_point_write_binary(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.Qp, MBEDTLS_ECP_PF_UNCOMPRESSED, &olen, mse.Qpt, sizeof(mse.Qpt));
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdh_free(&hkey);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
ret = mbedtls_ecdh_calc_secret(&hkey, &olen, buf, MBEDTLS_ECP_MAX_BYTES, random_gen, NULL);
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdh_free(&hkey);
|
||||
mbedtls_platform_zeroize(buf, sizeof(buf));
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
ret = mbedtls_hkdf(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), NULL, 0, buf, olen, mse.Qpt, sizeof(mse.Qpt), mse.key_enc, sizeof(mse.key_enc));
|
||||
mbedtls_platform_zeroize(buf, sizeof(buf));
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdh_free(&hkey);
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
mse.init = true;
|
||||
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01));
|
||||
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 5));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 2));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, 3));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, -FIDO2_ALG_ECDH_ES_HKDF_256));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 1));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, FIDO2_CURVE_P256));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 2));
|
||||
uint8_t pkey[32];
|
||||
mbedtls_mpi_write_binary(&hkey.ctx.mbed_ecdh.Q.X, pkey, 32);
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, 32));
|
||||
CBOR_CHECK(cbor_encode_negative_int(&mapEncoder2, 3));
|
||||
mbedtls_mpi_write_binary(&hkey.ctx.mbed_ecdh.Q.Y, pkey, 32);
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, pkey, 32));
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
mbedtls_ecdh_free(&hkey);
|
||||
}
|
||||
}
|
||||
else if (cmd == CTAP_VENDOR_UNLOCK) {
|
||||
if (mse.init == false)
|
||||
CBOR_ERROR(CTAP2_ERR_NOT_ALLOWED);
|
||||
|
||||
mbedtls_chachapoly_context chatx;
|
||||
int ret = mse_decrypt_ct(vendorParam.data, vendorParam.len);
|
||||
if (ret != 0) {
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
|
||||
if (!file_has_data(ef_keydev_enc))
|
||||
CBOR_ERROR(CTAP2_ERR_INTEGRITY_FAILURE);
|
||||
|
||||
uint8_t *keyenc = file_get_data(ef_keydev_enc);
|
||||
size_t keyenc_len = file_get_size(ef_keydev_enc);
|
||||
mbedtls_chachapoly_init(&chatx);
|
||||
mbedtls_chachapoly_setkey(&chatx, vendorParam.data);
|
||||
ret = mbedtls_chachapoly_auth_decrypt(&chatx, sizeof(keydev_dec), keyenc, NULL, 0, keyenc + keyenc_len - 16, keyenc + 12, keydev_dec);
|
||||
mbedtls_chachapoly_free(&chatx);
|
||||
if (ret != 0){
|
||||
CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER);
|
||||
}
|
||||
has_keydev_dec = true;
|
||||
goto err;
|
||||
}
|
||||
else if (cmd == CTAP_VENDOR_EA) {
|
||||
if (vendorCmd == 0x01) {
|
||||
uint8_t buffer[1024];
|
||||
mbedtls_ecdsa_context ekey;
|
||||
mbedtls_ecdsa_init(&ekey);
|
||||
int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, file_get_data(ef_keydev), file_get_size(ef_keydev));
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
ret = mbedtls_ecp_mul(&ekey.grp, &ekey.Q, &ekey.d, &ekey.grp.G, random_gen, NULL);
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
pico_unique_board_id_t rpiid;
|
||||
pico_get_unique_board_id(&rpiid);
|
||||
mbedtls_x509write_csr ctx;
|
||||
mbedtls_x509write_csr_init(&ctx);
|
||||
snprintf((char *)buffer, sizeof(buffer), "C=ES,O=Pico Keys,OU=Authenticator Attestation,CN=Pico Fido EE Serial %llu", ((uint64_t)rpiid.id[0] << 56) | ((uint64_t)rpiid.id[1] << 48) | ((uint64_t)rpiid.id[2] << 40) | ((uint64_t)rpiid.id[3] << 32) | (rpiid.id[4] << 24) | (rpiid.id[5] << 16) | (rpiid.id[6] << 8) | rpiid.id[7]);
|
||||
mbedtls_x509write_csr_set_subject_name(&ctx, (char *)buffer);
|
||||
mbedtls_pk_context key;
|
||||
mbedtls_pk_init(&key);
|
||||
mbedtls_pk_setup(&key, mbedtls_pk_info_from_type(MBEDTLS_PK_ECKEY));
|
||||
key.pk_ctx = &ekey;
|
||||
mbedtls_x509write_csr_set_key(&ctx, &key);
|
||||
mbedtls_x509write_csr_set_md_alg(&ctx, MBEDTLS_MD_SHA256);
|
||||
mbedtls_x509write_csr_set_extension(&ctx, "\x2B\x06\x01\x04\x01\x82\xE5\x1C\x01\x01\x04", 0xB, 0, aaguid, sizeof(aaguid));
|
||||
ret = mbedtls_x509write_csr_der(&ctx, buffer, sizeof(buffer), random_gen, NULL);
|
||||
mbedtls_ecdsa_free(&ekey);
|
||||
if (ret <= 0) {
|
||||
mbedtls_x509write_csr_free(&ctx);
|
||||
CBOR_ERROR(CTAP2_ERR_PROCESSING);
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, 1));
|
||||
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)
|
||||
flash_write_data_to_file(ef_ee_ea, vendorParam.data, vendorParam.len);
|
||||
low_flash_available();
|
||||
goto err;
|
||||
}
|
||||
}
|
||||
else
|
||||
CBOR_ERROR(CTAP2_ERR_UNSUPPORTED_OPTION);
|
||||
CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder));
|
||||
resp_size = cbor_encoder_get_buffer_size(&encoder, ctap_resp->init.data + 1);
|
||||
|
||||
err:
|
||||
CBOR_FREE_BYTE_STRING(pinUvAuthParam);
|
||||
CBOR_FREE_BYTE_STRING(vendorParam);
|
||||
|
||||
if (error != CborNoError) {
|
||||
if (error == CborErrorImproperValue)
|
||||
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE;
|
||||
return error;
|
||||
}
|
||||
res_APDU_size = resp_size;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cbor_vendor(const uint8_t *data, size_t len) {
|
||||
if (len == 0)
|
||||
return CTAP1_ERR_INVALID_LEN;
|
||||
if (data[0] >= CTAP_VENDOR_BACKUP)
|
||||
return cbor_vendor_generic(data[0], data + 1, len - 1);
|
||||
return CTAP2_ERR_INVALID_CBOR;
|
||||
}
|
||||
@@ -38,7 +38,9 @@ int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id
|
||||
mbedtls_chachapoly_context chatx;
|
||||
mbedtls_chachapoly_init(&chatx);
|
||||
mbedtls_chachapoly_setkey(&chatx, key);
|
||||
return mbedtls_chachapoly_auth_decrypt(&chatx, cred_id_len - (4 + 12 + 16), iv, rp_id_hash, 32, tag, cipher, cipher);
|
||||
int ret = mbedtls_chachapoly_auth_decrypt(&chatx, cred_id_len - (4 + 12 + 16), iv, rp_id_hash, 32, tag, cipher, cipher);
|
||||
mbedtls_chachapoly_free(&chatx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int credential_create(CborCharString *rpId, CborByteString *userId, CborCharString *userName, CborCharString *userDisplayName, CredOptions *opts, CredExtensions *extensions, bool use_sign_count, int alg, int curve, uint8_t *cred_id, size_t *cred_id_len) {
|
||||
@@ -58,6 +60,10 @@ int credential_create(CborCharString *rpId, CborByteString *userId, CborCharStri
|
||||
if (extensions->present == true) {
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07));
|
||||
CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, CborIndefiniteLength));
|
||||
if (extensions->credBlob.present == true && extensions->credBlob.len < MAX_CREDBLOB_LENGTH) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "credBlob"));
|
||||
CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, extensions->credBlob.data, extensions->credBlob.len));
|
||||
}
|
||||
if (extensions->credProtect != 0) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "credProtect"));
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder2, extensions->credProtect));
|
||||
@@ -66,6 +72,10 @@ int credential_create(CborCharString *rpId, CborByteString *userId, CborCharStri
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "hmac-secret"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, *extensions->hmac_secret));
|
||||
}
|
||||
if (extensions->largeBlobKey == ptrue) {
|
||||
CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "largeBlobKey"));
|
||||
CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, true));
|
||||
}
|
||||
CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2));
|
||||
}
|
||||
CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x08));
|
||||
@@ -153,6 +163,8 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r
|
||||
CBOR_FIELD_GET_KEY_TEXT(2);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "hmac-secret", cred->extensions.hmac_secret);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_UINT(2, "credProtect", cred->extensions.credProtect);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BYTES(2, "credBlob", cred->extensions.credBlob);
|
||||
CBOR_FIELD_KEY_TEXT_VAL_BOOL(2, "largeBlobKey", cred->extensions.largeBlobKey);
|
||||
CBOR_ADVANCE(2);
|
||||
}
|
||||
CBOR_PARSE_MAP_END(_f1, 2);
|
||||
@@ -307,10 +319,24 @@ int credential_derive_chacha_key(uint8_t *outk) {
|
||||
int r = 0;
|
||||
if ((r = load_keydev(outk)) != 0)
|
||||
return r;
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512);
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"SLIP-0022", 9, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)CRED_PROTO, 4, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"Encryption key", 14, outk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int credential_derive_large_blob_key(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk) {
|
||||
memset(outk, 0, 32);
|
||||
int r = 0;
|
||||
if ((r = load_keydev(outk)) != 0)
|
||||
return r;
|
||||
const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256);
|
||||
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"SLIP-0022", 9, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)CRED_PROTO, 4, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, (uint8_t *)"largeBlobKey", 12, outk);
|
||||
mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -30,6 +30,9 @@ typedef struct CredOptions {
|
||||
typedef struct CredExtensions {
|
||||
const bool *hmac_secret;
|
||||
uint64_t credProtect;
|
||||
const bool *minPinLength;
|
||||
CborByteString credBlob;
|
||||
const bool *largeBlobKey;
|
||||
bool present;
|
||||
} CredExtensions;
|
||||
|
||||
@@ -61,5 +64,6 @@ 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, size_t cred_id_len, const uint8_t *rp_id_hash, Credential *cred);
|
||||
extern int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk);
|
||||
extern int credential_derive_large_blob_key(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk);
|
||||
|
||||
#endif // _CREDENTIAL_H_
|
||||
|
||||
@@ -114,6 +114,34 @@ typedef struct {
|
||||
#define CTAP_GET_NEXT_ASSERTION 0x08
|
||||
#define CTAP_CREDENTIAL_MGMT 0x0A
|
||||
#define CTAP_SELECTION 0x0B
|
||||
#define CTAP_LARGE_BLOBS 0x0C
|
||||
#define CTAP_CONFIG 0x0D
|
||||
|
||||
#define CTAP_CONFIG_AUT_ENABLE 0x03e43f56b34285e2
|
||||
#define CTAP_CONFIG_AUT_DISABLE 0x1831a40f04a25ed9
|
||||
|
||||
#define CTAP_VENDOR_CBOR (CTAPHID_VENDOR_FIRST + 1)
|
||||
|
||||
#define CTAP_VENDOR_BACKUP 0x01
|
||||
#define CTAP_VENDOR_MSE 0x02
|
||||
#define CTAP_VENDOR_UNLOCK 0x03
|
||||
#define CTAP_VENDOR_EA 0x04
|
||||
|
||||
#define CTAP_PERMISSION_MC 0x01 // MakeCredential
|
||||
#define CTAP_PERMISSION_GA 0x02 // GetAssertion
|
||||
#define CTAP_PERMISSION_CM 0x04 // CredentialManagement
|
||||
#define CTAP_PERMISSION_BE 0x08 // BioEnrollment
|
||||
#define CTAP_PERMISSION_LBW 0x10 // LargeBlobWrite
|
||||
#define CTAP_PERMISSION_ACFG 0x20 // AuthenticatorConfiguration
|
||||
|
||||
typedef struct mse {
|
||||
uint8_t Qpt[65];
|
||||
uint8_t key_enc[12 + 32];
|
||||
bool init;
|
||||
} mse_t;
|
||||
extern mse_t mse;
|
||||
|
||||
extern int mse_decrypt_ct(uint8_t *, size_t);
|
||||
|
||||
// Command status responses
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
|
||||
extern uint8_t *driver_prepare_response();
|
||||
extern void driver_exec_finished(size_t size_next);
|
||||
extern int cbor_process(const uint8_t *data, size_t len);
|
||||
extern int cbor_process(uint8_t, const uint8_t *data, size_t len);
|
||||
extern const uint8_t aaguid[16];
|
||||
|
||||
extern const bool _btrue, _bfalse;
|
||||
|
||||
@@ -39,6 +39,9 @@ int fido_unload();
|
||||
|
||||
pinUvAuthToken_t paut = {0};
|
||||
|
||||
uint8_t keydev_dec[32];
|
||||
bool has_keydev_dec = false;
|
||||
|
||||
const uint8_t fido_aid[] = {
|
||||
8,
|
||||
0xA0, 0x00, 0x00, 0x06, 0x47, 0x2F, 0x00, 0x01
|
||||
@@ -113,13 +116,18 @@ int x509_create_cert(mbedtls_ecdsa_context *ecdsa, uint8_t *buffer, size_t buffe
|
||||
mbedtls_x509write_crt_set_authority_key_identifier(&ctx);
|
||||
mbedtls_x509write_crt_set_key_usage(&ctx, MBEDTLS_X509_KU_DIGITAL_SIGNATURE | MBEDTLS_X509_KU_KEY_CERT_SIGN);
|
||||
int ret = mbedtls_x509write_crt_der(&ctx, buffer, buffer_size, core1 ? random_gen : random_gen_core0, NULL);
|
||||
/* pk cannot be freed, as it is freed later */
|
||||
//mbedtls_pk_free(&key);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int load_keydev(uint8_t *key) {
|
||||
if (!ef_keydev || file_get_size(ef_keydev) == 0)
|
||||
if (has_keydev_dec == false && !file_has_data(ef_keydev))
|
||||
return CCID_ERR_MEMORY_FATAL;
|
||||
memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev));
|
||||
if (has_keydev_dec == true)
|
||||
memcpy(key, keydev_dec, sizeof(keydev_dec));
|
||||
else
|
||||
memcpy(key, file_get_data(ef_keydev), file_get_size(ef_keydev));
|
||||
//return mkek_decrypt(key, file_get_size(ef_keydev));
|
||||
return CCID_OK;
|
||||
}
|
||||
@@ -201,8 +209,9 @@ int derive_key(const uint8_t *app_id, bool new_key, uint8_t *key_handle, int cur
|
||||
|
||||
int scan_files(bool core1) {
|
||||
ef_keydev = search_by_fid(EF_KEY_DEV, NULL, SPECIFY_EF);
|
||||
ef_keydev_enc = search_by_fid(EF_KEY_DEV_ENC, NULL, SPECIFY_EF);
|
||||
if (ef_keydev) {
|
||||
if (!file_has_data(ef_keydev)) {
|
||||
if (!file_has_data(ef_keydev) && !file_has_data(ef_keydev_enc)) {
|
||||
printf("KEY DEVICE is empty. Generating SECP256R1 curve...");
|
||||
mbedtls_ecdsa_context ecdsa;
|
||||
mbedtls_ecdsa_init(&ecdsa);
|
||||
@@ -234,11 +243,15 @@ int scan_files(bool core1) {
|
||||
mbedtls_ecdsa_context key;
|
||||
mbedtls_ecdsa_init(&key);
|
||||
int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &key, file_get_data(ef_keydev), file_get_size(ef_keydev));
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdsa_free(&key);
|
||||
return ret;
|
||||
}
|
||||
ret = mbedtls_ecp_mul(&key.grp, &key.Q, &key.d, &key.grp.G, core1 ? random_gen : random_gen_core0, NULL);
|
||||
if (ret != 0)
|
||||
if (ret != 0) {
|
||||
mbedtls_ecdsa_free(&key);
|
||||
return ret;
|
||||
}
|
||||
ret = x509_create_cert(&key, cert, sizeof(cert), core1);
|
||||
mbedtls_ecdsa_free(&key);
|
||||
if (ret <= 0)
|
||||
@@ -276,6 +289,10 @@ int scan_files(bool core1) {
|
||||
else {
|
||||
printf("FATAL ERROR: Auth Token not found in memory!\r\n");
|
||||
}
|
||||
ef_largeblob = search_by_fid(EF_LARGEBLOB, NULL, SPECIFY_EF);
|
||||
if (!file_has_data(ef_largeblob)) {
|
||||
flash_write_data_to_file(ef_largeblob, (const uint8_t *)"\x80\x76\xbe\x8b\x52\x8d\x00\x75\xf7\xaa\xe9\x8d\x6f\xa5\x7a\x6d\x3c", 17);
|
||||
}
|
||||
low_flash_available();
|
||||
return CCID_OK;
|
||||
}
|
||||
@@ -318,6 +335,19 @@ uint32_t get_sign_counter() {
|
||||
return (*caddr) | (*(caddr + 1) << 8) | (*(caddr + 2) << 16) | (*(caddr + 3) << 24);
|
||||
}
|
||||
|
||||
uint8_t get_opts() {
|
||||
file_t *ef = search_by_fid(EF_OPTS, NULL, SPECIFY_EF);
|
||||
if (file_has_data(ef))
|
||||
return *file_get_data(ef);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void set_opts(uint8_t opts) {
|
||||
file_t *ef = search_by_fid(EF_OPTS, NULL, SPECIFY_EF);
|
||||
flash_write_data_to_file(ef, &opts, sizeof(uint8_t));
|
||||
low_flash_available();
|
||||
}
|
||||
|
||||
typedef struct cmd
|
||||
{
|
||||
uint8_t ins;
|
||||
|
||||
@@ -63,12 +63,7 @@ extern int ecdh(uint8_t protocol, const mbedtls_ecp_point *Q, uint8_t *sharedSec
|
||||
#define FIDO2_AUT_FLAG_AT 0x40
|
||||
#define FIDO2_AUT_FLAG_ED 0x80
|
||||
|
||||
#define FIDO2_PERMISSION_MC 0x1
|
||||
#define FIDO2_PERMISSION_GA 0x2
|
||||
#define FIDO2_PERMISSION_CM 0x4
|
||||
#define FIDO2_PERMISSION_BE 0x8
|
||||
#define FIDO2_PERMISSION_LBW 0x10
|
||||
#define FIDO2_PERMISSION_ACFG 0x20
|
||||
#define FIDO2_OPT_EA 0x01 // Enterprise Attestation
|
||||
|
||||
#define MAX_PIN_RETRIES 8
|
||||
extern bool getUserPresentFlagValue();
|
||||
@@ -78,9 +73,15 @@ extern void clearUserVerifiedFlag();
|
||||
extern void clearPinUvAuthTokenPermissionsExceptLbw();
|
||||
extern void send_keepalive();
|
||||
extern uint32_t get_sign_counter();
|
||||
extern uint8_t get_opts();
|
||||
extern void set_opts(uint8_t);
|
||||
#define MAX_CREDENTIAL_COUNT_IN_LIST 16
|
||||
#define MAX_CRED_ID_LENGTH 1024
|
||||
#define MAX_RESIDENT_CREDENTIALS 256
|
||||
#define MAX_CREDBLOB_LENGTH 128
|
||||
#define MAX_MSG_SIZE 1024
|
||||
#define MAX_FRAGMENT_LENGTH (MAX_MSG_SIZE - 64)
|
||||
#define MAX_LARGE_BLOB_SIZE 2048
|
||||
|
||||
typedef struct known_app {
|
||||
const uint8_t *rp_id_hash;
|
||||
@@ -101,6 +102,7 @@ typedef struct pinUvAuthToken {
|
||||
bool in_use;
|
||||
uint8_t permissions;
|
||||
uint8_t rp_id_hash[32];
|
||||
bool has_rp_id;
|
||||
bool user_present;
|
||||
bool user_verified;
|
||||
} pinUvAuthToken_t;
|
||||
|
||||
@@ -21,10 +21,15 @@
|
||||
file_t file_entries[] = {
|
||||
{.fid = 0x3f00, .parent = 0xff, .name = NULL, .type = FILE_TYPE_DF, .data = NULL, .ef_structure = 0, .acl = {0}}, // MF
|
||||
{.fid = EF_KEY_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Device Key
|
||||
{.fid = EF_KEY_DEV_ENC, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Device Key Enc
|
||||
{.fid = EF_EE_DEV, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // End Entity Certificate Device
|
||||
{.fid = EF_EE_DEV_EA, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // End Entity Enterprise Attestation Certificate
|
||||
{.fid = EF_COUNTER, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Global counter
|
||||
{.fid = EF_PIN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // PIN
|
||||
{.fid = EF_AUTHTOKEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // AUTH TOKEN
|
||||
{.fid = EF_MINPINLEN, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // MIN PIN LENGTH
|
||||
{.fid = EF_OPTS, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Global options
|
||||
{.fid = EF_LARGEBLOB, .parent = 0, .name = NULL, .type = FILE_TYPE_INTERNAL_EF | FILE_DATA_FLASH, .data = NULL, .ef_structure = FILE_EF_TRANSPARENT, .acl = {0xff}}, // Large Blob
|
||||
{ .fid = 0x0000, .parent = 0xff, .name = NULL, .type = FILE_TYPE_UNKNOWN, .data = NULL, .ef_structure = 0, .acl = {0} } //end
|
||||
};
|
||||
|
||||
@@ -35,3 +40,5 @@ file_t *ef_certdev = NULL;
|
||||
file_t *ef_counter = NULL;
|
||||
file_t *ef_pin = NULL;
|
||||
file_t *ef_authtoken = NULL;
|
||||
file_t *ef_keydev_enc = NULL;
|
||||
file_t *ef_largeblob = NULL;
|
||||
|
||||
@@ -21,17 +21,24 @@
|
||||
#include "file.h"
|
||||
|
||||
#define EF_KEY_DEV 0xCC00
|
||||
#define EF_KEY_DEV_ENC 0xCC01
|
||||
#define EF_EE_DEV 0xCE00
|
||||
#define EF_EE_DEV_EA 0xCE01
|
||||
#define EF_COUNTER 0xC000
|
||||
#define EF_OPTS 0xC001
|
||||
#define EF_PIN 0x1080
|
||||
#define EF_AUTHTOKEN 0x1090
|
||||
#define EF_MINPINLEN 0x1100
|
||||
#define EF_CRED 0xCF00 // Creds at 0xCF00 - 0xCFFF
|
||||
#define EF_RP 0xD000 // RPs at 0xD000 - 0xD0FF
|
||||
#define EF_LARGEBLOB 0x1101 // Large Blob Array
|
||||
|
||||
extern file_t *ef_keydev;
|
||||
extern file_t *ef_certdev;
|
||||
extern file_t *ef_counter;
|
||||
extern file_t *ef_pin;
|
||||
extern file_t *ef_authtoken;
|
||||
extern file_t *ef_keydev_enc;
|
||||
extern file_t *ef_largeblob;
|
||||
|
||||
#endif //_FILES_H_
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#ifndef __VERSION_H_
|
||||
#define __VERSION_H_
|
||||
|
||||
#define PICO_FIDO_VERSION 0x0202
|
||||
#define PICO_FIDO_VERSION 0x020A
|
||||
|
||||
#define PICO_FIDO_VERSION_MAJOR ((PICO_FIDO_VERSION >> 8) & 0xff)
|
||||
#define PICO_FIDO_VERSION_MINOR (PICO_FIDO_VERSION & 0xff)
|
||||
|
||||
@@ -296,7 +296,7 @@ class Device():
|
||||
|
||||
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.CREATE, origin=self.__origin, challenge=os.urandom(32)
|
||||
type=CollectedClientData.TYPE.GET, origin=self.__origin, challenge=os.urandom(32)
|
||||
)
|
||||
rp_id = rp_id if rp_id is not Ellipsis else self.__rp['id']
|
||||
if (ctap1 is True):
|
||||
|
||||
@@ -76,7 +76,7 @@ def test_get_assertion_allow_list_filtering_and_buffering(device):
|
||||
len(rp2_assertions)
|
||||
)
|
||||
|
||||
assert counts in [(None, None), (l1, l2)]
|
||||
assert counts in [(1, 1), (l1, l2)]
|
||||
|
||||
def test_corrupt_credId(device, MCRes):
|
||||
# apply bit flip
|
||||
|
||||
130
tests/pico-fido/test_blob.py
Normal file
130
tests/pico-fido/test_blob.py
Normal file
@@ -0,0 +1,130 @@
|
||||
import pytest
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.ctap2.pin import PinProtocolV2, ClientPin
|
||||
from utils import verify
|
||||
import os
|
||||
|
||||
PIN='12345678'
|
||||
SMALL_BLOB=b"A"*32
|
||||
LARGE_BLOB=b"B"*1024
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def MCCredBlob(device):
|
||||
res = device.doMC(extensions={'credBlob': SMALL_BLOB})['res'].attestation_object
|
||||
return res
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def GACredBlob(device, MCCredBlob):
|
||||
res = device.doGA(allow_list=[
|
||||
{"id": MCCredBlob.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
], extensions={'getCredBlob': True})
|
||||
|
||||
assertions = res['res'].get_assertions()
|
||||
for a in assertions:
|
||||
verify(MCCredBlob, a, res['req']['client_data'].hash)
|
||||
return assertions[0]
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def MCLBK(device):
|
||||
res = device.doMC(
|
||||
rk=True,
|
||||
extensions={'largeBlob':{'support':'required'}}
|
||||
)['res']
|
||||
return res
|
||||
|
||||
@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"}
|
||||
],extensions={'largeBlob':{'read': True}}
|
||||
)
|
||||
assertions = res['res'].get_assertions()
|
||||
for a in assertions:
|
||||
verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
|
||||
return res['res']
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def GALBReadLBK(GALBRead):
|
||||
return GALBRead.get_assertions()[0]
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def GALBReadLB(GALBRead):
|
||||
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"}
|
||||
],extensions={'largeBlob':{'write': LARGE_BLOB}}
|
||||
)
|
||||
assertions = res['res'].get_assertions()
|
||||
for a in assertions:
|
||||
verify(MCLBK.attestation_object, a, res['req']['client_data'].hash)
|
||||
return res['res'].get_response(0)
|
||||
|
||||
def test_supports_credblob(info):
|
||||
assert info.extensions
|
||||
assert 'credBlob' in info.extensions
|
||||
assert info.max_cred_blob_length
|
||||
assert info.max_cred_blob_length > 0
|
||||
|
||||
def test_mc_credblob(MCCredBlob):
|
||||
assert MCCredBlob.auth_data.extensions
|
||||
assert "credBlob" in MCCredBlob.auth_data.extensions
|
||||
assert MCCredBlob.auth_data.extensions['credBlob'] is True
|
||||
|
||||
def test_ga_credblob(GACredBlob):
|
||||
assert GACredBlob.auth_data.extensions
|
||||
assert "credBlob" in GACredBlob.auth_data.extensions
|
||||
assert GACredBlob.auth_data.extensions['credBlob'] == SMALL_BLOB
|
||||
|
||||
def test_wrong_credblob(device, info):
|
||||
device.reset()
|
||||
cdh = os.urandom(32)
|
||||
ClientPin(device.client()._backend.ctap2).set_pin(PIN)
|
||||
pin_token = ClientPin(device.client()._backend.ctap2).get_pin_token(PIN, permissions=ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.AUTHENTICATOR_CFG)
|
||||
protocol = PinProtocolV2()
|
||||
MC = device.MC(
|
||||
client_data_hash=cdh,
|
||||
extensions={'credBlob': b'A'*(info.max_cred_blob_length+1)},
|
||||
pin_uv_protocol=protocol.VERSION,
|
||||
pin_uv_param=protocol.authenticate(pin_token, cdh)
|
||||
)['res']
|
||||
|
||||
assert MC.auth_data.extensions
|
||||
assert "credBlob" in MC.auth_data.extensions
|
||||
assert MC.auth_data.extensions['credBlob'] is False
|
||||
|
||||
res = device.doGA(allow_list=[
|
||||
{"id": MC.auth_data.credential_data.credential_id, "type": "public-key"}
|
||||
], extensions={'getCredBlob': True})
|
||||
|
||||
assertions = res['res'].get_assertions()
|
||||
for a in assertions:
|
||||
verify(MC, a, res['req']['client_data'].hash)
|
||||
|
||||
assert assertions[0].auth_data.extensions
|
||||
assert "credBlob" in assertions[0].auth_data.extensions
|
||||
assert len(assertions[0].auth_data.extensions['credBlob']) == 0
|
||||
|
||||
def test_supports_largeblobs(info):
|
||||
assert info.extensions
|
||||
assert 'largeBlobKey' in info.extensions
|
||||
assert 'largeBlobs' in info.options
|
||||
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
|
||||
|
||||
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 'blob' in GALBReadLB.extension_results
|
||||
assert GALBReadLB.extension_results['blob'] == LARGE_BLOB
|
||||
@@ -3,76 +3,78 @@ import time
|
||||
import random
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.ctap2 import CredentialManagement
|
||||
from fido2.utils import sha256, hmac_sha256
|
||||
from fido2.ctap2.pin import PinProtocolV2
|
||||
from fido2.utils import sha256
|
||||
from fido2.ctap2.pin import PinProtocolV2, ClientPin
|
||||
from binascii import hexlify
|
||||
from utils import generate_random_user
|
||||
|
||||
PIN = "12345678"
|
||||
|
||||
|
||||
@pytest.fixture(params=[PIN], scope = 'function')
|
||||
def PinToken(request, device, client_pin):
|
||||
def client_pin_set(request, device, client_pin):
|
||||
#device.reboot()
|
||||
device.reset()
|
||||
pin = request.param
|
||||
client_pin.set_pin(pin)
|
||||
return client_pin.get_pin_token(pin)
|
||||
|
||||
|
||||
@pytest.fixture(scope = 'function')
|
||||
def MC_RK_Res(device, PinToken):
|
||||
def MC_RK_Res(device, client_pin_set):
|
||||
rp = {"id": "ssh:", "name": "Bate Goiko"}
|
||||
device.doMC(rp=rp, rk=True)
|
||||
|
||||
rp = {"id": "xakcop.com", "name": "John Doe"}
|
||||
device.doMC(rp=rp, rk=True)
|
||||
|
||||
def PinToken(device):
|
||||
return ClientPin(device.client()._backend.ctap2).get_pin_token(PIN, permissions=ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.CREDENTIAL_MGMT)
|
||||
|
||||
@pytest.fixture(scope = 'function')
|
||||
def CredMgmt(device, PinToken):
|
||||
def CredMgmt(device):
|
||||
pt = PinToken(device)
|
||||
pin_protocol = PinProtocolV2()
|
||||
return CredentialManagement(device.client()._backend.ctap2, pin_protocol, PinToken)
|
||||
return CredentialManagement(device.client()._backend.ctap2, pin_protocol, pt)
|
||||
|
||||
|
||||
def _test_enumeration(CredMgmt, rp_map):
|
||||
def _test_enumeration(device, rp_map):
|
||||
"Enumerate credentials using BFS"
|
||||
res = CredMgmt.enumerate_rps()
|
||||
credMgmt = CredMgmt(device)
|
||||
res = credMgmt.enumerate_rps()
|
||||
assert len(rp_map.keys()) == len(res)
|
||||
|
||||
for rp in res:
|
||||
creds = CredMgmt.enumerate_creds(sha256(rp[3]["id"]))
|
||||
assert len(creds) == rp_map[rp[3]["id"].decode()]
|
||||
creds = credMgmt.enumerate_creds(sha256(rp[3]["id"].encode()))
|
||||
assert len(creds) == rp_map[rp[3]["id"]]
|
||||
|
||||
|
||||
def _test_enumeration_interleaved(CredMgmt, rp_map):
|
||||
def _test_enumeration_interleaved(device, rp_map):
|
||||
"Enumerate credentials using DFS"
|
||||
first_rp = CredMgmt.enumerate_rps_begin()
|
||||
credMgmt = CredMgmt(device)
|
||||
first_rp = credMgmt.enumerate_rps_begin()
|
||||
assert len(rp_map.keys()) == first_rp[CredentialManagement.RESULT.TOTAL_RPS]
|
||||
|
||||
rk_count = 1
|
||||
first_rk = CredMgmt.enumerate_creds_begin(sha256(first_rp[3]["id"]))
|
||||
first_rk = credMgmt.enumerate_creds_begin(sha256(first_rp[3]["id"].encode()))
|
||||
for i in range(1, first_rk[CredentialManagement.RESULT.TOTAL_CREDENTIALS]):
|
||||
c = CredMgmt.enumerate_creds_next()
|
||||
c = credMgmt.enumerate_creds_next()
|
||||
rk_count += 1
|
||||
|
||||
assert rk_count == rp_map[first_rp[3]["id"].decode()]
|
||||
assert rk_count == rp_map[first_rp[3]["id"]]
|
||||
|
||||
for i in range(1, first_rp[CredentialManagement.RESULT.TOTAL_RPS]):
|
||||
next_rp = CredMgmt.enumerate_rps_next()
|
||||
next_rp = credMgmt.enumerate_rps_next()
|
||||
|
||||
rk_count = 1
|
||||
first_rk = CredMgmt.enumerate_creds_begin(
|
||||
sha256(next_rp[3]["id"])
|
||||
first_rk =credMgmt.enumerate_creds_begin(
|
||||
sha256(next_rp[3]["id"].encode())
|
||||
)
|
||||
for i in range(1, first_rk[CredentialManagement.RESULT.TOTAL_CREDENTIALS]):
|
||||
c = CredMgmt.enumerate_creds_next()
|
||||
c = credMgmt.enumerate_creds_next()
|
||||
rk_count += 1
|
||||
|
||||
assert rk_count == rp_map[next_rp[3]["id"].decode()]
|
||||
assert rk_count == rp_map[next_rp[3]["id"]]
|
||||
|
||||
|
||||
def CredMgmtWrongPinAuth(device, pin_token):
|
||||
def CredMgmtWrongPinAuth(device):
|
||||
pin_token = PinToken(device)
|
||||
pin_protocol = PinProtocolV2()
|
||||
wrong_pt = bytearray(pin_token)
|
||||
wrong_pt[0] = (wrong_pt[0] + 1) % 256
|
||||
@@ -98,55 +100,58 @@ def test_get_info(info):
|
||||
assert 0x8 in info
|
||||
assert info[0x8] > 1
|
||||
|
||||
def test_get_metadata(CredMgmt, MC_RK_Res):
|
||||
metadata = CredMgmt.get_metadata()
|
||||
def test_get_metadata_ok(MC_RK_Res, device):
|
||||
metadata = CredMgmt(device).get_metadata()
|
||||
assert metadata[CredentialManagement.RESULT.EXISTING_CRED_COUNT] == 2
|
||||
assert metadata[CredentialManagement.RESULT.MAX_REMAINING_COUNT] >= 48
|
||||
|
||||
def test_enumerate_rps(CredMgmt, MC_RK_Res):
|
||||
res = CredMgmt.enumerate_rps()
|
||||
def test_enumerate_rps(MC_RK_Res, device):
|
||||
res = CredMgmt(device).enumerate_rps()
|
||||
assert len(res) == 2
|
||||
assert res[0][CredentialManagement.RESULT.RP]["id"] == b"ssh:"
|
||||
assert res[0][CredentialManagement.RESULT.RP]["id"] == "ssh:"
|
||||
assert res[0][CredentialManagement.RESULT.RP_ID_HASH] == sha256(b"ssh:")
|
||||
# Solo doesn't store rpId with the exception of "ssh:"
|
||||
assert res[1][CredentialManagement.RESULT.RP]["id"] == b"xakcop.com"
|
||||
assert res[1][CredentialManagement.RESULT.RP]["id"] == "xakcop.com"
|
||||
assert res[1][CredentialManagement.RESULT.RP_ID_HASH] == sha256(b"xakcop.com")
|
||||
|
||||
def test_enumarate_creds(CredMgmt, MC_RK_Res):
|
||||
res = CredMgmt.enumerate_creds(sha256(b"ssh:"))
|
||||
def test_enumarate_creds(MC_RK_Res, device):
|
||||
credMgmt = CredMgmt(device)
|
||||
res = credMgmt.enumerate_creds(sha256(b"ssh:"))
|
||||
assert len(res) == 1
|
||||
assert_cred_response_has_all_fields(res[0])
|
||||
res = CredMgmt.enumerate_creds(sha256(b"xakcop.com"))
|
||||
res = credMgmt.enumerate_creds(sha256(b"xakcop.com"))
|
||||
assert len(res) == 1
|
||||
assert_cred_response_has_all_fields(res[0])
|
||||
res = CredMgmt.enumerate_creds(sha256(b"missing.com"))
|
||||
res = credMgmt.enumerate_creds(sha256(b"missing.com"))
|
||||
assert not res
|
||||
|
||||
def test_get_metadata_wrong_pinauth(device, MC_RK_Res, PinToken):
|
||||
def test_get_metadata_wrong_pinauth(device, MC_RK_Res):
|
||||
cmd = lambda credMgmt: credMgmt.get_metadata()
|
||||
_test_wrong_pinauth(device, cmd, PinToken)
|
||||
_test_wrong_pinauth(device, cmd)
|
||||
|
||||
def test_rpbegin_wrong_pinauth(device, MC_RK_Res, PinToken):
|
||||
def test_rpbegin_wrong_pinauth(device, MC_RK_Res):
|
||||
cmd = lambda credMgmt: credMgmt.enumerate_rps_begin()
|
||||
_test_wrong_pinauth(device, cmd, PinToken)
|
||||
_test_wrong_pinauth(device, cmd)
|
||||
|
||||
def test_rkbegin_wrong_pinauth(device, MC_RK_Res, PinToken):
|
||||
def test_rkbegin_wrong_pinauth(device, MC_RK_Res):
|
||||
cmd = lambda credMgmt: credMgmt.enumerate_creds_begin(sha256(b"ssh:"))
|
||||
_test_wrong_pinauth(device, cmd, PinToken)
|
||||
_test_wrong_pinauth(device, cmd)
|
||||
|
||||
def test_rpnext_without_rpbegin(device, CredMgmt, MC_RK_Res):
|
||||
CredMgmt.enumerate_creds_begin(sha256(b"ssh:"))
|
||||
def test_rpnext_without_rpbegin(device, MC_RK_Res):
|
||||
credMgmt = CredMgmt(device)
|
||||
credMgmt.enumerate_creds_begin(sha256(b"ssh:"))
|
||||
with pytest.raises(CtapError) as e:
|
||||
CredMgmt.enumerate_rps_next()
|
||||
credMgmt.enumerate_rps_next()
|
||||
assert e.value.code == CtapError.ERR.NOT_ALLOWED
|
||||
|
||||
def test_rknext_without_rkbegin(device, CredMgmt, MC_RK_Res):
|
||||
CredMgmt.enumerate_rps_begin()
|
||||
def test_rknext_without_rkbegin(device, MC_RK_Res):
|
||||
credMgmt = CredMgmt(device)
|
||||
credMgmt.enumerate_rps_begin()
|
||||
with pytest.raises(CtapError) as e:
|
||||
CredMgmt.enumerate_creds_next()
|
||||
credMgmt.enumerate_creds_next()
|
||||
assert e.value.code == CtapError.ERR.NOT_ALLOWED
|
||||
|
||||
def test_delete(device, PinToken, CredMgmt):
|
||||
def test_delete(device):
|
||||
|
||||
# create a new RK
|
||||
rp = {"id": "example_3.com", "name": "John Doe 2"}
|
||||
@@ -156,21 +161,22 @@ def test_delete(device, PinToken, CredMgmt):
|
||||
auth = device.doGA(rp_id=rp['id'])
|
||||
|
||||
# get the ID from enumeration
|
||||
creds = CredMgmt.enumerate_creds(reg.auth_data.rp_id_hash)
|
||||
credMgmt = CredMgmt(device)
|
||||
creds = credMgmt.enumerate_creds(reg.auth_data.rp_id_hash)
|
||||
for cred in creds:
|
||||
if cred[7]["id"] == reg.auth_data.credential_data.credential_id:
|
||||
break
|
||||
|
||||
# delete it
|
||||
cred = {"id": cred[7]["id"], "type": "public-key"}
|
||||
CredMgmt.delete_cred(cred)
|
||||
credMgmt.delete_cred(cred)
|
||||
|
||||
# make sure it doesn't work
|
||||
with pytest.raises(CtapError) as e:
|
||||
auth = device.doGA(rp_id=rp['id'])
|
||||
assert e.value.code == CtapError.ERR.NO_CREDENTIALS
|
||||
|
||||
def test_add_delete(device, PinToken, CredMgmt):
|
||||
def test_add_delete(device):
|
||||
""" Delete a credential in the 'middle' and ensure other credentials are not affected. """
|
||||
|
||||
rp = {"id": "example_4.com", "name": "John Doe 3"}
|
||||
@@ -182,11 +188,12 @@ def test_add_delete(device, PinToken, CredMgmt):
|
||||
regs.append(reg)
|
||||
|
||||
# Check they all enumerate
|
||||
res = CredMgmt.enumerate_creds(regs[1].auth_data.rp_id_hash)
|
||||
credMgmt = CredMgmt(device)
|
||||
res = credMgmt.enumerate_creds(regs[1].auth_data.rp_id_hash)
|
||||
assert len(res) == 3
|
||||
|
||||
# delete the middle one
|
||||
creds = CredMgmt.enumerate_creds(reg.auth_data.rp_id_hash)
|
||||
creds = credMgmt.enumerate_creds(reg.auth_data.rp_id_hash)
|
||||
for cred in creds:
|
||||
if cred[7]["id"] == regs[1].auth_data.credential_data.credential_id:
|
||||
break
|
||||
@@ -194,16 +201,16 @@ def test_add_delete(device, PinToken, CredMgmt):
|
||||
assert cred[7]["id"] == regs[1].auth_data.credential_data.credential_id
|
||||
|
||||
cred = {"id": cred[7]["id"], "type": "public-key"}
|
||||
CredMgmt.delete_cred(cred)
|
||||
credMgmt.delete_cred(cred)
|
||||
|
||||
# Check one less enumerates
|
||||
res = CredMgmt.enumerate_creds(regs[0].auth_data.rp_id_hash)
|
||||
res = credMgmt.enumerate_creds(regs[0].auth_data.rp_id_hash)
|
||||
assert len(res) == 2
|
||||
|
||||
def test_multiple_creds_per_multiple_rps(
|
||||
device, PinToken, CredMgmt, MC_RK_Res
|
||||
device, MC_RK_Res
|
||||
):
|
||||
res = CredMgmt.enumerate_rps()
|
||||
res = CredMgmt(device).enumerate_rps()
|
||||
assert len(res) == 2
|
||||
|
||||
new_rps = [
|
||||
@@ -217,27 +224,26 @@ def test_multiple_creds_per_multiple_rps(
|
||||
for i in range(0, 3):
|
||||
reg = device.doMC(rp=rp, rk=True, user=generate_random_user())
|
||||
|
||||
res = CredMgmt.enumerate_rps()
|
||||
credMgmt = CredMgmt(device)
|
||||
res = credMgmt.enumerate_rps()
|
||||
assert len(res) == 5
|
||||
|
||||
for rp in res:
|
||||
if rp[3]["id"][:12] == "new_example_":
|
||||
creds = CredMgmt.enumerate_creds(sha256(rp[3]["id"].encode("utf8")))
|
||||
creds = credMgmt.enumerate_creds(sha256(rp[3]["id"].encode("utf8")))
|
||||
assert len(creds) == 3
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"enumeration_test", [_test_enumeration, _test_enumeration_interleaved]
|
||||
)
|
||||
def test_multiple_enumeration(
|
||||
device, PinToken, MC_RK_Res, CredMgmt, enumeration_test
|
||||
device, MC_RK_Res, enumeration_test
|
||||
):
|
||||
""" Test enumerate still works after different commands """
|
||||
|
||||
res = CredMgmt.enumerate_rps()
|
||||
|
||||
expected_enumeration = {"xakcop.com": 1, "ssh:": 1}
|
||||
|
||||
enumeration_test(CredMgmt, expected_enumeration)
|
||||
enumeration_test(device, expected_enumeration)
|
||||
|
||||
new_rps = [
|
||||
{"id": "example-2.com", "name": "Example-2-creds", "count": 2},
|
||||
@@ -253,27 +259,19 @@ def test_multiple_enumeration(
|
||||
# Now expect creds from this RP
|
||||
expected_enumeration[rp["id"]] = rp["count"]
|
||||
|
||||
enumeration_test(CredMgmt, expected_enumeration)
|
||||
enumeration_test(CredMgmt, expected_enumeration)
|
||||
|
||||
metadata = CredMgmt.get_metadata()
|
||||
|
||||
enumeration_test(CredMgmt, expected_enumeration)
|
||||
enumeration_test(CredMgmt, expected_enumeration)
|
||||
enumeration_test(device, expected_enumeration)
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"enumeration_test", [_test_enumeration, _test_enumeration_interleaved]
|
||||
)
|
||||
def test_multiple_enumeration_with_deletions(
|
||||
device, PinToken, MC_RK_Res, CredMgmt, enumeration_test
|
||||
device, MC_RK_Res, enumeration_test
|
||||
):
|
||||
""" Create each credential in random order. Test enumerate still works after randomly deleting each credential"""
|
||||
|
||||
res = CredMgmt.enumerate_rps()
|
||||
|
||||
expected_enumeration = {"xakcop.com": 1, "ssh:": 1}
|
||||
|
||||
enumeration_test(CredMgmt, expected_enumeration)
|
||||
enumeration_test(device, expected_enumeration)
|
||||
|
||||
new_rps = [
|
||||
{"id": "example-1.com", "name": "Example-1-creds"},
|
||||
@@ -294,7 +292,7 @@ def test_multiple_enumeration_with_deletions(
|
||||
else:
|
||||
expected_enumeration[rp["id"]] += 1
|
||||
|
||||
enumeration_test(CredMgmt, expected_enumeration)
|
||||
enumeration_test(device, expected_enumeration)
|
||||
|
||||
total_creds = len(new_rps)
|
||||
|
||||
@@ -304,10 +302,11 @@ def test_multiple_enumeration_with_deletions(
|
||||
num = expected_enumeration[rp]
|
||||
|
||||
index = 0 if num == 1 else random.randint(0, num - 1)
|
||||
cred = CredMgmt.enumerate_creds(sha256(rp.encode("utf8")))[index]
|
||||
credMgmt = CredMgmt(device)
|
||||
cred = credMgmt.enumerate_creds(sha256(rp.encode("utf8")))[index]
|
||||
|
||||
# print('Delete %d index (%d total) cred of %s' % (index, expected_enumeration[rp], rp))
|
||||
CredMgmt.delete_cred({"id": cred[7]["id"], "type": "public-key"})
|
||||
credMgmt.delete_cred({"id": cred[7]["id"], "type": "public-key"})
|
||||
|
||||
expected_enumeration[rp] -= 1
|
||||
if expected_enumeration[rp] == 0:
|
||||
@@ -316,43 +315,13 @@ def test_multiple_enumeration_with_deletions(
|
||||
if len(list(expected_enumeration.keys())) == 0:
|
||||
break
|
||||
|
||||
enumeration_test(CredMgmt, expected_enumeration)
|
||||
enumeration_test(device, expected_enumeration)
|
||||
|
||||
def _test_wrong_pinauth(device, cmd, PinToken):
|
||||
def _test_wrong_pinauth(device, cmd):
|
||||
|
||||
credMgmt = CredMgmtWrongPinAuth(device, PinToken)
|
||||
credMgmt = CredMgmtWrongPinAuth(device)
|
||||
|
||||
for i in range(2):
|
||||
with pytest.raises(CtapError) as e:
|
||||
cmd(credMgmt)
|
||||
assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
cmd(credMgmt)
|
||||
assert e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED
|
||||
|
||||
#device.reboot()
|
||||
credMgmt = CredMgmtWrongPinAuth(device, PinToken)
|
||||
|
||||
for i in range(2):
|
||||
time.sleep(0.2)
|
||||
with pytest.raises(CtapError) as e:
|
||||
cmd(credMgmt)
|
||||
assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
cmd(credMgmt)
|
||||
assert e.value.code == CtapError.ERR.PIN_AUTH_BLOCKED
|
||||
|
||||
#device.reboot()
|
||||
credMgmt = CredMgmtWrongPinAuth(device, PinToken)
|
||||
|
||||
for i in range(1):
|
||||
time.sleep(0.2)
|
||||
with pytest.raises(CtapError) as e:
|
||||
cmd(credMgmt)
|
||||
assert e.value.code == CtapError.ERR.PIN_AUTH_INVALID
|
||||
|
||||
with pytest.raises(CtapError) as e:
|
||||
cmd(credMgmt)
|
||||
assert e.value.code == CtapError.ERR.PIN_BLOCKED
|
||||
|
||||
81
tests/pico-fido/test_minpinlength.py
Normal file
81
tests/pico-fido/test_minpinlength.py
Normal file
@@ -0,0 +1,81 @@
|
||||
import pytest
|
||||
from fido2.ctap2.extensions import CredProtectExtension
|
||||
from fido2.webauthn import UserVerificationRequirement
|
||||
from fido2.ctap import CtapError
|
||||
from fido2.ctap2.pin import PinProtocolV2, ClientPin
|
||||
from fido2.ctap2 import Config
|
||||
|
||||
PIN='12345678'
|
||||
MINPINLENGTH=6
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def MCMinPin(device):
|
||||
res = device.doMC(rk=True, extensions={'minPinLength': True})['res'].attestation_object
|
||||
return res
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def SetMinPin(device):
|
||||
device.reset()
|
||||
ClientPin(device.client()._backend.ctap2).set_pin(PIN)
|
||||
cfg = FidoConfig(device)
|
||||
cfg.set_min_pin_length(MINPINLENGTH,rp_ids=['example.com'])
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def SetMinPinWrongRpid(device):
|
||||
device.reset()
|
||||
ClientPin(device.client()._backend.ctap2).set_pin(PIN)
|
||||
cfg = FidoConfig(device)
|
||||
cfg.set_min_pin_length(MINPINLENGTH,rp_ids=['notanexample.com'])
|
||||
|
||||
def PinToken(device):
|
||||
return ClientPin(device.client()._backend.ctap2).get_pin_token(PIN, permissions=ClientPin.PERMISSION.MAKE_CREDENTIAL | ClientPin.PERMISSION.AUTHENTICATOR_CFG)
|
||||
|
||||
def FidoConfig(device):
|
||||
pt = PinToken(device)
|
||||
pin_protocol = PinProtocolV2()
|
||||
return Config(device.client()._backend.ctap2, pin_protocol, pt)
|
||||
|
||||
def test_supports_minpin(info):
|
||||
assert info.extensions
|
||||
assert 'minPinLength' in info.extensions
|
||||
assert info.options
|
||||
assert 'setMinPINLength' in info.options
|
||||
assert info.options['setMinPINLength'] is True
|
||||
|
||||
def test_minpin(SetMinPin, MCMinPin):
|
||||
assert MCMinPin.auth_data.extensions
|
||||
assert "minPinLength" in MCMinPin.auth_data.extensions
|
||||
assert MCMinPin.auth_data.extensions['minPinLength'] == MINPINLENGTH
|
||||
|
||||
def test_minpin_bad_rpid(SetMinPinWrongRpid, MCMinPin):
|
||||
assert not MCMinPin.auth_data.extensions
|
||||
assert "minPinLength" not in MCMinPin.auth_data.extensions
|
||||
|
||||
def test_setminpin(device, SetMinPin, MCMinPin):
|
||||
cfg = FidoConfig(device)
|
||||
cfg.set_min_pin_length(MINPINLENGTH+2,rp_ids=['example.com'])
|
||||
res = device.doMC(rk=True, extensions={'minPinLength': True})['res'].attestation_object
|
||||
assert res.auth_data.extensions
|
||||
assert "minPinLength" in res.auth_data.extensions
|
||||
assert res.auth_data.extensions['minPinLength'] == MINPINLENGTH+2
|
||||
|
||||
def test_no_setminpin(device, SetMinPin, MCMinPin):
|
||||
cfg = FidoConfig(device)
|
||||
with pytest.raises(CtapError) as e:
|
||||
cfg.set_min_pin_length(MINPINLENGTH-2,rp_ids=['example.com'])
|
||||
assert e.value.code == CtapError.ERR.PIN_POLICY_VIOLATION
|
||||
|
||||
def test_setminpin_check_force(device, SetMinPin, MCMinPin):
|
||||
cfg = FidoConfig(device)
|
||||
cfg.set_min_pin_length(len(PIN)+1,rp_ids=['example.com'])
|
||||
info = device.client()._backend.ctap2.get_info()
|
||||
assert info.force_pin_change == True
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"force", [True, False]
|
||||
)
|
||||
def test_setminpin_set_forcee(device, SetMinPin, MCMinPin, force):
|
||||
cfg = FidoConfig(device)
|
||||
cfg.set_min_pin_length(MINPINLENGTH,rp_ids=['example.com'],force_change_pin=force)
|
||||
info = device.client()._backend.ctap2.get_info()
|
||||
assert info.force_pin_change == force
|
||||
451
tools/pico-fido-tool.py
Normal file
451
tools/pico-fido-tool.py
Normal file
@@ -0,0 +1,451 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
/*
|
||||
* This file is part of the Pico Fido distribution (https://github.com/polhenarejos/pico-fido).
|
||||
* Copyright (c) 2022 Pol Henarejos.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
"""
|
||||
|
||||
import sys
|
||||
import argparse
|
||||
import platform
|
||||
from binascii import hexlify
|
||||
from words import words
|
||||
from threading import Event
|
||||
from typing import Mapping, Any, Optional, Callable
|
||||
import struct
|
||||
import urllib.request
|
||||
import json
|
||||
from enum import IntEnum, unique
|
||||
|
||||
try:
|
||||
from fido2.ctap2.config import Config
|
||||
from fido2.ctap2 import Ctap2
|
||||
from fido2.hid import CtapHidDevice, CTAPHID
|
||||
from fido2.utils import bytes2int, int2bytes
|
||||
from fido2 import cbor
|
||||
from fido2.ctap import CtapDevice, CtapError
|
||||
from fido2.ctap2.pin import PinProtocol, _PinUv
|
||||
from fido2.ctap2.base import args
|
||||
except:
|
||||
print('ERROR: fido2 module not found! Install fido2 package.\nTry with `pip install fido2`')
|
||||
sys.exit(-1)
|
||||
|
||||
try:
|
||||
from cryptography.hazmat.primitives.asymmetric import ec
|
||||
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||
from cryptography.hazmat.primitives.ciphers.aead import ChaCha20Poly1305
|
||||
from cryptography.hazmat.primitives import hashes
|
||||
from cryptography import x509
|
||||
except:
|
||||
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
|
||||
sys.exit(-1)
|
||||
|
||||
from enum import IntEnum
|
||||
from binascii import hexlify
|
||||
|
||||
if (platform.system() == 'Windows' or platform.system() == 'Linux'):
|
||||
from secure_key import windows as skey
|
||||
elif (platform.system() == 'Darwin'):
|
||||
from secure_key import macos as skey
|
||||
else:
|
||||
print('ERROR: platform not supported')
|
||||
sys.exit(-1)
|
||||
|
||||
def get_pki_data(url, data=None, method='GET'):
|
||||
user_agent = 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; '
|
||||
'rv:1.9.0.7) Gecko/2009021910 Firefox/3.0.7'
|
||||
method = 'GET'
|
||||
if (data is not None):
|
||||
method = 'POST'
|
||||
req = urllib.request.Request(f"https://www.picokeys.com/pico/pico-fido/{url}/",
|
||||
method=method,
|
||||
data=data,
|
||||
headers={'User-Agent': user_agent, })
|
||||
response = urllib.request.urlopen(req)
|
||||
resp = response.read().decode('utf-8')
|
||||
j = json.loads(resp)
|
||||
return j
|
||||
|
||||
class VendorConfig(Config):
|
||||
|
||||
class PARAM(IntEnum):
|
||||
VENDOR_COMMAND_ID = 0x01
|
||||
VENDOR_AUT_CT = 0x02
|
||||
|
||||
class CMD(IntEnum):
|
||||
CONFIG_AUT_ENABLE = 0x03e43f56b34285e2
|
||||
CONFIG_AUT_DISABLE = 0x1831a40f04a25ed9
|
||||
|
||||
class RESP(IntEnum):
|
||||
KEY_AGREEMENT = 0x01
|
||||
|
||||
def __init__(self, ctap, pin_uv_protocol=None, pin_uv_token=None):
|
||||
super().__init__(ctap, pin_uv_protocol, pin_uv_token)
|
||||
|
||||
def enable_device_aut(self, ct):
|
||||
self._call(
|
||||
Config.CMD.VENDOR_PROTOTYPE,
|
||||
{
|
||||
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_ENABLE,
|
||||
VendorConfig.PARAM.VENDOR_AUT_CT: ct
|
||||
},
|
||||
)
|
||||
|
||||
def disable_device_aut(self):
|
||||
self._call(
|
||||
Config.CMD.VENDOR_PROTOTYPE,
|
||||
{
|
||||
VendorConfig.PARAM.VENDOR_COMMAND_ID: VendorConfig.CMD.CONFIG_AUT_DISABLE
|
||||
},
|
||||
)
|
||||
|
||||
class Ctap2Vendor(Ctap2):
|
||||
def __init__(self, device: CtapDevice, strict_cbor: bool = True):
|
||||
super().__init__(device=device, strict_cbor=strict_cbor)
|
||||
|
||||
|
||||
def send_vendor(
|
||||
self,
|
||||
cmd: int,
|
||||
data: Optional[Mapping[int, Any]] = None,
|
||||
*,
|
||||
event: Optional[Event] = None,
|
||||
on_keepalive: Optional[Callable[[int], None]] = None,
|
||||
) -> Mapping[int, Any]:
|
||||
"""Sends a VENDOR message to the device, and waits for a response.
|
||||
|
||||
:param cmd: The command byte of the request.
|
||||
:param data: The payload to send (to be CBOR encoded).
|
||||
:param event: Optional threading.Event used to cancel the request.
|
||||
:param on_keepalive: Optional function called when keep-alive is sent by
|
||||
the authenticator.
|
||||
"""
|
||||
request = struct.pack(">B", cmd)
|
||||
if data is not None:
|
||||
request += cbor.encode(data)
|
||||
response = self.device.call(CTAPHID.VENDOR_FIRST + 1, request, event, on_keepalive)
|
||||
status = response[0]
|
||||
if status != 0x00:
|
||||
raise CtapError(status)
|
||||
enc = response[1:]
|
||||
if not enc:
|
||||
return {}
|
||||
decoded = cbor.decode(enc)
|
||||
if self._strict_cbor:
|
||||
expected = cbor.encode(decoded)
|
||||
if expected != enc:
|
||||
raise ValueError(
|
||||
"Non-canonical CBOR from Authenticator.\n"
|
||||
f"Got: {enc.hex()}\nExpected: {expected.hex()}"
|
||||
)
|
||||
if isinstance(decoded, Mapping):
|
||||
return decoded
|
||||
raise TypeError("Decoded value of wrong type")
|
||||
|
||||
def vendor(
|
||||
self,
|
||||
cmd: int,
|
||||
sub_cmd: int,
|
||||
sub_cmd_params: Optional[Mapping[int, Any]] = None,
|
||||
pin_uv_protocol: Optional[int] = None,
|
||||
pin_uv_param: Optional[bytes] = None,
|
||||
) -> Mapping[int, Any]:
|
||||
"""CTAP2 authenticator vendor command.
|
||||
|
||||
This command is used to configure various authenticator features through the
|
||||
use of its subcommands.
|
||||
|
||||
This method is not intended to be called directly. It is intended to be used by
|
||||
an instance of the Config class.
|
||||
|
||||
:param sub_cmd: A Config sub command.
|
||||
:param sub_cmd_params: Sub command specific parameters.
|
||||
:param pin_uv_protocol: PIN/UV auth protocol version used.
|
||||
:param pin_uv_param: PIN/UV Auth parameter.
|
||||
"""
|
||||
return self.send_vendor(
|
||||
cmd,
|
||||
args(sub_cmd, sub_cmd_params, pin_uv_protocol, pin_uv_param),
|
||||
)
|
||||
|
||||
|
||||
class Vendor:
|
||||
"""Implementation of the CTAP2.1 Authenticator Vendor API. It is vendor implementation.
|
||||
|
||||
:param ctap: An instance of a CTAP2Vendor object.
|
||||
:param pin_uv_protocol: An instance of a PinUvAuthProtocol.
|
||||
:param pin_uv_token: A valid PIN/UV Auth Token for the current CTAP session.
|
||||
"""
|
||||
|
||||
@unique
|
||||
class CMD(IntEnum):
|
||||
VENDOR_BACKUP = 0x01
|
||||
VENDOR_MSE = 0x02
|
||||
VENDOR_UNLOCK = 0x03
|
||||
VENDOR_EA = 0x04
|
||||
|
||||
@unique
|
||||
class PARAM(IntEnum):
|
||||
PARAM = 0x01
|
||||
COSE_KEY = 0x02
|
||||
|
||||
class SUBCMD(IntEnum):
|
||||
ENABLE = 0x01
|
||||
DISABLE = 0x02
|
||||
KEY_AGREEMENT = 0x01
|
||||
EA_CSR = 0x01
|
||||
EA_UPLOAD = 0x02
|
||||
|
||||
class RESP(IntEnum):
|
||||
PARAM = 0x01
|
||||
COSE_KEY = 0x02
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
ctap: Ctap2Vendor,
|
||||
pin_uv_protocol: Optional[PinProtocol] = None,
|
||||
pin_uv_token: Optional[bytes] = None,
|
||||
):
|
||||
self.ctap = ctap
|
||||
self.pin_uv = (
|
||||
_PinUv(pin_uv_protocol, pin_uv_token)
|
||||
if pin_uv_protocol and pin_uv_token
|
||||
else None
|
||||
)
|
||||
self.__key_enc = None
|
||||
self.__iv = None
|
||||
|
||||
self.vcfg = VendorConfig(ctap)
|
||||
|
||||
def _call(self, cmd, sub_cmd, params=None):
|
||||
if params:
|
||||
params = {k: v for k, v in params.items() if v is not None}
|
||||
else:
|
||||
params = None
|
||||
if self.pin_uv:
|
||||
msg = (
|
||||
b"\xff" * 32
|
||||
+ b"\x0d"
|
||||
+ struct.pack("<b", sub_cmd)
|
||||
+ (cbor.encode(params) if params else b"")
|
||||
)
|
||||
pin_uv_protocol = self.pin_uv.protocol.VERSION
|
||||
pin_uv_param = self.pin_uv.protocol.authenticate(self.pin_uv.token, msg)
|
||||
else:
|
||||
pin_uv_protocol = None
|
||||
pin_uv_param = None
|
||||
return self.ctap.vendor(cmd, sub_cmd, params, pin_uv_protocol, pin_uv_param)
|
||||
|
||||
def backup_save(self, filename):
|
||||
ret = self._call(
|
||||
Vendor.CMD.VENDOR_BACKUP,
|
||||
Vendor.SUBCMD.ENABLE,
|
||||
)
|
||||
data = ret[Vendor.RESP.PARAM]
|
||||
d = int.from_bytes(skey.get_secure_key(), 'big')
|
||||
with open(filename, 'wb') as fp:
|
||||
fp.write(b'\x01')
|
||||
fp.write(data)
|
||||
pk = ec.derive_private_key(d, ec.SECP256R1())
|
||||
signature = pk.sign(data, ec.ECDSA(hashes.SHA256()))
|
||||
fp.write(signature)
|
||||
print('Remember the following words in this order:')
|
||||
for c in range(24):
|
||||
coef = (d//(2048**c))%2048
|
||||
print(f'{(c+1):02d} - {words[coef]}')
|
||||
|
||||
def backup_load(self, filename):
|
||||
d = 0
|
||||
if (d == 0):
|
||||
for c in range(24):
|
||||
word = input(f'Introduce word {(c+1):02d}: ')
|
||||
while (word not in words):
|
||||
word = input(f'Word not found. Please, tntroduce the correct word {(c+1):02d}: ')
|
||||
coef = words.index(word)
|
||||
d = d+(2048**c)*coef
|
||||
|
||||
pk = ec.derive_private_key(d, ec.SECP256R1())
|
||||
pb = pk.public_key()
|
||||
with open(filename, 'rb') as fp:
|
||||
format = fp.read(1)[0]
|
||||
if (format == 0x1):
|
||||
data = fp.read(60)
|
||||
signature = fp.read()
|
||||
pb.verify(signature, data, ec.ECDSA(hashes.SHA256()))
|
||||
skey.set_secure_key(pk)
|
||||
return self._call(
|
||||
Vendor.CMD.VENDOR_BACKUP,
|
||||
Vendor.SUBCMD.DISABLE,
|
||||
{
|
||||
Vendor.PARAM.PARAM: data
|
||||
},
|
||||
)
|
||||
|
||||
def mse(self):
|
||||
sk = ec.generate_private_key(ec.SECP256R1())
|
||||
pn = sk.public_key().public_numbers()
|
||||
self.__pb = sk.public_key().public_bytes(Encoding.X962, PublicFormat.UncompressedPoint)
|
||||
key_agreement = {
|
||||
1: 2,
|
||||
3: -25, # Per the spec, "although this is NOT the algorithm actually used"
|
||||
-1: 1,
|
||||
-2: int2bytes(pn.x, 32),
|
||||
-3: int2bytes(pn.y, 32),
|
||||
}
|
||||
|
||||
ret = self._call(
|
||||
Vendor.CMD.VENDOR_MSE,
|
||||
Vendor.SUBCMD.KEY_AGREEMENT,
|
||||
{
|
||||
Vendor.PARAM.COSE_KEY: key_agreement,
|
||||
},
|
||||
)
|
||||
|
||||
peer_cose_key = ret[VendorConfig.RESP.KEY_AGREEMENT]
|
||||
|
||||
x = bytes2int(peer_cose_key[-2])
|
||||
y = bytes2int(peer_cose_key[-3])
|
||||
pk = ec.EllipticCurvePublicNumbers(x, y, ec.SECP256R1()).public_key()
|
||||
shared_key = sk.exchange(ec.ECDH(), pk)
|
||||
|
||||
xkdf = HKDF(
|
||||
algorithm=hashes.SHA256(),
|
||||
length=12+32,
|
||||
salt=None,
|
||||
info=self.__pb
|
||||
)
|
||||
kdf_out = xkdf.derive(shared_key)
|
||||
self.__key_enc = kdf_out[12:]
|
||||
self.__iv = kdf_out[:12]
|
||||
|
||||
def encrypt_chacha(self, data):
|
||||
chacha = ChaCha20Poly1305(self.__key_enc)
|
||||
ct = chacha.encrypt(self.__iv, data, self.__pb)
|
||||
return ct
|
||||
|
||||
def unlock_device(self):
|
||||
ct = self.get_skey()
|
||||
self._call(
|
||||
Vendor.CMD.VENDOR_UNLOCK,
|
||||
Vendor.SUBCMD.ENABLE,
|
||||
{
|
||||
Vendor.PARAM.PARAM: ct
|
||||
},
|
||||
)
|
||||
|
||||
def _get_key_device(self):
|
||||
return skey.get_secure_key()
|
||||
|
||||
def get_skey(self):
|
||||
self.mse()
|
||||
ct = self.encrypt_chacha(self._get_key_device())
|
||||
return ct
|
||||
|
||||
def enable_device_aut(self):
|
||||
ct = self.get_skey()
|
||||
self.vcfg.enable_device_aut(ct)
|
||||
|
||||
def disable_device_aut(self):
|
||||
self.vcfg.disable_device_aut()
|
||||
|
||||
def csr(self):
|
||||
return self._call(
|
||||
Vendor.CMD.VENDOR_EA,
|
||||
Vendor.SUBCMD.EA_CSR,
|
||||
)[Vendor.RESP.PARAM]
|
||||
|
||||
def upload_ea(self, der):
|
||||
self._call(
|
||||
Vendor.CMD.VENDOR_EA,
|
||||
Vendor.SUBCMD.EA_UPLOAD,
|
||||
{
|
||||
Vendor.PARAM.PARAM: der
|
||||
}
|
||||
)
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
subparser = parser.add_subparsers(title="commands", dest="command")
|
||||
parser_secure = subparser.add_parser('secure', help='Manages security of Pico Fido.')
|
||||
parser_secure.add_argument('subcommand', choices=['enable', 'disable', 'unlock'], help='Enables, disables or unlocks the security.')
|
||||
|
||||
parser_backup = subparser.add_parser('backup', help='Manages the backup of Pico Fido.')
|
||||
parser_backup.add_argument('subcommand', choices=['save', 'load'], help='Saves or loads a backup.')
|
||||
parser_backup.add_argument('filename', help='File to save or load the backup.')
|
||||
|
||||
parser_attestation = subparser.add_parser('attestation', help='Manages Enterprise Attestation')
|
||||
parser_attestation.add_argument('subcommand', choices=['csr'])
|
||||
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.')
|
||||
|
||||
args = parser.parse_args()
|
||||
return args
|
||||
|
||||
def secure(vdr, args):
|
||||
if (args.subcommand == 'enable'):
|
||||
vdr.enable_device_aut()
|
||||
elif (args.subcommand == 'unlock'):
|
||||
vdr.unlock_device()
|
||||
elif (args.subcommand == 'disable'):
|
||||
vdr.disable_device_aut()
|
||||
|
||||
def backup(vdr, args):
|
||||
if (args.subcommand == 'save'):
|
||||
vdr.backup_save(args.filename)
|
||||
elif (args.subcommand == 'load'):
|
||||
vdr.backup_load(args.filename)
|
||||
|
||||
def attestation(vdr, args):
|
||||
if (args.subcommand == 'csr'):
|
||||
if (args.filename is None):
|
||||
csr = x509.load_der_x509_csr(vdr.csr())
|
||||
data = urllib.parse.urlencode({'csr': csr.public_bytes(Encoding.PEM)}).encode()
|
||||
j = get_pki_data('csr', data=data)
|
||||
cert = x509.load_pem_x509_certificate(j['x509'].encode())
|
||||
else:
|
||||
with open(args.filename, 'rb') as f:
|
||||
dataf = f.read()
|
||||
try:
|
||||
cert = x509.load_der_x509_certificate(dataf)
|
||||
except ValueError:
|
||||
cert = x509.load_pem_x509_certificate(dataf)
|
||||
vdr.upload_ea(cert.public_bytes(Encoding.DER))
|
||||
|
||||
def main(args):
|
||||
print('Pico Fido Tool v1.4')
|
||||
print('Author: Pol Henarejos')
|
||||
print('Report bugs to https://github.com/polhenarejos/pico-fido/issues')
|
||||
print('')
|
||||
print('')
|
||||
|
||||
dev = next(CtapHidDevice.list_devices(), None)
|
||||
|
||||
vdr = Vendor(Ctap2Vendor(dev))
|
||||
|
||||
if (args.command == 'secure'):
|
||||
secure(vdr, args)
|
||||
elif (args.command == 'backup'):
|
||||
backup(vdr, args)
|
||||
elif (args.command == 'attestation'):
|
||||
attestation(vdr, args)
|
||||
|
||||
def run():
|
||||
args = parse_args()
|
||||
main(args)
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
59
tools/secure_key/macos.py
Normal file
59
tools/secure_key/macos.py
Normal file
@@ -0,0 +1,59 @@
|
||||
import sys
|
||||
import keyring
|
||||
|
||||
DOMAIN = "PicoKeys.com"
|
||||
USERNAME = "Pico-Fido"
|
||||
|
||||
try:
|
||||
import keyring
|
||||
from keyrings.osx_keychain_keys.backend import OSXKeychainKeysBackend, OSXKeychainKeyType, OSXKeyChainKeyClassType
|
||||
except:
|
||||
print('ERROR: keyring module not found! Install keyring package.\nTry with `pip install keyrings.osx-keychain-keys`')
|
||||
sys.exit(-1)
|
||||
|
||||
try:
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PrivateFormat, NoEncryption
|
||||
except:
|
||||
print('ERROR: cryptography module not found! Install cryptography package.\nTry with `pip install cryptography`')
|
||||
sys.exit(-1)
|
||||
|
||||
|
||||
def get_backend(use_secure_enclave=False):
|
||||
backend = OSXKeychainKeysBackend(
|
||||
key_type=OSXKeychainKeyType.EC, # Key type, e.g. RSA, RC, DSA, ...
|
||||
key_class_type=OSXKeyChainKeyClassType.Private, # Private key, Public key, Symmetric-key
|
||||
key_size_in_bits=256,
|
||||
is_permanent=True, # If set, saves the key in keychain; else, returns a transient key
|
||||
use_secure_enclave=use_secure_enclave, # Saves the key in the T2 (TPM) chip, requires a code-signed interpreter
|
||||
access_group=None, # Limits key management and retrieval to set group, requires a code-signed interpreter
|
||||
is_extractable=True # If set, private key is extractable; else, it can't be retrieved, but only operated against
|
||||
)
|
||||
return backend
|
||||
|
||||
def generate_secure_key(use_secure_enclave=False):
|
||||
backend = get_backend(use_secure_enclave)
|
||||
backend.set_password(DOMAIN, USERNAME, password=None)
|
||||
return backend.get_password(DOMAIN, USERNAME)
|
||||
|
||||
def get_d(key):
|
||||
return key.private_numbers().private_value.to_bytes(32, 'big')
|
||||
|
||||
def set_secure_key(pk):
|
||||
backend = get_backend(False)
|
||||
try:
|
||||
backend.delete_password(DOMAIN, USERNAME)
|
||||
except:
|
||||
pass
|
||||
backend.set_password(DOMAIN, USERNAME, pk.private_bytes(Encoding.PEM, PrivateFormat.TraditionalOpenSSL, NoEncryption()))
|
||||
|
||||
def get_secure_key():
|
||||
key = None
|
||||
try:
|
||||
backend = get_backend(False)
|
||||
key = backend.get_password(DOMAIN, USERNAME)[0]
|
||||
except keyring.errors.KeyringError:
|
||||
try:
|
||||
key = generate_secure_key(False)[0] # It should be True, but secure enclave causes python segfault
|
||||
except keyring.errors.PasswordSetError:
|
||||
key = generate_secure_key(False)[0]
|
||||
return get_d(key)
|
||||
1
tools/words.py
Normal file
1
tools/words.py
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user