Created
April 29, 2020 18:26
-
-
Save x1ddos/19cce770b2929bd6c0a4f933f527ecbe to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
diff --git src/fido2/ctap.c src/fido2/ctap.c | |
index 5eb8e72..3ccb9cb 100644 | |
--- src/fido2/ctap.c | |
+++ src/fido2/ctap.c | |
@@ -4,155 +4,113 @@ | |
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or | |
// http://opensource.org/licenses/MIT>, at your option. This file may not be | |
// copied, modified, or distributed except according to those terms. | |
-#include <inttypes.h> | |
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
-#include "ctap.h" | |
- | |
#include "cbor.h" | |
-#include "cose_key.h" | |
+ | |
+#include "ctap.h" | |
+#include "u2f.h" | |
#include "ctaphid.h" | |
#include "ctap_parse.h" | |
+#include "ctap_errors.h" | |
+#include "cose_key.h" | |
+#include "crypto.h" | |
+#include "util.h" | |
+#include "log.h" | |
#include "device.h" | |
+#include APP_CONFIG | |
+#include "wallet.h" | |
#include "extensions.h" | |
-#include "fido2_keys.h" | |
-#include "storage.h" | |
- | |
-#include <memory/memory.h> | |
-#include <crypto/sha2/sha256.h> | |
-#include <screen.h> | |
-#include <securechip/securechip.h> | |
-#include <ui/workflow_stack.h> | |
-#include <usb/usb_packet.h> | |
-#include <util.h> | |
-#include <workflow/confirm.h> | |
-#include <workflow/select_ctap_credential.h> | |
-#include <workflow/status.h> | |
-#include <workflow/unlock.h> | |
-/** | |
- * CTAP request codes. | |
- */ | |
-#define CTAP_REQ_MAKE_CREDENTIAL (0x01) | |
-#define CTAP_REQ_GET_ASSERTION (0x02) | |
-#define CTAP_REQ_CANCEL (0x03) | |
-#define CTAP_REQ_GET_INFO (0x04) | |
-#define CTAP_REQ_CLIENT_PIN (0x06) | |
-#define CTAP_REQ_RESET (0x07) | |
-#define CTAP_REQ_GET_NEXT_ASSERTION (0x08) | |
-#define CTAP_REQ_VENDOR_FIRST (0x40) | |
-#define CTAP_REQ_VENDOR_LAST (0xBF) | |
+#include "device.h" | |
+#include "data_migration.h" | |
-/** | |
- * Auth data flags, defined in [WebAuthn] sec. 6.1. Authenticator Data. | |
- */ | |
-/** | |
- * User is present/not present. | |
- */ | |
-#define CTAP_AUTH_DATA_FLAG_USER_PRESENT (1 << 0) | |
-/** | |
- * User is verified/not verified. | |
- */ | |
-#define CTAP_AUTH_DATA_FLAG_USER_VERIFIED (1 << 2) | |
-/** | |
- * Indicates whether the authenticator added attested credential data. | |
- */ | |
-#define CTAP_AUTH_DATA_FLAG_ATTESTED_CRED_DATA_INCLUDED (1 << 6) | |
-/** | |
- * Indicates if the authenticator data has extensions. | |
- */ | |
-#define CTAP_AUTH_DATA_FLAG_EXTENSION_DATA_INCLUDED (1 << 7) | |
+uint8_t PIN_TOKEN[PIN_TOKEN_SIZE]; | |
+uint8_t KEY_AGREEMENT_PUB[64]; | |
+static uint8_t KEY_AGREEMENT_PRIV[32]; | |
+static int8_t PIN_BOOT_ATTEMPTS_LEFT = PIN_BOOT_ATTEMPTS; | |
-/** | |
- * Response tags. | |
- */ | |
-#define CTAP_RESP_VERSIONS (0x1) | |
-#define CTAP_RESP_EXTENSIONS (0x2) | |
-#define CTAP_RESP_AAGUID (0x3) | |
-#define CTAP_RESP_OPTIONS (0x4) | |
-#define CTAP_RESP_MAX_MSG_SIZE (0x5) | |
-#define CTAP_RESP_PIN_PROTOCOLS (0x6) | |
- | |
-#define CTAP_RESP_FMT (0x01) | |
-#define CTAP_RESP_AUTH_DATA (0x02) | |
-#define CTAP_RESP_ATT_STMT (0x03) | |
- | |
-#define CTAP_RESP_CREDENTIAL (0x01) | |
-#define CTAP_RESP_SIGNATURE (0x03) | |
-#define CTAP_RESP_PUBKEY_CREDENTIAL_USER_ENTITY (0x04) | |
-#define CTAP_RESP_NUM_CREDENTIALS (0x05) | |
- | |
-#define CTAP_RESP_KEY_AGREEMENT (0x01) | |
-#define CTAP_RESP_PIN_TOKEN (0x02) | |
-#define CTAP_RESP_RETRIES (0x03) | |
- | |
- | |
-typedef struct { | |
- enum { | |
- CTAP_MAKE_CREDENTIAL_STARTED, | |
- CTAP_MAKE_CREDENTIAL_UNLOCKED, | |
- CTAP_MAKE_CREDENTIAL_WAIT_CONFIRM, | |
- CTAP_MAKE_CREDENTIAL_FINISHED, | |
- CTAP_MAKE_CREDENTIAL_FAILED, | |
- } state; | |
- ctap_make_credential_req_t req; | |
-} ctap_make_credential_state_t; | |
- | |
-typedef struct { | |
- enum { | |
- CTAP_GET_ASSERTION_STARTED, | |
- CTAP_GET_ASSERTION_UNLOCKED, | |
- CTAP_GET_ASSERTION_WAIT_CONFIRM, | |
- CTAP_GET_ASSERTION_CONFIRMED, | |
- CTAP_GET_ASSERTION_SELECT_CREDENTIAL, | |
- CTAP_GET_ASSERTION_SELECTED_CREDENTIAL, | |
- /** User aborted the request */ | |
- CTAP_GET_ASSERTION_DENIED, | |
- /** No valid credentials were found. */ | |
- CTAP_GET_ASSERTION_NO_CREDENTIALS, | |
- CTAP_GET_ASSERTION_FINISHED, | |
- } state; | |
- /** Key handle that was selected for authentication. */ | |
- u2f_keyhandle_t auth_credential; | |
- /** Private key corresponding to auth_credential. */ | |
- uint8_t auth_privkey[HMAC_SHA256_LEN]; | |
- /** User ID corresponding to auth_credential. | |
- * | |
- * When no allow list is present, it's mandatory that | |
- * we add a user ID to the credential we return. | |
- */ | |
- uint8_t user_id[CTAP_USER_ID_MAX_SIZE]; | |
- /** Actual size of the user ID. */ | |
- size_t user_id_size; | |
- /** | |
- * List of valid credentials for this GA request. | |
- */ | |
- ctap_credential_display_list_t cred_list; | |
- /** | |
- * For each credential in cred_list, save the index | |
- * that that credential has in the RK memory. | |
- */ | |
- int cred_idx[CTAP_CREDENTIAL_LIST_MAX_SIZE]; | |
- ctap_get_assertion_req_t req; | |
-} ctap_get_assertion_state_t; | |
- | |
-static struct { | |
- enum { | |
- CTAP_BLOCKING_OP_NONE, | |
- CTAP_BLOCKING_OP_MAKE_CRED, | |
- CTAP_BLOCKING_OP_GET_ASSERTION | |
- } blocking_op; | |
- union { | |
- ctap_make_credential_state_t make_cred; | |
- ctap_get_assertion_state_t get_assertion; | |
- } data; | |
-} _state = {0}; | |
- | |
-static uint8_t ctap_get_info(CborEncoder * encoder) | |
+AuthenticatorState STATE; | |
+ | |
+static void ctap_reset_key_agreement(); | |
+ | |
+struct _getAssertionState getAssertionState; | |
+ | |
+// Generate a mask to keep the confidentiality of the "metadata" field in the credential ID. | |
+// Mask = hmac(device-secret, 14-random-bytes-in-credential-id) | |
+// Masked_output = Mask ^ metadata | |
+static void add_masked_metadata_for_credential(CredentialId * credential, uint32_t cred_protect){ | |
+ uint8_t mask[32]; | |
+ crypto_sha256_hmac_init(CRYPTO_TRANSPORT_KEY, 0, mask); | |
+ crypto_sha256_update(credential->entropy.nonce, CREDENTIAL_NONCE_SIZE - 4); | |
+ crypto_sha256_hmac_final(CRYPTO_TRANSPORT_KEY,0, mask); | |
+ | |
+ credential->entropy.metadata.value = *((uint32_t*)mask) ^ cred_protect; | |
+} | |
+ | |
+static uint32_t read_metadata_from_masked_credential(CredentialId * credential){ | |
+ uint8_t mask[32]; | |
+ crypto_sha256_hmac_init(CRYPTO_TRANSPORT_KEY, 0, mask); | |
+ crypto_sha256_update(credential->entropy.nonce, CREDENTIAL_NONCE_SIZE - 4); | |
+ crypto_sha256_hmac_final(CRYPTO_TRANSPORT_KEY,0, mask); | |
+ | |
+ return credential->entropy.metadata.value ^ *((uint32_t*)mask); | |
+} | |
+ | |
+ | |
+ | |
+uint8_t check_credential_metadata(CredentialId * credential, uint8_t is_verified, uint8_t is_from_credid_list) | |
+{ | |
+ uint32_t cred_protect = read_metadata_from_masked_credential(credential); | |
+ switch (cred_protect){ | |
+ case EXT_CRED_PROTECT_OPTIONAL_WITH_CREDID: | |
+ if (!is_from_credid_list) { | |
+ if (!is_verified) | |
+ { | |
+ return CTAP2_ERR_NOT_ALLOWED; | |
+ } | |
+ } | |
+ break; | |
+ case EXT_CRED_PROTECT_REQUIRED: | |
+ if (!is_verified) | |
+ { | |
+ return CTAP2_ERR_NOT_ALLOWED; | |
+ } | |
+ break; | |
+ } | |
+ return 0; | |
+} | |
+ | |
+static uint8_t verify_pin_auth_ex(uint8_t * pinAuth, uint8_t *buf, size_t len) | |
+{ | |
+ uint8_t hmac[32]; | |
+ | |
+ crypto_sha256_hmac_init(PIN_TOKEN, PIN_TOKEN_SIZE, hmac); | |
+ crypto_sha256_update(buf, len); | |
+ crypto_sha256_hmac_final(PIN_TOKEN, PIN_TOKEN_SIZE, hmac); | |
+ | |
+ if (memcmp(pinAuth, hmac, 16) == 0) | |
+ { | |
+ return 0; | |
+ } | |
+ else | |
+ { | |
+ printf2(TAG_ERR,"Pin auth failed\n"); | |
+ dump_hex1(TAG_ERR,pinAuth,16); | |
+ dump_hex1(TAG_ERR,hmac,16); | |
+ return CTAP2_ERR_PIN_AUTH_INVALID; | |
+ } | |
+} | |
+ | |
+uint8_t verify_pin_auth(uint8_t * pinAuth, uint8_t * clientDataHash) | |
+{ | |
+ return verify_pin_auth_ex(pinAuth, clientDataHash, CLIENT_DATA_HASH_SIZE); | |
+} | |
+ | |
+uint8_t ctap_get_info(CborEncoder * encoder) | |
{ | |
- (void)_state; | |
int ret; | |
CborEncoder array; | |
CborEncoder map; | |
@@ -161,48 +119,54 @@ static uint8_t ctap_get_info(CborEncoder * encoder) | |
uint8_t aaguid[16]; | |
device_read_aaguid(aaguid); | |
- ret = cbor_encoder_create_map(encoder, &map, 6); | |
+ ret = cbor_encoder_create_map(encoder, &map, 8); | |
check_ret(ret); | |
{ | |
- ret = cbor_encode_uint(&map, CTAP_RESP_VERSIONS); // versions key | |
+ | |
+ ret = cbor_encode_uint(&map, RESP_versions); // versions key | |
check_ret(ret); | |
{ | |
- ret = cbor_encoder_create_array(&map, &array, 2); | |
+ ret = cbor_encoder_create_array(&map, &array, 3); | |
check_ret(ret); | |
{ | |
ret = cbor_encode_text_stringz(&array, "U2F_V2"); | |
check_ret(ret); | |
ret = cbor_encode_text_stringz(&array, "FIDO_2_0"); | |
check_ret(ret); | |
+ ret = cbor_encode_text_stringz(&array, "FIDO_2_1_PRE"); | |
+ check_ret(ret); | |
} | |
ret = cbor_encoder_close_container(&map, &array); | |
check_ret(ret); | |
} | |
- ret = cbor_encode_uint(&map, CTAP_RESP_EXTENSIONS); | |
+ ret = cbor_encode_uint(&map, RESP_extensions); | |
check_ret(ret); | |
{ | |
- ret = cbor_encoder_create_array(&map, &array, 1); | |
+ ret = cbor_encoder_create_array(&map, &array, 2); | |
check_ret(ret); | |
{ | |
ret = cbor_encode_text_stringz(&array, "hmac-secret"); | |
check_ret(ret); | |
+ | |
+ ret = cbor_encode_text_stringz(&array, "credProtect"); | |
+ check_ret(ret); | |
} | |
ret = cbor_encoder_close_container(&map, &array); | |
check_ret(ret); | |
} | |
- ret = cbor_encode_uint(&map, CTAP_RESP_AAGUID); | |
+ ret = cbor_encode_uint(&map, RESP_aaguid); | |
check_ret(ret); | |
{ | |
ret = cbor_encode_byte_string(&map, aaguid, 16); | |
check_ret(ret); | |
} | |
- ret = cbor_encode_uint(&map, CTAP_RESP_OPTIONS); | |
+ ret = cbor_encode_uint(&map, RESP_options); | |
check_ret(ret); | |
{ | |
- ret = cbor_encoder_create_map(&map, &options, 4); | |
+ ret = cbor_encoder_create_map(&map, &options,5); | |
check_ret(ret); | |
{ | |
ret = cbor_encode_text_string(&options, "rk", 2); | |
@@ -219,40 +183,53 @@ static uint8_t ctap_get_info(CborEncoder * encoder) | |
check_ret(ret); | |
} | |
- ret = cbor_encode_text_string(&options, "uv", 2); // Capable of verifying user | |
+ // NOT [yet] capable of verifying user | |
+ // Do not add option if UV isn't supported. | |
+ // | |
+ // ret = cbor_encode_text_string(&options, "uv", 2); | |
+ // check_ret(ret); | |
+ // { | |
+ // ret = cbor_encode_boolean(&options, 0); | |
+ // check_ret(ret); | |
+ // } | |
+ | |
+ ret = cbor_encode_text_string(&options, "plat", 4); | |
+ check_ret(ret); | |
+ { | |
+ ret = cbor_encode_boolean(&options, 0); // Not attached to platform | |
+ check_ret(ret); | |
+ } | |
+ | |
+ ret = cbor_encode_text_string(&options, "credMgmt", 8); | |
check_ret(ret); | |
{ | |
- /* | |
- * The option should be true/false based on whether the UV function has already | |
- * been initialized. | |
- */ | |
- ret = cbor_encode_boolean(&options, true); | |
+ ret = cbor_encode_boolean(&options, 1); | |
check_ret(ret); | |
} | |
- ret = cbor_encode_text_string(&options, "plat", 4); | |
+ ret = cbor_encode_text_string(&options, "clientPin", 9); | |
check_ret(ret); | |
{ | |
- ret = cbor_encode_boolean(&options, 0); // Not attached to platform | |
+ ret = cbor_encode_boolean(&options, ctap_is_pin_set()); | |
check_ret(ret); | |
} | |
- /* | |
- * We're not capable of PIN authentication, so the clientPin option | |
- * should be unset. | |
- */ | |
+ | |
+ | |
+ | |
+ | |
} | |
ret = cbor_encoder_close_container(&map, &options); | |
check_ret(ret); | |
} | |
- ret = cbor_encode_uint(&map, CTAP_RESP_MAX_MSG_SIZE); | |
+ ret = cbor_encode_uint(&map, RESP_maxMsgSize); | |
check_ret(ret); | |
{ | |
ret = cbor_encode_int(&map, CTAP_MAX_MESSAGE_SIZE); | |
check_ret(ret); | |
} | |
- ret = cbor_encode_uint(&map, CTAP_RESP_PIN_PROTOCOLS); | |
+ ret = cbor_encode_uint(&map, RESP_pinProtocols); | |
check_ret(ret); | |
{ | |
ret = cbor_encoder_create_array(&map, &pins, 1); | |
@@ -264,6 +241,22 @@ static uint8_t ctap_get_info(CborEncoder * encoder) | |
ret = cbor_encoder_close_container(&map, &pins); | |
check_ret(ret); | |
} | |
+ | |
+ | |
+ ret = cbor_encode_uint(&map, 0x07); //maxCredentialCountInList | |
+ check_ret(ret); | |
+ { | |
+ ret = cbor_encode_uint(&map, ALLOW_LIST_MAX_SIZE); | |
+ check_ret(ret); | |
+ } | |
+ | |
+ ret = cbor_encode_uint(&map, 0x08); // maxCredentialIdLength | |
+ check_ret(ret); | |
+ { | |
+ ret = cbor_encode_uint(&map, 128); | |
+ check_ret(ret); | |
+ } | |
+ | |
} | |
ret = cbor_encoder_close_container(encoder, &map); | |
check_ret(ret); | |
@@ -273,7 +266,7 @@ static uint8_t ctap_get_info(CborEncoder * encoder) | |
-static int ctap_add_cose_key(CborEncoder* cose_key, uint8_t* x, uint8_t* y, int32_t algtype) | |
+static int ctap_add_cose_key(CborEncoder * cose_key, uint8_t * x, uint8_t * y, uint8_t credtype, int32_t algtype) | |
{ | |
int ret; | |
CborEncoder map; | |
@@ -323,214 +316,491 @@ static int ctap_add_cose_key(CborEncoder* cose_key, uint8_t* x, uint8_t* y, int3 | |
return 0; | |
} | |
+static int ctap_generate_cose_key(CborEncoder * cose_key, uint8_t * hmac_input, int len, uint8_t credtype, int32_t algtype) | |
+{ | |
+ uint8_t x[32], y[32]; | |
-/** | |
- * Encode the 32bit U2F counter value as a big-endian | |
- * sequence of bytes. | |
- * @param counter Counter to encode. | |
- * @param buf_out Buffer in which to encode the counter. Must be 4 bytes wide. | |
- */ | |
-static void _encode_u2f_counter(uint32_t counter, uint8_t* buf_out) | |
+ if (credtype != PUB_KEY_CRED_PUB_KEY) | |
+ { | |
+ printf2(TAG_ERR,"Error, pubkey credential type not supported\n"); | |
+ return -1; | |
+ } | |
+ switch(algtype) | |
+ { | |
+ case COSE_ALG_ES256: | |
+ if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_FAST); | |
+ crypto_ecc256_derive_public_key(hmac_input, len, x, y); | |
+ if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_IDLE); | |
+ break; | |
+ default: | |
+ printf2(TAG_ERR,"Error, COSE alg %d not supported\n", algtype); | |
+ return -1; | |
+ } | |
+ int ret = ctap_add_cose_key(cose_key, x, y, credtype, algtype); | |
+ check_ret(ret); | |
+ return 0; | |
+} | |
+ | |
+void make_auth_tag(uint8_t * rpIdHash, uint8_t * nonce, uint32_t count, uint8_t * tag) | |
{ | |
- *buf_out++ = (counter >> 24) & 0xff; | |
- *buf_out++ = (counter >> 16) & 0xff; | |
- *buf_out++ = (counter >> 8) & 0xff; | |
- *buf_out++ = (counter >> 0) & 0xff; | |
+ uint8_t hashbuf[32]; | |
+ memset(hashbuf,0,sizeof(hashbuf)); | |
+ crypto_sha256_hmac_init(CRYPTO_TRANSPORT_KEY, 0, hashbuf); | |
+ crypto_sha256_update(rpIdHash, 32); | |
+ crypto_sha256_update(nonce, CREDENTIAL_NONCE_SIZE); | |
+ crypto_sha256_update((uint8_t*)&count, 4); | |
+ crypto_sha256_hmac_final(CRYPTO_TRANSPORT_KEY,0,hashbuf); | |
+ | |
+ memmove(tag, hashbuf, CREDENTIAL_TAG_SIZE); | |
} | |
-/** | |
- * Copy the source string into the destination buffer. | |
- * If the string is too long to fit the destination buffer, | |
- * truncate the string with "...", so that the resulting | |
- * string is always a valid NULL-terminated string. | |
- */ | |
-static void _copy_or_truncate(char* dst, size_t dst_size, const char* src) | |
+void ctap_flush_state() | |
{ | |
- size_t src_size = strlen(src); | |
- bool truncate = false; | |
+ authenticator_write_state(&STATE); | |
+} | |
- const char* padding = "..."; | |
- size_t padding_size = strlen(padding); | |
+static uint32_t auth_data_update_count(CTAP_authDataHeader * authData) | |
+{ | |
+ uint32_t count = ctap_atomic_count( 0 ); | |
+ if (count == 0) // count 0 will indicate invalid token | |
+ { | |
+ count = ctap_atomic_count( 0 ); | |
- if (dst_size < src_size + 1) { | |
- /* | |
- * String is too long. | |
- * Truncate the source string to the biggest possible size. | |
- */ | |
- truncate = true; | |
- src_size = dst_size - 1 - padding_size; | |
} | |
- strncpy(dst, src, src_size); | |
- if (!truncate) { | |
- dst[src_size] = '\0'; | |
- } else { | |
- strcpy(dst + src_size, padding); | |
- dst[src_size + padding_size] = '\0'; | |
+ uint8_t * byte = (uint8_t*) &authData->signCount; | |
+ | |
+ *byte++ = (count >> 24) & 0xff; | |
+ *byte++ = (count >> 16) & 0xff; | |
+ *byte++ = (count >> 8) & 0xff; | |
+ *byte++ = (count >> 0) & 0xff; | |
+ | |
+ return count; | |
+} | |
+ | |
+static void ctap_increment_rk_store() | |
+{ | |
+ STATE.rk_stored++; | |
+ ctap_flush_state(); | |
+} | |
+static void ctap_decrement_rk_store() | |
+{ | |
+ STATE.rk_stored--; | |
+ ctap_flush_state(); | |
+} | |
+ | |
+// Return 1 if rk is valid, 0 if not. | |
+static int ctap_rk_is_valid(CTAP_residentKey * rk) | |
+{ | |
+ return (rk->id.count > 0 && rk->id.count != 0xffffffff); | |
+} | |
+ | |
+static int load_nth_valid_rk(int n, CTAP_residentKey * rk) { | |
+ | |
+ int valid_count = 0; | |
+ unsigned int i; | |
+ for (i = 0; i < ctap_rk_size(); i++) | |
+ { | |
+ ctap_load_rk(i, rk); | |
+ if ( ctap_rk_is_valid(rk) ) { | |
+ if (valid_count == n) { | |
+ return i; | |
+ } | |
+ valid_count++; | |
+ } | |
} | |
+ return -1; | |
} | |
-static int _is_matching_rk(ctap_resident_key_t* rk, ctap_resident_key_t* rk2) | |
+static int is_matching_rk(CTAP_residentKey * rk, CTAP_residentKey * rk2) | |
{ | |
- return (memcmp(rk->rp_id_hash, rk2->rp_id_hash, 32) == 0) && | |
- (memcmp(rk->rp_id, rk2->rp_id, CTAP_STORAGE_RP_ID_MAX_SIZE) == 0) && | |
- (memcmp(rk->user_name, rk2->user_name, CTAP_STORAGE_USER_NAME_LIMIT) == 0); | |
+ return (memcmp(rk->id.rpIdHash, rk2->id.rpIdHash, 32) == 0) && | |
+ (memcmp(rk->user.id, rk2->user.id, rk->user.id_size) == 0) && | |
+ (rk->user.id_size == rk2->user.id_size); | |
} | |
-static void _get_assertion_unlock_cb(bool result, void* param) { | |
- (void)param; | |
- if (!result) { | |
- /* | |
- * User didn't authenticate. | |
- * Let's count this as a "user denied" error. | |
- */ | |
- _state.data.get_assertion.state = CTAP_GET_ASSERTION_DENIED; | |
- return; | |
+static int ctap_make_extensions(CTAP_extensions * ext, uint8_t * ext_encoder_buf, unsigned int * ext_encoder_buf_size) | |
+{ | |
+ CborEncoder extensions; | |
+ int ret; | |
+ uint8_t extensions_used = 0; | |
+ uint8_t hmac_secret_output_is_valid = 0; | |
+ uint8_t hmac_secret_requested_is_valid = 0; | |
+ uint8_t cred_protect_is_valid = 0; | |
+ uint8_t hmac_secret_output[64]; | |
+ uint8_t shared_secret[32]; | |
+ uint8_t hmac[32]; | |
+ uint8_t credRandom[32]; | |
+ uint8_t saltEnc[64]; | |
+ | |
+ if (ext->hmac_secret_present == EXT_HMAC_SECRET_PARSED) | |
+ { | |
+ printf1(TAG_CTAP, "Processing hmac-secret..\r\n"); | |
+ memmove(saltEnc, ext->hmac_secret.saltEnc, sizeof(saltEnc)); | |
+ | |
+ crypto_ecc256_shared_secret((uint8_t*) &ext->hmac_secret.keyAgreement.pubkey, | |
+ KEY_AGREEMENT_PRIV, | |
+ shared_secret); | |
+ crypto_sha256_init(); | |
+ crypto_sha256_update(shared_secret, 32); | |
+ crypto_sha256_final(shared_secret); | |
+ | |
+ crypto_sha256_hmac_init(shared_secret, 32, hmac); | |
+ crypto_sha256_update(saltEnc, ext->hmac_secret.saltLen); | |
+ crypto_sha256_hmac_final(shared_secret, 32, hmac); | |
+ | |
+ if (memcmp(ext->hmac_secret.saltAuth, hmac, 16) == 0) | |
+ { | |
+ printf1(TAG_CTAP, "saltAuth is valid\r\n"); | |
+ } | |
+ else | |
+ { | |
+ printf1(TAG_CTAP, "saltAuth is invalid\r\n"); | |
+ return CTAP2_ERR_EXTENSION_FIRST; | |
+ } | |
+ | |
+ // Generate credRandom | |
+ crypto_sha256_hmac_init(CRYPTO_TRANSPORT_KEY2, 0, credRandom); | |
+ crypto_sha256_update((uint8_t*)&ext->hmac_secret.credential->id, sizeof(CredentialId)); | |
+ crypto_sha256_update(&getAssertionState.user_verified, 1); | |
+ crypto_sha256_hmac_final(CRYPTO_TRANSPORT_KEY2, 0, credRandom); | |
+ | |
+ // Decrypt saltEnc | |
+ crypto_aes256_init(shared_secret, NULL); | |
+ crypto_aes256_decrypt(saltEnc, ext->hmac_secret.saltLen); | |
+ | |
+ // Generate outputs | |
+ crypto_sha256_hmac_init(credRandom, 32, hmac_secret_output); | |
+ crypto_sha256_update(saltEnc, 32); | |
+ crypto_sha256_hmac_final(credRandom, 32, hmac_secret_output); | |
+ | |
+ if (ext->hmac_secret.saltLen == 64) | |
+ { | |
+ crypto_sha256_hmac_init(credRandom, 32, hmac_secret_output + 32); | |
+ crypto_sha256_update(saltEnc + 32, 32); | |
+ crypto_sha256_hmac_final(credRandom, 32, hmac_secret_output + 32); | |
+ } | |
+ | |
+ // Encrypt for final output | |
+ crypto_aes256_init(shared_secret, NULL); | |
+ crypto_aes256_encrypt(hmac_secret_output, ext->hmac_secret.saltLen); | |
+ | |
+ | |
+ extensions_used += 1; | |
+ hmac_secret_output_is_valid = 1; | |
+ } | |
+ else if (ext->hmac_secret_present == EXT_HMAC_SECRET_REQUESTED) | |
+ { | |
+ extensions_used += 1; | |
+ hmac_secret_requested_is_valid = 1; | |
+ } | |
+ if (ext->cred_protect != EXT_CRED_PROTECT_INVALID) { | |
+ if ( | |
+ ext->cred_protect == EXT_CRED_PROTECT_OPTIONAL || | |
+ ext->cred_protect == EXT_CRED_PROTECT_OPTIONAL_WITH_CREDID || | |
+ ext->cred_protect == EXT_CRED_PROTECT_REQUIRED | |
+ ) | |
+ { | |
+ extensions_used += 1; | |
+ cred_protect_is_valid = 1; | |
+ } | |
+ } | |
+ | |
+ if (extensions_used > 0) | |
+ { | |
+ | |
+ // output | |
+ cbor_encoder_init(&extensions, ext_encoder_buf, *ext_encoder_buf_size, 0); | |
+ { | |
+ CborEncoder extension_output_map; | |
+ ret = cbor_encoder_create_map(&extensions, &extension_output_map, extensions_used); | |
+ check_ret(ret); | |
+ if (hmac_secret_output_is_valid) { | |
+ { | |
+ ret = cbor_encode_text_stringz(&extension_output_map, "hmac-secret"); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_encode_byte_string(&extension_output_map, hmac_secret_output, ext->hmac_secret.saltLen); | |
+ check_ret(ret); | |
+ } | |
+ } | |
+ if (hmac_secret_requested_is_valid) { | |
+ { | |
+ ret = cbor_encode_text_stringz(&extension_output_map, "hmac-secret"); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_encode_boolean(&extension_output_map, 1); | |
+ check_ret(ret); | |
+ } | |
+ } | |
+ if (cred_protect_is_valid) { | |
+ { | |
+ ret = cbor_encode_text_stringz(&extension_output_map, "credProtect"); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_encode_int(&extension_output_map, ext->cred_protect); | |
+ check_ret(ret); | |
+ } | |
+ } | |
+ ret = cbor_encoder_close_container(&extensions, &extension_output_map); | |
+ check_ret(ret); | |
+ | |
+ } | |
+ *ext_encoder_buf_size = cbor_encoder_get_buffer_size(&extensions, ext_encoder_buf); | |
+ | |
+ } else | |
+ { | |
+ *ext_encoder_buf_size = 0; | |
} | |
- _state.data.get_assertion.state = CTAP_GET_ASSERTION_UNLOCKED; | |
+ | |
+ | |
+ | |
+ return 0; | |
+} | |
+ | |
+static unsigned int get_credential_id_size(int type) | |
+{ | |
+ if (type == PUB_KEY_CRED_CTAP1) | |
+ return U2F_KEY_HANDLE_SIZE; | |
+ if (type == PUB_KEY_CRED_CUSTOM) | |
+ return getAssertionState.customCredIdSize; | |
+ return sizeof(CredentialId); | |
} | |
-static void _get_assertion_allow_cb(bool result, void* param) | |
+static int ctap2_user_presence_test() | |
{ | |
- (void)param; | |
- ctap_get_assertion_state_t* state = &_state.data.get_assertion; | |
- if (result) { | |
- state->state = CTAP_GET_ASSERTION_CONFIRMED; | |
- } else { | |
- state->state = CTAP_GET_ASSERTION_DENIED; | |
+ device_set_status(CTAPHID_STATUS_UPNEEDED); | |
+ int ret = ctap_user_presence_test(CTAP2_UP_DELAY_MS); | |
+ if ( ret > 1 ) | |
+ { | |
+ return CTAP2_ERR_PROCESSING; | |
+ } | |
+ else if ( ret > 0 ) | |
+ { | |
+ return CTAP1_ERR_SUCCESS; | |
+ } | |
+ else if (ret < 0) | |
+ { | |
+ return CTAP2_ERR_KEEPALIVE_CANCEL; | |
+ } | |
+ else | |
+ { | |
+ return CTAP2_ERR_ACTION_TIMEOUT; | |
} | |
} | |
-static workflow_t* _get_assertion_confirm(ctap_rp_id_t* rp) | |
+static int ctap_make_auth_data(struct rpId * rp, CborEncoder * map, uint8_t * auth_data_buf, uint32_t * len, CTAP_credInfo * credInfo, CTAP_extensions * extensions) | |
{ | |
- char prompt_buf[100]; | |
- size_t prompt_size; | |
- if (rp->name && rp->name[0] != '\0') { | |
- /* There is a human-readable name attached to this domain. */ | |
- prompt_size = snprintf(prompt_buf, 100, "Authenticate on\n%s\n(%.*s)\nsize %u", | |
- rp->name, (int)rp->size, rp->id, sizeof(_state)); | |
- } else { | |
- prompt_size = snprintf(prompt_buf, 100, "Authenticate on\n%.*s\nsize %u", | |
- (int)rp->size, rp->id, sizeof(_state)); | |
+ CborEncoder cose_key; | |
+ | |
+ unsigned int auth_data_sz = sizeof(CTAP_authDataHeader); | |
+ uint32_t count; | |
+ CTAP_residentKey rk, rk2; | |
+ CTAP_authData * authData = (CTAP_authData *)auth_data_buf; | |
+ | |
+ uint8_t * cose_key_buf = auth_data_buf + sizeof(CTAP_authData); | |
+ | |
+ // memset(&cose_key, 0, sizeof(CTAP_residentKey)); | |
+ memset(&rk, 0, sizeof(CTAP_residentKey)); | |
+ memset(&rk2, 0, sizeof(CTAP_residentKey)); | |
+ | |
+ if((sizeof(CTAP_authDataHeader)) > *len) | |
+ { | |
+ printf1(TAG_ERR,"assertion fail, auth_data_buf must be at least %d bytes\n", sizeof(CTAP_authData) - sizeof(CTAP_attestHeader)); | |
+ exit(1); | |
+ } | |
+ | |
+ crypto_sha256_init(); | |
+ crypto_sha256_update(rp->id, rp->size); | |
+ crypto_sha256_final(authData->head.rpIdHash); | |
+ | |
+ count = auth_data_update_count(&authData->head); | |
+ | |
+ int but; | |
+ | |
+ but = ctap2_user_presence_test(); | |
+ if (CTAP2_ERR_PROCESSING == but) | |
+ { | |
+ authData->head.flags = (0 << 0); // User presence disabled | |
+ } | |
+ else | |
+ { | |
+ check_retr(but); | |
+ authData->head.flags = (1 << 0); // User presence | |
} | |
- if (prompt_size >= 100) { | |
- prompt_buf[99] = '\0'; | |
+ | |
+ | |
+ device_set_status(CTAPHID_STATUS_PROCESSING); | |
+ | |
+ authData->head.flags |= (ctap_is_pin_set() << 2); | |
+ | |
+ | |
+ if (credInfo != NULL) | |
+ { | |
+ // add attestedCredentialData | |
+ authData->head.flags |= (1 << 6);//include attestation data | |
+ | |
+ cbor_encoder_init(&cose_key, cose_key_buf, *len - sizeof(CTAP_authData), 0); | |
+ | |
+ device_read_aaguid(authData->attest.aaguid); | |
+ authData->attest.credLenL = sizeof(CredentialId) & 0x00FF; | |
+ authData->attest.credLenH = (sizeof(CredentialId) & 0xFF00) >> 8; | |
+ | |
+ memset((uint8_t*)&authData->attest.id, 0, sizeof(CredentialId)); | |
+ | |
+ ctap_generate_rng(authData->attest.id.entropy.nonce, CREDENTIAL_NONCE_SIZE); | |
+ add_masked_metadata_for_credential(&authData->attest.id, extensions->cred_protect); | |
+ | |
+ authData->attest.id.count = count; | |
+ | |
+ memmove(authData->attest.id.rpIdHash, authData->head.rpIdHash, 32); | |
+ | |
+ // Make a tag we can later check to make sure this is a token we made | |
+ make_auth_tag(authData->head.rpIdHash, authData->attest.id.entropy.nonce, count, authData->attest.id.tag); | |
+ | |
+ // resident key | |
+ if (credInfo->rk) | |
+ { | |
+ memmove(&rk.id, &authData->attest.id, sizeof(CredentialId)); | |
+ memmove(&rk.user, &credInfo->user, sizeof(CTAP_userEntity)); | |
+ | |
+ // Copy rpId to RK, but it could be cropped. | |
+ int rp_id_size = rp->size < sizeof(rk.rpId) ? rp->size : sizeof(rk.rpId); | |
+ memmove(rk.rpId, rp->id, rp_id_size); | |
+ rk.rpIdSize = rp_id_size; | |
+ | |
+ unsigned int index = STATE.rk_stored; | |
+ unsigned int i; | |
+ for (i = 0; i < index; i++) | |
+ { | |
+ int raw_i = load_nth_valid_rk(i, &rk2); | |
+ if (is_matching_rk(&rk, &rk2)) | |
+ { | |
+ ctap_overwrite_rk(raw_i, &rk); | |
+ goto done_rk; | |
+ } | |
+ } | |
+ for (i = 0; i < ctap_rk_size(); i++){ | |
+ ctap_load_rk(i, &rk2); | |
+ if ( ! ctap_rk_is_valid(&rk2) ){ | |
+ ctap_increment_rk_store(); | |
+ ctap_store_rk(i, &rk); | |
+ printf1(TAG_GREEN, "Created rk %d:", i); dump_hex1(TAG_GREEN, rk.id.rpIdHash, 32); | |
+ goto done_rk; | |
+ } | |
+ } | |
+ | |
+ printf2(TAG_ERR, "Out of memory for resident keys\r\n"); | |
+ return CTAP2_ERR_KEY_STORE_FULL; | |
+ } | |
+done_rk: | |
+ | |
+ printf1(TAG_GREEN, "MADE credId: "); dump_hex1(TAG_GREEN, (uint8_t*) &authData->attest.id, sizeof(CredentialId)); | |
+ | |
+ ctap_generate_cose_key(&cose_key, (uint8_t*)&authData->attest.id, sizeof(CredentialId), credInfo->publicKeyCredentialType, credInfo->COSEAlgorithmIdentifier); | |
+ | |
+ auth_data_sz = sizeof(CTAP_authData) + cbor_encoder_get_buffer_size(&cose_key, cose_key_buf); | |
+ | |
} | |
- const confirm_params_t params = { | |
- .title = "FIDO2", | |
- .body = prompt_buf, | |
- .scrollable = false, | |
- }; | |
- return workflow_confirm(¶ms, _get_assertion_allow_cb, NULL); | |
+ | |
+ | |
+ | |
+ | |
+ *len = auth_data_sz; | |
+ return 0; | |
} | |
+ | |
/** | |
- * @param sig[in] Location to deposit signature (must be 64 bytes) | |
- * @param out_encoded_sig[in] Location to deposit der signature (must be 72 bytes) | |
- * @return Length of DER encoded signature. | |
+ * | |
+ * @param in_sigbuf IN location to deposit signature (must be 64 bytes) | |
+ * @param out_sigder OUT location to deposit der signature (must be 72 bytes) | |
+ * @return length of der signature | |
* // FIXME add tests for maximum and minimum length of the input and output | |
*/ | |
-static int _encode_der_sig(const uint8_t* sig, uint8_t* out_encoded_sig) | |
+int ctap_encode_der_sig(const uint8_t * const in_sigbuf, uint8_t * const out_sigder) | |
{ | |
// Need to caress into dumb der format .. | |
+ uint8_t i; | |
uint8_t lead_s = 0; // leading zeros | |
uint8_t lead_r = 0; | |
- for (int i=0; i < 32; i++) { | |
- if (sig[i] == 0) { | |
- lead_r++; | |
- } | |
- else { | |
- break; | |
- } | |
- } | |
+ for (i=0; i < 32; i++) | |
+ if (in_sigbuf[i] == 0) lead_r++; | |
+ else break; | |
- for (int i=0; i < 32; i++) { | |
- if (sig[i+32] == 0) { | |
- lead_s++; | |
- } | |
- else { | |
- break; | |
- } | |
- } | |
+ for (i=0; i < 32; i++) | |
+ if (in_sigbuf[i+32] == 0) lead_s++; | |
+ else break; | |
- int8_t pad_s = ((sig[32 + lead_s] & 0x80) == 0x80); | |
- int8_t pad_r = ((sig[0 + lead_r] & 0x80) == 0x80); | |
+ int8_t pad_s = ((in_sigbuf[32 + lead_s] & 0x80) == 0x80); | |
+ int8_t pad_r = ((in_sigbuf[0 + lead_r] & 0x80) == 0x80); | |
- memset(out_encoded_sig, 0, 72); | |
- out_encoded_sig[0] = 0x30; | |
- out_encoded_sig[1] = 0x44 + pad_s + pad_r - lead_s - lead_r; | |
+ memset(out_sigder, 0, 72); | |
+ out_sigder[0] = 0x30; | |
+ out_sigder[1] = 0x44 + pad_s + pad_r - lead_s - lead_r; | |
// R ingredient | |
- out_encoded_sig[2] = 0x02; | |
- out_encoded_sig[3 + pad_r] = 0; | |
- out_encoded_sig[3] = 0x20 + pad_r - lead_r; | |
- memmove(out_encoded_sig + 4 + pad_r, sig + lead_r, 32u - lead_r); | |
+ out_sigder[2] = 0x02; | |
+ out_sigder[3 + pad_r] = 0; | |
+ out_sigder[3] = 0x20 + pad_r - lead_r; | |
+ memmove(out_sigder + 4 + pad_r, in_sigbuf + lead_r, 32u - lead_r); | |
// S ingredient | |
- out_encoded_sig[4 + 32 + pad_r - lead_r] = 0x02; | |
- out_encoded_sig[5 + 32 + pad_r + pad_s - lead_r] = 0; | |
- out_encoded_sig[5 + 32 + pad_r - lead_r] = 0x20 + pad_s - lead_s; | |
- memmove(out_encoded_sig + 6 + 32 + pad_r + pad_s - lead_r, sig + 32u + lead_s, 32u - lead_s); | |
+ out_sigder[4 + 32 + pad_r - lead_r] = 0x02; | |
+ out_sigder[5 + 32 + pad_r + pad_s - lead_r] = 0; | |
+ out_sigder[5 + 32 + pad_r - lead_r] = 0x20 + pad_s - lead_s; | |
+ memmove(out_sigder + 6 + 32 + pad_r + pad_s - lead_r, in_sigbuf + 32u + lead_s, 32u - lead_s); | |
return 0x46 + pad_s + pad_r - lead_r - lead_s; | |
} | |
-/** | |
- * Computes the EC256 | |
- * See [WebAuthn], 8.2 "Signing procedure" | |
- * require load_key prior to this | |
- * @param[in] auth_data Authenticator data for the attestation. | |
- * @param[in] auth_data_len Length of auth_data. | |
- * @param[in] client_data_hash Hash of the serialized client data. | |
- * @param[out] sigbuf_out Buffer in which to store the computed signature | |
- */ | |
-static bool _calculate_signature(const uint8_t* privkey, uint8_t* auth_data, size_t auth_data_len, uint8_t* client_data_hash, uint8_t* sigbuf_out) | |
+// require load_key prior to this | |
+// @data data to hash before signature | |
+// @clientDataHash for signature | |
+// @tmp buffer for hash. (can be same as data if data >= 32 bytes) | |
+// @sigbuf OUT location to deposit signature (must be 64 bytes) | |
+// @sigder OUT location to deposit der signature (must be 72 bytes) | |
+// @return length of der signature | |
+int ctap_calculate_signature(uint8_t * data, int datalen, uint8_t * clientDataHash, uint8_t * hashbuf, uint8_t * sigbuf, uint8_t * sigder) | |
{ | |
- uint8_t hash_buf[SHA256_LEN]; | |
- | |
- sha256_context_t ctx; | |
- sha256_reset(&ctx); | |
- noise_sha256_update(&ctx, auth_data, auth_data_len); | |
- noise_sha256_update(&ctx, client_data_hash, CLIENT_DATA_HASH_SIZE); | |
- sha256_finish(&ctx, hash_buf); | |
- if (!securechip_ecc_unsafe_sign(privkey, hash_buf, sigbuf_out)) { | |
- return false; | |
- } | |
- return true; | |
+ // calculate attestation sig | |
+ crypto_sha256_init(); | |
+ crypto_sha256_update(data, datalen); | |
+ crypto_sha256_update(clientDataHash, CLIENT_DATA_HASH_SIZE); | |
+ crypto_sha256_final(hashbuf); | |
+ | |
+ crypto_ecc256_sign(hashbuf, 32, sigbuf); | |
+ return ctap_encode_der_sig(sigbuf,sigder); | |
} | |
-/** | |
- * Adds the encoding of an attestation statement into a CBOR encoder. | |
- * | |
- * @param map[in] Encoder in which to append the attestation statement. | |
- * @param signature[in] Signature to add to the statement. | |
- * @param len[in] Length of signature. | |
- * @return Error code (or 0 for success). | |
- */ | |
-static uint8_t _add_attest_statement(CborEncoder* map, const uint8_t* signature, int len) | |
+uint8_t ctap_add_attest_statement(CborEncoder * map, uint8_t * sigder, int len) | |
{ | |
int ret; | |
- /* TODO: simo: generate another cert? */ | |
- const uint8_t *cert = FIDO2_ATT_CERT; | |
- uint16_t cert_size = FIDO2_ATT_CERT_SIZE; | |
- | |
+ uint8_t cert[1024]; | |
+ uint16_t cert_size = device_attestation_cert_der_get_size(); | |
+ if (cert_size > sizeof(cert)){ | |
+ printf2(TAG_ERR,"Certificate is too large for CTAP2 buffer\r\n"); | |
+ return CTAP2_ERR_PROCESSING; | |
+ } | |
+ device_attestation_read_cert_der(cert); | |
+ | |
CborEncoder stmtmap; | |
CborEncoder x5carr; | |
- ret = cbor_encode_int(map, CTAP_RESP_ATT_STMT); | |
+ ret = cbor_encode_int(map,RESP_attStmt); | |
check_ret(ret); | |
ret = cbor_encoder_create_map(map, &stmtmap, 3); | |
check_ret(ret); | |
{ | |
ret = cbor_encode_text_stringz(&stmtmap,"alg"); | |
check_ret(ret); | |
- ret = cbor_encode_int(&stmtmap, COSE_ALG_ES256); | |
+ ret = cbor_encode_int(&stmtmap,COSE_ALG_ES256); | |
check_ret(ret); | |
} | |
{ | |
ret = cbor_encode_text_stringz(&stmtmap,"sig"); | |
check_ret(ret); | |
- ret = cbor_encode_byte_string(&stmtmap, signature, len); | |
+ ret = cbor_encode_byte_string(&stmtmap, sigder, len); | |
check_ret(ret); | |
} | |
{ | |
@@ -539,7 +809,7 @@ static uint8_t _add_attest_statement(CborEncoder* map, const uint8_t* signature, | |
ret = cbor_encoder_create_array(&stmtmap, &x5carr, 1); | |
check_ret(ret); | |
{ | |
- ret = cbor_encode_byte_string(&x5carr, cert, cert_size); | |
+ ret = cbor_encode_byte_string(&x5carr, cert, device_attestation_cert_der_get_size()); | |
check_ret(ret); | |
ret = cbor_encoder_close_container(&stmtmap, &x5carr); | |
check_ret(ret); | |
@@ -551,430 +821,197 @@ static uint8_t _add_attest_statement(CborEncoder* map, const uint8_t* signature, | |
return 0; | |
} | |
-/** | |
- * Computes the sha256 hash of the given RP id. | |
- * @param rp_hash_out Buffer in which to store the computed hash. | |
- * Must be SHA256_LEN bytes wide. | |
- */ | |
-static void _compute_rpid_hash(ctap_rp_id_t* rp, uint8_t* rp_hash_out) { | |
- if (wally_sha256(rp->id, rp->size, rp_hash_out, SHA256_LEN) != WALLY_OK) { | |
- Abort("wally_sha256 failed"); | |
- } | |
-} | |
- | |
- | |
-/** | |
- * Asks the user for confirmation when | |
- * a stored FIDO2 credential is about | |
- * to be overwritten with a new one for | |
- * the same user. | |
- */ | |
-static bool _confirm_overwrite_credential(void) { | |
- /* TODO */ | |
- return true; | |
-} | |
- | |
-/** | |
- * Check if any of the keys in a MakeCredential's | |
- * exclude_list belong to our device. | |
- * | |
- * @param req MakeCredential request to analyze. | |
- * @return Verification status: | |
- * - 0 if no invalid key was found; | |
- * - CTAP2_ERR_CREDENTIAL_EXCLUDED if an excluded key belongs to us; | |
- * - other errors if we failed to parse the exclude list. | |
- */ | |
-static uint8_t _verify_exclude_list(ctap_make_credential_req_t* req) | |
+// Return 1 if credential belongs to this token | |
+int ctap_authenticate_credential(struct rpId * rp, CTAP_credentialDescriptor * desc) | |
{ | |
- for (size_t i = 0; i < req->exclude_list_size; i++) { | |
- u2f_keyhandle_t excl_cred; | |
- bool cred_valid; | |
- uint8_t ret = ctap_parse_credential_descriptor(&req->exclude_list, &excl_cred, &cred_valid); | |
- if (!cred_valid || ret == CTAP2_ERR_CBOR_UNEXPECTED_TYPE) { | |
- /* Skip credentials that fail to parse. */ | |
- continue; | |
- } | |
- if (ret != CborNoError) { | |
- return ret; | |
- } | |
+ uint8_t rpIdHash[32]; | |
+ uint8_t tag[16]; | |
- uint8_t privkey[HMAC_SHA256_LEN]; | |
- UTIL_CLEANUP_32(privkey); | |
- bool key_is_ours = u2f_keyhandle_verify(req->rp.id, (uint8_t*)&excl_cred, sizeof(excl_cred), privkey); | |
- if (key_is_ours) | |
- { | |
- return true; | |
- } | |
+ switch(desc->type) | |
+ { | |
+ case PUB_KEY_CRED_PUB_KEY: | |
+ crypto_sha256_init(); | |
+ crypto_sha256_update(rp->id, rp->size); | |
+ crypto_sha256_final(rpIdHash); | |
- ret = cbor_value_advance(&req->exclude_list); | |
- check_ret(ret); | |
+ printf1(TAG_RED,"rpId: %s\r\n", rp->id); dump_hex1(TAG_RED,rp->id, rp->size); | |
+ if (memcmp(desc->credential.id.rpIdHash, rpIdHash, 32) != 0) | |
+ { | |
+ return 0; | |
+ } | |
+ make_auth_tag(rpIdHash, desc->credential.id.entropy.nonce, desc->credential.id.count, tag); | |
+ return (memcmp(desc->credential.id.tag, tag, CREDENTIAL_TAG_SIZE) == 0); | |
+ break; | |
+ case PUB_KEY_CRED_CTAP1: | |
+ crypto_sha256_init(); | |
+ crypto_sha256_update(rp->id, rp->size); | |
+ crypto_sha256_final(rpIdHash); | |
+ return u2f_authenticate_credential((struct u2f_key_handle *)&desc->credential.id, U2F_KEY_HANDLE_SIZE,rpIdHash); | |
+ break; | |
+ case PUB_KEY_CRED_CUSTOM: | |
+ return is_extension_request(getAssertionState.customCredId, getAssertionState.customCredIdSize); | |
+ break; | |
+ default: | |
+ printf1(TAG_ERR, "PUB_KEY_CRED_UNKNOWN %x\r\n",desc->type); | |
+ break; | |
} | |
- return false; | |
-} | |
-static bool _ask_generic_authorization(void) { | |
- const confirm_params_t params = { | |
- .title = "FIDO2", | |
- .body = "Proceed?", | |
- }; | |
- return workflow_confirm_blocking(¶ms); | |
+ return 0; | |
} | |
-/** | |
- * Called after the user has confirmed (or declined) the | |
- * creation of a new credential. | |
- */ | |
-static void _make_credential_allow_cb(bool result, void* param) { | |
- (void)param; | |
- if (result) { | |
- _state.data.make_cred.state = CTAP_MAKE_CREDENTIAL_FINISHED; | |
- } else { | |
- _state.data.make_cred.state = CTAP_MAKE_CREDENTIAL_FAILED; | |
- } | |
-} | |
-/** | |
- * Asks the user whether he wants to proceed | |
- * with the creation of a new credential. | |
- * @param req MakeCredential CTAP request. | |
- * @return Confirmation workflow. | |
- */ | |
-static workflow_t* _make_credential_allow(ctap_make_credential_req_t* req) | |
-{ | |
- char prompt_buf[100]; | |
- size_t prompt_size; | |
- if (req->rp.name && req->rp.name[0] != '\0') { | |
- /* There is a human-readable name attached to this domain. */ | |
- prompt_size = snprintf(prompt_buf, 100, "Create credential for\n%s\n(%.*s)\n", | |
- req->rp.name, (int)req->rp.size, req->rp.id); | |
- } else { | |
- prompt_size = snprintf(prompt_buf, 100, "Create credential for\n%.*s\n", | |
- (int)req->rp.size, req->rp.id); | |
- } | |
- if (prompt_size >= 100) { | |
- prompt_buf[99] = '\0'; | |
- } | |
- const confirm_params_t params = { | |
- .title = "FIDO2", | |
- .body = prompt_buf, | |
- }; | |
- return workflow_confirm(¶ms, _make_credential_allow_cb, NULL); | |
-} | |
- | |
-static void _make_credential_unlock_cb(bool result, void* param) { | |
- (void)param; | |
- //screen_sprintf_debug(1000, "UNLOCK CB %d", result); | |
- if (!result) { | |
- /* | |
- * User didn't authenticate. | |
- * Let's count this as a "user denied" error. | |
- */ | |
- _state.data.make_cred.state = CTAP_MAKE_CREDENTIAL_FAILED; | |
- return; | |
- } | |
- _state.data.make_cred.state = CTAP_MAKE_CREDENTIAL_UNLOCKED; | |
-} | |
- | |
-static void _make_credential_init_state(ctap_make_credential_req_t* req) | |
-{ | |
- _state.data.make_cred.state = CTAP_MAKE_CREDENTIAL_STARTED; | |
- memcpy(&_state.data.make_cred.req, req, sizeof(*req)); | |
-} | |
-static void _make_credential_free_state(void) | |
+uint8_t ctap_make_credential(CborEncoder * encoder, uint8_t * request, int length) | |
{ | |
-} | |
- | |
-static uint8_t ctap_make_credential(CborEncoder * encoder, const in_buffer_t* in_buffer) { | |
- ctap_make_credential_req_t MC; | |
+ CTAP_makeCredential MC; | |
int ret; | |
+ unsigned int i; | |
+ uint8_t auth_data_buf[310]; | |
+ CTAP_credentialDescriptor * excl_cred = (CTAP_credentialDescriptor *) auth_data_buf; | |
+ uint8_t * sigbuf = auth_data_buf + 32; | |
+ uint8_t * sigder = auth_data_buf + 32 + 64; | |
- ret = ctap_parse_make_credential(&MC,encoder, in_buffer); | |
+ ret = ctap_parse_make_credential(&MC,encoder,request,length); | |
- if (ret != 0) { | |
+ if (ret != 0) | |
+ { | |
+ printf2(TAG_ERR,"error, parse_make_credential failed\n"); | |
return ret; | |
} | |
- if (MC.pin_auth_empty) { | |
- /* | |
- * pin_auth was present and was an empty string. | |
- * The client is asking us if we support pin | |
- * (and asks to check for user presence before we move on). | |
- */ | |
- bool result = _ask_generic_authorization(); | |
- if (!result) { | |
- return CTAP2_ERR_OPERATION_DENIED; | |
- } | |
- /* We don't support PIN semantics. */ | |
- return CTAP2_ERR_PIN_NOT_SET; | |
+ if (MC.pinAuthEmpty) | |
+ { | |
+ ret = ctap2_user_presence_test(); | |
+ check_retr(ret); | |
+ return ctap_is_pin_set() == 1 ? CTAP2_ERR_PIN_AUTH_INVALID : CTAP2_ERR_PIN_NOT_SET; | |
+ } | |
+ if ((MC.paramsParsed & MC_requiredMask) != MC_requiredMask) | |
+ { | |
+ printf2(TAG_ERR,"error, required parameter(s) for makeCredential are missing\n"); | |
+ return CTAP2_ERR_MISSING_PARAMETER; | |
} | |
- if (MC.pin_auth_present) { | |
- /* We don't support pin_auth. */ | |
- return CTAP2_ERR_PIN_AUTH_INVALID; | |
+ if (ctap_is_pin_set() == 1 && MC.pinAuthPresent == 0) | |
+ { | |
+ printf2(TAG_ERR,"pinAuth is required\n"); | |
+ return CTAP2_ERR_PIN_REQUIRED; | |
+ } | |
+ else | |
+ { | |
+ if (ctap_is_pin_set() || (MC.pinAuthPresent)) | |
+ { | |
+ ret = verify_pin_auth(MC.pinAuth, MC.clientDataHash); | |
+ check_retr(ret); | |
+ } | |
} | |
- if (MC.up == 1 || MC.up == 0) { | |
- /* | |
- * The UP flag can't be set for authenticatorMakeCredential. | |
- * It must always be unset (0xFF). | |
- */ | |
+ if (MC.up == 1 || MC.up == 0) | |
+ { | |
return CTAP2_ERR_INVALID_OPTION; | |
} | |
- _make_credential_init_state(&MC); | |
- workflow_stack_start_workflow(workflow_unlock(_make_credential_unlock_cb, NULL)); | |
- return CTAP1_ERR_SUCCESS; | |
-} | |
+ // crypto_aes256_init(CRYPTO_TRANSPORT_KEY, NULL); | |
+ for (i = 0; i < MC.excludeListSize; i++) | |
+ { | |
+ ret = parse_credential_descriptor(&MC.excludeList, excl_cred); | |
+ if (ret == CTAP2_ERR_CBOR_UNEXPECTED_TYPE) | |
+ { | |
+ continue; | |
+ } | |
+ check_retr(ret); | |
-/** | |
- * Generates a new credential in response to a MakeCredential request. | |
- * Only called when the user has already accepted and identified with the device. | |
- * | |
- * @return CTAP status code. | |
- */ | |
+ printf1(TAG_GREEN, "checking credId: "); dump_hex1(TAG_GREEN, (uint8_t*) &excl_cred->credential.id, sizeof(CredentialId)); | |
-static int _make_credential_complete(buffer_t* out_buf) | |
-{ | |
- ctap_make_credential_state_t* state = &_state.data.make_cred; | |
- /* | |
- * The exclude list contains a list of credentials that we | |
- * must check. If any credential was generated by our device, | |
- * we must return with an error. This allows the server to avoid | |
- * us creating more than one credential for the same user/device pair. | |
- */ | |
- int ret = _verify_exclude_list(&state->req); | |
- if (ret != CborNoError) { | |
- return CTAP2_ERR_CBOR_PARSING; | |
- } | |
- | |
- /* Update the U2F counter. */ | |
- uint32_t u2f_counter; | |
- if (!securechip_u2f_counter_inc(&u2f_counter)) { | |
- workflow_status_blocking("Failed to create key.", false); | |
- return CTAP2_ERR_OPERATION_DENIED; | |
- } | |
- | |
- ctap_auth_data_t auth_data; | |
- _compute_rpid_hash(&state->req.rp, auth_data.head.rp_id_hash); | |
- | |
- /* Generate the key. */ | |
- memset((uint8_t*)&auth_data.attest.id, 0, sizeof(u2f_keyhandle_t)); | |
- uint8_t* nonce = auth_data.attest.id.nonce; | |
- uint8_t* mac = auth_data.attest.id.mac; | |
- uint8_t pubkey[64]; | |
- uint8_t privkey[HMAC_SHA256_LEN]; | |
- UTIL_CLEANUP_32(privkey); | |
- bool key_create_success = u2f_keyhandle_create_key(state->req.rp.id, nonce, privkey, mac, pubkey); | |
- if (!key_create_success) { | |
- /* TODO: simo: do something. */ | |
- Abort("Failed to create new FIDO2 key."); | |
- } | |
- | |
- /* | |
- * Find where to store this key. | |
- * If it's new, store it in the first | |
- * available location. Otherwise, overwrite | |
- * the existing key (after confirming with the user). | |
- */ | |
- if (state->req.cred_info.rk) { | |
- ctap_resident_key_t rk_to_store; | |
- memset(&rk_to_store, 0, sizeof(rk_to_store)); | |
- memcpy(&rk_to_store.key_handle, &auth_data.attest.id, sizeof(rk_to_store.key_handle)); | |
- memcpy(&rk_to_store.rp_id_hash, auth_data.head.rp_id_hash, sizeof(auth_data.head.rp_id_hash)); | |
- _copy_or_truncate((char*)rk_to_store.rp_id, sizeof(rk_to_store.rp_id), (const char*)state->req.rp.id); | |
- _copy_or_truncate((char*)rk_to_store.user_name, sizeof(rk_to_store.user_name), (const char*)state->req.cred_info.user.name); | |
- _copy_or_truncate((char*)rk_to_store.display_name, sizeof(rk_to_store.display_name), (const char*)state->req.cred_info.user.display_name); | |
- rk_to_store.valid = CTAP_RESIDENT_KEY_VALID; | |
- rk_to_store.creation_time = u2f_counter; | |
- if (state->req.cred_info.user.id_size > CTAP_USER_ID_MAX_SIZE) { | |
- /* We can't store such a big user ID. | |
- * But we can't even truncate it... So nothing we can do, alas. | |
- */ | |
- return CTAP2_ERR_REQUEST_TOO_LARGE; | |
- } | |
- //screen_sprintf_debug(2000, "UID (%u): %02x%02x", | |
- //state->req.cred_info.user.id_size, | |
- //state->req.cred_info.user.id[0], state->req.cred_info.user.id[state->req.cred_info.user.id_size - 1] | |
- //); | |
- rk_to_store.user_id_size = state->req.cred_info.user.id_size; | |
- memcpy(rk_to_store.user_id, state->req.cred_info.user.id, state->req.cred_info.user.id_size); | |
- | |
- int store_location = 0; | |
- bool must_overwrite = false; | |
- bool free_spot_found = false; | |
- | |
- for (int i = 0; i < MEMORY_CTAP_RESIDENT_KEYS_MAX; i++) { | |
- /* Check if we want to overwrite */ | |
- ctap_resident_key_t this_key; | |
- bool mem_result = memory_get_ctap_resident_key(i, &this_key); | |
- if (!mem_result) { | |
- /* Skip on error */ | |
- continue; | |
- } | |
- if (this_key.valid != CTAP_RESIDENT_KEY_VALID) { | |
- /* Skip invalid keys, mark spot as free */ | |
- if (!free_spot_found) { | |
- store_location = i; | |
- free_spot_found = true; | |
- } | |
- continue; | |
- } | |
- if (_is_matching_rk(&rk_to_store, &this_key)) { | |
- /* Found a matching key. Need to overwrite. */ | |
- free_spot_found = true; | |
- must_overwrite = true; | |
- store_location = i; | |
- break; | |
- } | |
- } | |
- if (!free_spot_found) { | |
- workflow_status_blocking("Out of memory for resident keys", false); | |
- return CTAP2_ERR_KEY_STORE_FULL; | |
- } | |
- if (must_overwrite) { | |
- if (!_confirm_overwrite_credential()) { | |
- workflow_status_blocking("Operation cancelled", false); | |
- return CTAP2_ERR_OPERATION_DENIED; | |
+ if (ctap_authenticate_credential(&MC.rp, excl_cred)) | |
+ { | |
+ if ( check_credential_metadata(&excl_cred->credential.id, MC.pinAuthPresent, 1) == 0) | |
+ { | |
+ ret = ctap2_user_presence_test(); | |
+ check_retr(ret); | |
+ printf1(TAG_MC, "Cred %d failed!\r\n",i); | |
+ return CTAP2_ERR_CREDENTIAL_EXCLUDED; | |
} | |
} | |
- memory_store_ctap_resident_key(store_location, &rk_to_store); | |
- //screen_sprintf_debug(500, "Stored key #%d\n", store_location); | |
- //uint8_t* cred_raw = (uint8_t*)&rk_to_store.key_handle; | |
- //screen_sprintf_debug(3000, "KH: %02x..%02x", | |
- //cred_raw[0], cred_raw[15]); | |
- } else { | |
- //screen_print_debug("Not stored key\n", 500); | |
- } | |
- | |
- /* | |
- * Now create the response. | |
- * This is an attestation object, as defined | |
- * in [WebAuthn], 6.4 (Figure 5). | |
- */ | |
- CborEncoder encoder; | |
- CborEncoder attest_obj; | |
- memset(&encoder,0,sizeof(CborEncoder)); | |
- cbor_encoder_init(&encoder, out_buf->data, out_buf->max_len, 0); | |
- ret = cbor_encoder_create_map(&encoder, &attest_obj, 3); | |
- check_ret(ret); | |
- | |
- /* | |
- * First comes the Authenticator Data. | |
- * (Note: the rpId has already been stored at the start of auth_data...) | |
- */ | |
- auth_data.head.flags = CTAP_AUTH_DATA_FLAG_ATTESTED_CRED_DATA_INCLUDED | | |
- CTAP_AUTH_DATA_FLAG_USER_VERIFIED | CTAP_AUTH_DATA_FLAG_USER_PRESENT; | |
- _encode_u2f_counter(u2f_counter, (uint8_t*)&auth_data.head.signCount); | |
- | |
- device_read_aaguid(auth_data.attest.aaguid); | |
- | |
- /* Encode the length of the key handle in big endian. */ | |
- uint16_t key_length = sizeof(u2f_keyhandle_t); | |
- auth_data.attest.cred_len[0] = (key_length & 0xFF00) >> 8; | |
- auth_data.attest.cred_len[1] = (key_length & 0x00FF); | |
- | |
- CborEncoder cose_key; | |
- uint8_t* cose_key_buf = auth_data.other; | |
- cbor_encoder_init(&cose_key, cose_key_buf, sizeof(auth_data.other), 0); | |
- ret = ctap_add_cose_key(&cose_key, pubkey, pubkey + 32, COSE_ALG_ES256); | |
- if (ret != CborNoError) { | |
- return ret; | |
+ ret = cbor_value_advance(&MC.excludeList); | |
+ check_ret(ret); | |
} | |
- size_t cose_key_len = cbor_encoder_get_buffer_size(&cose_key, cose_key_buf); | |
- size_t actual_auth_data_len = sizeof(auth_data) - sizeof(auth_data.other) + cose_key_len; | |
- /* FUTURE: manage extensions if we want to. */ | |
- /* | |
- * 3 fields in an attestation object: | |
- * - fmt | |
- * - authData | |
- * - attStmt | |
- */ | |
+ CborEncoder map; | |
+ ret = cbor_encoder_create_map(encoder, &map, 3); | |
+ check_ret(ret); | |
+ | |
{ | |
- ret = cbor_encode_int(&attest_obj, CTAP_RESP_FMT); | |
+ ret = cbor_encode_int(&map,RESP_fmt); | |
check_ret(ret); | |
- ret = cbor_encode_text_stringz(&attest_obj, "packed"); | |
+ ret = cbor_encode_text_stringz(&map, "packed"); | |
check_ret(ret); | |
} | |
+ uint32_t auth_data_sz = sizeof(auth_data_buf); | |
+ | |
+ ret = ctap_make_auth_data(&MC.rp, &map, auth_data_buf, &auth_data_sz, | |
+ &MC.credInfo, &MC.extensions); | |
+ check_retr(ret); | |
+ | |
+ { | |
+ unsigned int ext_encoder_buf_size = sizeof(auth_data_buf) - auth_data_sz; | |
+ uint8_t * ext_encoder_buf = auth_data_buf + auth_data_sz; | |
+ | |
+ ret = ctap_make_extensions(&MC.extensions, ext_encoder_buf, &ext_encoder_buf_size); | |
+ check_retr(ret); | |
+ if (ext_encoder_buf_size) | |
+ { | |
+ ((CTAP_authData *)auth_data_buf)->head.flags |= (1 << 7); | |
+ auth_data_sz += ext_encoder_buf_size; | |
+ } | |
+ } | |
{ | |
- ret = cbor_encode_int(&attest_obj, CTAP_RESP_AUTH_DATA); | |
+ ret = cbor_encode_int(&map,RESP_authData); | |
check_ret(ret); | |
- ret = cbor_encode_byte_string(&attest_obj, (uint8_t*)&auth_data, actual_auth_data_len); | |
+ ret = cbor_encode_byte_string(&map, auth_data_buf, auth_data_sz); | |
check_ret(ret); | |
} | |
- /* Compute the attestation statement. */ | |
- uint8_t sigbuf[32]; | |
- bool sig_success = _calculate_signature(FIDO2_ATT_PRIV_KEY, (uint8_t*)&auth_data, actual_auth_data_len, state->req.client_data_hash, sigbuf); | |
- if (!sig_success) { | |
- return CTAP1_ERR_OTHER; | |
- } | |
- uint8_t attest_signature[72]; | |
- int attest_sig_size = _encode_der_sig(sigbuf, attest_signature); | |
+ crypto_ecc256_load_attestation_key(); | |
+ int sigder_sz = ctap_calculate_signature(auth_data_buf, auth_data_sz, MC.clientDataHash, auth_data_buf, sigbuf, sigder); | |
+ printf1(TAG_MC,"der sig [%d]: ", sigder_sz); dump_hex1(TAG_MC, sigder, sigder_sz); | |
- ret = _add_attest_statement(&attest_obj, attest_signature, attest_sig_size); | |
- if (ret != CborNoError) { | |
- return ret; | |
- } | |
+ ret = ctap_add_attest_statement(&map, sigder, sigder_sz); | |
+ check_retr(ret); | |
- ret = cbor_encoder_close_container(&encoder, &attest_obj); | |
+ ret = cbor_encoder_close_container(encoder, &map); | |
check_ret(ret); | |
- //workflow_status_create("Registration\ncompleted.", true); | |
- out_buf->len = cbor_encoder_get_buffer_size(&encoder, out_buf->data); | |
return CTAP1_ERR_SUCCESS; | |
} | |
-static ctap_request_result_t _make_credential_continue(buffer_t* out_buf) { | |
- ctap_request_result_t result = {.status = 0, .request_completed = true}; | |
- ctap_make_credential_state_t* state = &_state.data.make_cred; | |
- | |
- switch (state->state) { | |
- case CTAP_MAKE_CREDENTIAL_UNLOCKED: | |
- /* | |
- * Request permission to the user. | |
- * This must be done before checking for excluded credentials etc. | |
- * so that we don't reveal the existance of credentials without | |
- * the user's consent. | |
- */ | |
- workflow_stack_start_workflow(_make_credential_allow(&_state.data.make_cred.req)); | |
- state->state = CTAP_MAKE_CREDENTIAL_WAIT_CONFIRM; | |
- result.request_completed = false; | |
- return result; | |
- case CTAP_MAKE_CREDENTIAL_FINISHED: | |
- result.status = _make_credential_complete(out_buf); | |
- return result; | |
- case CTAP_MAKE_CREDENTIAL_FAILED: | |
- workflow_status_blocking("Operation cancelled", false); | |
- result.status = CTAP2_ERR_OPERATION_DENIED; | |
- return result; | |
- case CTAP_MAKE_CREDENTIAL_STARTED: | |
- case CTAP_MAKE_CREDENTIAL_WAIT_CONFIRM: | |
- result.request_completed = false; | |
- return result; | |
- default: | |
- Abort("Invalid make_credential state"); | |
- } | |
-} | |
- | |
-static uint8_t ctap_add_credential_descriptor(CborEncoder* map, u2f_keyhandle_t* key_handle) | |
+/*static int pick_first_authentic_credential(CTAP_getAssertion * GA)*/ | |
+/*{*/ | |
+ /*int i;*/ | |
+ /*for (i = 0; i < GA->credLen; i++)*/ | |
+ /*{*/ | |
+ /*if (GA->creds[i].credential.enc.count != 0)*/ | |
+ /*{*/ | |
+ /*return i;*/ | |
+ /*}*/ | |
+ /*}*/ | |
+ /*return -1;*/ | |
+/*}*/ | |
+ | |
+static uint8_t ctap_add_credential_descriptor(CborEncoder * map, struct Credential * cred, int type) | |
{ | |
CborEncoder desc; | |
- int ret = cbor_encode_int(map, CTAP_RESP_CREDENTIAL); | |
- check_ret(ret); | |
- ret = cbor_encoder_create_map(map, &desc, 2); | |
+ int ret = cbor_encoder_create_map(map, &desc, 2); | |
check_ret(ret); | |
{ | |
ret = cbor_encode_text_string(&desc, "id", 2); | |
check_ret(ret); | |
- ret = cbor_encode_byte_string(&desc, (uint8_t*)key_handle, | |
- sizeof(*key_handle)); | |
+ ret = cbor_encode_byte_string(&desc, (uint8_t*)&cred->id, | |
+ get_credential_id_size(type)); | |
check_ret(ret); | |
} | |
@@ -993,40 +1030,48 @@ static uint8_t ctap_add_credential_descriptor(CborEncoder* map, u2f_keyhandle_t* | |
return 0; | |
} | |
-/** | |
- * Comparator function used to qsort() the credentials. | |
- * @return >0 if b is more recent than a, 0 if they have the same age (should never happen!), | |
- * <0 otherwise. | |
- */ | |
-static int _compare_display_credentials(const void * _a, const void * _b) | |
-{ | |
- const ctap_credential_display_t* a = (const ctap_credential_display_t* )_a; | |
- const ctap_credential_display_t* b = (const ctap_credential_display_t* )_b; | |
- return b->creation_time - a->creation_time; | |
-} | |
- | |
-/** | |
- * Adds the "publickKeyCredentialUserEntity" field to a CBOR | |
- * object, containing the specified user id as its only field. | |
- * | |
- * @param user_id must be at least user_id_size wide. | |
- * @param user_id_size Length of user_id. | |
- */ | |
-static uint8_t _encode_user_id(CborEncoder* map, const uint8_t* user_id, size_t user_id_size) | |
+uint8_t ctap_add_user_entity(CborEncoder * map, CTAP_userEntity * user, int is_verified) | |
{ | |
CborEncoder entity; | |
- int ret = cbor_encode_int(map, CTAP_RESP_PUBKEY_CREDENTIAL_USER_ENTITY); | |
+ int dispname = (user->name[0] != 0) && is_verified; | |
+ int ret; | |
+ int map_size = 1; | |
+ | |
+ if (dispname) | |
+ { | |
+ map_size = strlen((const char *)user->icon) > 0 ? 4 : 3; | |
+ } | |
+ ret = cbor_encoder_create_map(map, &entity, map_size); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_encode_text_string(&entity, "id", 2); | |
check_ret(ret); | |
- ret = cbor_encoder_create_map(map, &entity, 1); | |
+ ret = cbor_encode_byte_string(&entity, user->id, user->id_size); | |
check_ret(ret); | |
+ if (dispname) | |
{ | |
- ret = cbor_encode_text_string(&entity, "id", 2); | |
+ if (strlen((const char *)user->icon) > 0) | |
+ { | |
+ ret = cbor_encode_text_string(&entity, "icon", 4); | |
+ check_ret(ret); | |
+ ret = cbor_encode_text_stringz(&entity, (const char *)user->icon); | |
+ check_ret(ret); | |
+ } | |
+ | |
+ ret = cbor_encode_text_string(&entity, "name", 4); | |
check_ret(ret); | |
- ret = cbor_encode_byte_string(&entity, user_id, user_id_size); | |
+ ret = cbor_encode_text_stringz(&entity, (const char *)user->name); | |
check_ret(ret); | |
+ | |
+ ret = cbor_encode_text_string(&entity, "displayName", 11); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_encode_text_stringz(&entity, (const char *)user->displayName); | |
+ check_ret(ret); | |
+ | |
} | |
ret = cbor_encoder_close_container(map, &entity); | |
@@ -1035,468 +1080,1573 @@ static uint8_t _encode_user_id(CborEncoder* map, const uint8_t* user_id, size_t | |
return 0; | |
} | |
-/** | |
- * Fills a getAssertion response, as defined in the FIDO2 specs, 5.2. | |
- * | |
- * The response map contains: | |
- * - Credential descriptor | |
- * - Auth data | |
- * - Signature | |
- * - User ID (if present) | |
- * | |
- * Note that we don't include any user data as there is no need for that | |
- * (the user has already been selected on the device). | |
- */ | |
-static uint8_t ctap_end_get_assertion(CborEncoder* encoder, u2f_keyhandle_t* key_handle, uint8_t* auth_data_buf, unsigned int auth_data_buf_sz, uint8_t* privkey, uint8_t* client_data_hash, const uint8_t* user_id, size_t user_id_size) | |
+static int cred_cmp_func(const void * _a, const void * _b) | |
{ | |
- int ret; | |
- uint8_t signature[64]; | |
- uint8_t encoded_sig[72]; | |
- int encoded_sig_size; | |
+ CTAP_credentialDescriptor * a = (CTAP_credentialDescriptor * )_a; | |
+ CTAP_credentialDescriptor * b = (CTAP_credentialDescriptor * )_b; | |
+ return b->credential.id.count - a->credential.id.count; | |
+} | |
- CborEncoder map; | |
- int map_size = 3; | |
- if (user_id_size) { | |
- map_size++; | |
- } | |
- | |
- ret = cbor_encoder_create_map(encoder, &map, map_size); | |
- check_ret(ret); | |
+static void add_existing_user_info(CTAP_credentialDescriptor * cred) | |
+{ | |
+ CTAP_residentKey rk; | |
+ int index = STATE.rk_stored; | |
+ int i; | |
+ for (i = 0; i < index; i++) | |
+ { | |
+ load_nth_valid_rk(i, &rk); | |
+ if (is_matching_rk(&rk, (CTAP_residentKey *)&cred->credential)) | |
+ { | |
+ printf1(TAG_GREEN, "found rk match for allowList item (%d)\r\n", i); | |
+ memmove(&cred->credential.user, &rk.user, sizeof(CTAP_userEntity)); | |
+ return; | |
+ } | |
- ret = ctap_add_credential_descriptor(&map, key_handle); // 1 | |
- if (ret != CborNoError) { | |
- return ret; | |
} | |
+ printf1(TAG_GREEN, "NO rk match for allowList item \r\n"); | |
+} | |
+// @return the number of valid credentials | |
+// sorts the credentials. Most recent creds will be first, invalid ones last. | |
+int ctap_filter_invalid_credentials(CTAP_getAssertion * GA) | |
+{ | |
+ unsigned int i; | |
+ int count = 0; | |
+ uint8_t rpIdHash[32]; | |
+ CTAP_residentKey rk; | |
+ | |
+ for (i = 0; i < (unsigned int)GA->credLen; i++) | |
{ | |
- ret = cbor_encode_int(&map, CTAP_RESP_AUTH_DATA); // 2 | |
- check_ret(ret); | |
- ret = cbor_encode_byte_string(&map, auth_data_buf, auth_data_buf_sz); | |
- check_ret(ret); | |
- } | |
+ if (! ctap_authenticate_credential(&GA->rp, &GA->creds[i])) | |
+ { | |
+ printf1(TAG_GA, "CRED #%d is invalid\n", GA->creds[i].credential.id.count); | |
+#ifdef ENABLE_U2F_EXTENSIONS | |
+ if (is_extension_request((uint8_t*)&GA->creds[i].credential.id, sizeof(CredentialId))) | |
+ { | |
+ printf1(TAG_EXT, "CRED #%d is extension\n", GA->creds[i].credential.id.count); | |
+ count++; | |
+ } | |
+ else | |
+#endif | |
+ { | |
+ GA->creds[i].credential.id.count = 0; // invalidate | |
+ } | |
- bool sig_success = _calculate_signature(privkey, auth_data_buf, auth_data_buf_sz, client_data_hash, signature); | |
- if (!sig_success) { | |
- return CTAP1_ERR_OTHER; | |
+ } | |
+ else | |
+ { | |
+ | |
+ int protection_status = | |
+ check_credential_metadata(&GA->creds[i].credential.id, getAssertionState.user_verified, 1); | |
+ | |
+ if (protection_status != 0) { | |
+ printf1(TAG_GREEN,"skipping protected wrapped credential.\r\n"); | |
+ GA->creds[i].credential.id.count = 0; // invalidate | |
+ } | |
+ else | |
+ { | |
+ // add user info if it exists | |
+ add_existing_user_info(&GA->creds[i]); | |
+ count++; | |
+ } | |
+ | |
+ } | |
} | |
- encoded_sig_size = _encode_der_sig(signature, encoded_sig); | |
+ // No allowList, so use all matching RK's matching rpId | |
+ if (!GA->credLen) | |
{ | |
- ret = cbor_encode_int(&map, CTAP_RESP_SIGNATURE); // 3 | |
- check_ret(ret); | |
- ret = cbor_encode_byte_string(&map, encoded_sig, encoded_sig_size); | |
- check_ret(ret); | |
+ crypto_sha256_init(); | |
+ crypto_sha256_update(GA->rp.id,GA->rp.size); | |
+ crypto_sha256_final(rpIdHash); | |
+ | |
+ printf1(TAG_GREEN, "true rpIdHash: "); dump_hex1(TAG_GREEN, rpIdHash, 32); | |
+ for(i = 0; i < ctap_rk_size(); i++) | |
+ { | |
+ ctap_load_rk(i, &rk); | |
+ if (! ctap_rk_is_valid(&rk)) { | |
+ continue; | |
+ } | |
+ | |
+ printf1(TAG_GREEN, "rpIdHash%d: ", i); dump_hex1(TAG_GREEN, rk.id.rpIdHash, 32); | |
+ | |
+ int protection_status = | |
+ check_credential_metadata(&rk.id, getAssertionState.user_verified, 0); | |
+ | |
+ if (protection_status != 0) { | |
+ printf1(TAG_GREEN,"skipping protected rk credential.\r\n"); | |
+ continue; | |
+ } | |
+ | |
+ if (memcmp(rk.id.rpIdHash, rpIdHash, 32) == 0) | |
+ { | |
+ printf1(TAG_GA, "RK %d is a rpId match!\r\n", i); | |
+ if (count == ALLOW_LIST_MAX_SIZE-1) | |
+ { | |
+ printf2(TAG_ERR, "not enough ram allocated for matching RK's (%d). Skipping.\r\n", count); | |
+ break; | |
+ } | |
+ GA->creds[count].type = PUB_KEY_CRED_PUB_KEY; | |
+ memmove(&(GA->creds[count].credential), &rk, sizeof(CTAP_residentKey)); | |
+ count++; | |
+ } | |
+ } | |
+ GA->credLen = count; | |
} | |
- if (user_id_size) | |
+ | |
+ printf1(TAG_GA, "qsort length: %d\n", GA->credLen); | |
+ qsort(GA->creds, GA->credLen, sizeof(CTAP_credentialDescriptor), cred_cmp_func); | |
+ return count; | |
+} | |
+ | |
+ | |
+static int8_t save_credential_list( uint8_t * clientDataHash, | |
+ CTAP_credentialDescriptor * creds, | |
+ uint32_t count, | |
+ CTAP_extensions * extensions) | |
+{ | |
+ if(count) | |
{ | |
- ret = _encode_user_id(&map, user_id, user_id_size); // 4 | |
- if (ret != CborNoError) { | |
- return ret; | |
+ if (count > ALLOW_LIST_MAX_SIZE-1) | |
+ { | |
+ printf2(TAG_ERR, "ALLOW_LIST_MAX_SIZE Exceeded\n"); | |
+ return CTAP2_ERR_TOO_MANY_ELEMENTS; | |
} | |
+ | |
+ memmove(getAssertionState.clientDataHash, clientDataHash, CLIENT_DATA_HASH_SIZE); | |
+ memmove(getAssertionState.creds, creds, sizeof(CTAP_credentialDescriptor) * (count)); | |
+ memmove(&getAssertionState.extensions, extensions, sizeof(CTAP_extensions)); | |
+ | |
} | |
- ret = cbor_encoder_close_container(encoder, &map); | |
+ getAssertionState.count = count; | |
+ getAssertionState.index = 0; | |
+ printf1(TAG_GA,"saved %d credentials\n",count); | |
return 0; | |
} | |
-/** | |
- * Selects one of the matching credentials in the given credential list. | |
- * | |
- * @param GA getAssertion request that must be examined. Must contain | |
- * an allow list. | |
- * @param chosen_credential_out Will be filled with a pointer to the chosen credential, | |
- * or NULL if no key was found. | |
- * @param chosen_privkey Will be filled with the the private key corresponding to chosen_credential. | |
- * Must be at least HMAC_SHA256_LEN bytes wide. | |
- */ | |
-static void _authenticate_with_allow_list(ctap_get_assertion_req_t* GA, u2f_keyhandle_t** chosen_credential_out, uint8_t* chosen_privkey) | |
+static CTAP_credentialDescriptor * pop_credential() | |
{ | |
- /* | |
- * We can just pick the first credential that we're able to authenticate with. | |
- * No need to ask the user to select one if many credentials match. | |
- * See Client to Authenticator Protocol, 5.2, point 9. | |
- */ | |
- for (int i = 0; i < GA->cred_len; i++) { | |
- u2f_keyhandle_t* this_key = GA->creds + i; | |
- bool key_valid = u2f_keyhandle_verify(GA->rp.id, (uint8_t*)this_key, sizeof(*this_key), chosen_privkey); | |
- if (key_valid) { | |
- /* Found an applicable credential. */ | |
- *chosen_credential_out = this_key; | |
- return; | |
- } | |
+ if (getAssertionState.count > 0 && getAssertionState.index < getAssertionState.count) | |
+ { | |
+ return &getAssertionState.creds[getAssertionState.index++]; | |
+ } | |
+ else | |
+ { | |
+ return NULL; | |
} | |
- /* No keys were found. */ | |
- util_zero(chosen_privkey, HMAC_SHA256_LEN); | |
- *chosen_credential_out = NULL; | |
} | |
-/** | |
- * Called when the user has selected one of the credentials from | |
- * the available credential list for an authentication request. | |
- * | |
- * This function will decode the selected authentication key and | |
- * move to the CTAP_GET_ASSERTION_SELECTED_CREDENTIAL state. | |
- */ | |
-static void _auth_credential_selected(int selected_cred, void* param) | |
+// adds 2 to map, or 3 if add_user is true | |
+uint8_t ctap_end_get_assertion(CborEncoder * map, CTAP_credentialDescriptor * cred, uint8_t * auth_data_buf, unsigned int auth_data_buf_sz, uint8_t * clientDataHash) | |
{ | |
- (void)param; | |
- ctap_get_assertion_state_t* state = &_state.data.get_assertion; | |
- | |
- if (selected_cred < 0) { | |
- /* User aborted. */ | |
- state->state = CTAP_GET_ASSERTION_DENIED; | |
- return; | |
- } | |
+ int ret; | |
+ uint8_t sigbuf[64]; | |
+ uint8_t sigder[72]; | |
+ int sigder_sz; | |
- /* Now load the credential that was selected in the output buffer. */ | |
- ctap_resident_key_t selected_key; | |
- //screen_sprintf_debug(500, "Selected cred #%d", cred_idx[selected_cred]); | |
- bool mem_result = memory_get_ctap_resident_key(state->cred_list.creds[selected_cred].mem_id, &selected_key); | |
+ ret = cbor_encode_int(map, RESP_credential); | |
+ check_ret(ret); | |
- if (!mem_result) { | |
- /* Shouldn't happen, but if it does we effectively don't have any valid credential to provide. */ | |
- state->state = CTAP_GET_ASSERTION_NO_CREDENTIALS; | |
- return; | |
- } | |
- /* Sanity check the stored credential. */ | |
- if (selected_key.valid != CTAP_RESIDENT_KEY_VALID || | |
- selected_key.user_id_size > CTAP_USER_ID_MAX_SIZE) { | |
- state->state = CTAP_GET_ASSERTION_NO_CREDENTIALS; | |
- return; | |
- } | |
- memcpy(&state->auth_credential, &selected_key.key_handle, sizeof(selected_key.key_handle)); | |
- state->user_id_size = selected_key.user_id_size; | |
- memcpy(state->user_id, selected_key.user_id, state->user_id_size); | |
+ ret = ctap_add_credential_descriptor(map, &cred->credential, cred->type); // 1 | |
+ check_retr(ret); | |
- /* Sanity check the key and extract the private key. */ | |
- bool key_valid = u2f_keyhandle_verify(state->req.rp.id, (const uint8_t*)&state->auth_credential, sizeof(state->auth_credential), state->auth_privkey); | |
- if (!key_valid) { | |
- workflow_status_blocking("Internal error. Keyhandle verification failed.", false); | |
- state->state = CTAP_GET_ASSERTION_NO_CREDENTIALS; | |
- return; | |
+ { | |
+ ret = cbor_encode_int(map,RESP_authData); // 2 | |
+ check_ret(ret); | |
+ ret = cbor_encode_byte_string(map, auth_data_buf, auth_data_buf_sz); | |
+ check_ret(ret); | |
} | |
- state->state = CTAP_GET_ASSERTION_SELECTED_CREDENTIAL; | |
-} | |
+ unsigned int cred_size = get_credential_id_size(cred->type); | |
+ crypto_ecc256_load_key((uint8_t*)&cred->credential.id, cred_size, NULL, 0); | |
-/** | |
- * Selects one of the stored credentials for authentication. | |
- * | |
- * @param GA getAssertion request that must be examined. Must contain | |
- * an allow list. | |
- * @param chosen_credential_out Will be filled with the chosen credential. | |
- * @param chosen_privkey Will be filled with the the private key corresponding to chosen_credential. | |
- * Must be at least HMAC_SHA256_LEN bytes wide. | |
- * @param user_id_out Will be filled with the stored User ID corresponding to the | |
- * chosen credential. Must be CTAP_STORAGE_USER_NAME_LIMIT bytes long. | |
- * @param user_id_size_out Will be filled with the size of user_id. | |
- * | |
- * @return true if authentication was successful (operation should continue), false otherwise. | |
- */ | |
-static bool _authenticate_with_rk(ctap_get_assertion_req_t* GA) | |
-{ | |
- ctap_get_assertion_state_t* state = &_state.data.get_assertion; | |
- state->cred_list.n_elems = 0; | |
- | |
- /* | |
- * Compute the hash of the RP id so that we | |
- * can match it against the keys we have in memory. | |
- */ | |
- uint8_t rp_id_hash[SHA256_LEN]; | |
- _compute_rpid_hash(&GA->rp, rp_id_hash); | |
- /* Check all keys that match this RP. */ | |
- for (int i = 0; i < MEMORY_CTAP_RESIDENT_KEYS_MAX; i++) { | |
- ctap_resident_key_t this_key; | |
- bool mem_result = memory_get_ctap_resident_key(i, &this_key); | |
- if (!mem_result || this_key.valid != CTAP_RESIDENT_KEY_VALID) { | |
- continue; | |
- } | |
- if (!memcmp(this_key.rp_id_hash, rp_id_hash, SHA256_LEN)) { | |
- /* | |
- * This key matches the RP! Add its user information to | |
- * our list. | |
- */ | |
- ctap_credential_display_t* this_cred = state->cred_list.creds + state->cred_list.n_elems; | |
- this_cred->mem_id = i; | |
- memcpy(this_cred->username, this_key.user_name, sizeof(this_key.user_name)); | |
- memcpy(this_cred->display_name, this_key.display_name, sizeof(this_key.display_name)); | |
- state->cred_list.n_elems++; | |
- if (state->cred_list.n_elems == CTAP_CREDENTIAL_LIST_MAX_SIZE) { | |
- /* No more space */ | |
- break; | |
- } | |
- } | |
- } | |
- if (state->cred_list.n_elems == 0) { | |
- return false; | |
+#ifdef ENABLE_U2F_EXTENSIONS | |
+ if ( extend_fido2(&cred->credential.id, sigder) ) | |
+ { | |
+ sigder_sz = 72; | |
} | |
- /* Sort credentials by creation time. */ | |
- qsort(state->cred_list.creds, state->cred_list.n_elems, sizeof(*state->cred_list.creds), _compare_display_credentials); | |
- if (state->cred_list.n_elems > 1) { | |
- workflow_t* wf = workflow_select_ctap_credential(&state->cred_list, _auth_credential_selected, NULL); | |
- workflow_stack_start_workflow(wf); | |
- state->state = CTAP_GET_ASSERTION_SELECT_CREDENTIAL; | |
- } else { | |
- _auth_credential_selected(0, NULL); | |
+ else | |
+#endif | |
+ { | |
+ sigder_sz = ctap_calculate_signature(auth_data_buf, auth_data_buf_sz, clientDataHash, auth_data_buf, sigbuf, sigder); | |
} | |
- return true; | |
-} | |
- | |
-/** | |
- * @param auth_data_buf Must be at least sizeof(ctap_auth_data_t) bytes wide. | |
- * @param data_buf_len_out Will be filled with the actual auth data size. | |
- */ | |
-static uint8_t _make_authentication_response(ctap_get_assertion_req_t* GA, uint8_t* auth_data_buf, size_t* data_buf_len_out) { | |
- ctap_auth_data_header_t* auth_data_header = (ctap_auth_data_header_t*)auth_data_buf; | |
- auth_data_header->flags = 0; | |
- if (GA->up) { | |
- auth_data_header->flags |= CTAP_AUTH_DATA_FLAG_USER_PRESENT; // User presence | |
- } | |
- if (GA->uv) { | |
- auth_data_header->flags |= CTAP_AUTH_DATA_FLAG_USER_VERIFIED; // User presence | |
+ { | |
+ ret = cbor_encode_int(map, RESP_signature); // 3 | |
+ check_ret(ret); | |
+ ret = cbor_encode_byte_string(map, sigder, sigder_sz); | |
+ check_ret(ret); | |
} | |
- _compute_rpid_hash(&GA->rp, auth_data_header->rp_id_hash); | |
- | |
- /* Update the U2F counter. */ | |
- uint32_t u2f_counter; | |
- if (!securechip_u2f_counter_inc(&u2f_counter)) { | |
- return CTAP2_ERR_OPERATION_DENIED; | |
- } | |
- _encode_u2f_counter(u2f_counter, (uint8_t*)&auth_data_header->signCount); | |
+ if (cred->credential.user.id_size) | |
+ { | |
+ printf1(TAG_GREEN, "adding user details to output\r\n"); | |
- uint32_t actual_auth_data_size = sizeof(ctap_auth_data_header_t); | |
+ int ret = cbor_encode_int(map, RESP_publicKeyCredentialUserEntity); | |
+ check_ret(ret); | |
- /* FUTURE: manage extensions if we want to. */ | |
- *data_buf_len_out = actual_auth_data_size; | |
- return CTAP1_ERR_SUCCESS; | |
-} | |
+ ret = ctap_add_user_entity(map, &cred->credential.user, getAssertionState.user_verified); // 4 | |
+ check_retr(ret); | |
+ } | |
-static void _get_assertion_init_state(ctap_get_assertion_req_t* req) | |
-{ | |
- _state.data.get_assertion.state = CTAP_GET_ASSERTION_STARTED; | |
- memcpy(&_state.data.get_assertion.req, req, sizeof(*req)); | |
-} | |
-static void _get_assertion_free_state(void) | |
-{ | |
- util_zero(_state.data.get_assertion.auth_privkey, | |
- sizeof(_state.data.get_assertion.auth_privkey) | |
- ); | |
+ return 0; | |
} | |
-static uint8_t ctap_get_assertion(const in_buffer_t* in_buffer) | |
+uint8_t ctap_get_next_assertion(CborEncoder * encoder) | |
{ | |
- ctap_get_assertion_req_t req; | |
+ int ret; | |
+ CborEncoder map; | |
- int ret = ctap_parse_get_assertion(&req, in_buffer); | |
+ CTAP_credentialDescriptor * cred = pop_credential(); | |
- if (ret != 0) { | |
- return ret; | |
+ if (cred == NULL) | |
+ { | |
+ return CTAP2_ERR_NOT_ALLOWED; | |
} | |
- if (req.pin_auth_empty) { | |
- bool result = _ask_generic_authorization(); | |
- if (!result) { | |
- return CTAP2_ERR_OPERATION_DENIED; | |
- } | |
- return CTAP2_ERR_PIN_NOT_SET; | |
+ auth_data_update_count(&getAssertionState.buf.authData); | |
+ memmove(getAssertionState.buf.authData.rpIdHash, cred->credential.id.rpIdHash, 32); | |
+ | |
+ if (cred->credential.user.id_size) | |
+ { | |
+ printf1(TAG_GREEN, "adding user info to assertion response\r\n"); | |
+ ret = cbor_encoder_create_map(encoder, &map, 4); | |
} | |
- if (req.pin_auth_present) { | |
- /* We don't support pin_auth. */ | |
- return CTAP2_ERR_PIN_AUTH_INVALID; | |
+ else | |
+ { | |
+ printf1(TAG_GREEN, "NOT adding user info to assertion response\r\n"); | |
+ ret = cbor_encoder_create_map(encoder, &map, 3); | |
} | |
- if (!req.rp.size || !req.client_data_hash_present) { | |
- /* Both parameters are mandatory. */ | |
- return CTAP2_ERR_MISSING_PARAMETER; | |
+ check_ret(ret); | |
+ | |
+ // if only one account for this RP, null out the user details | |
+ if (!getAssertionState.user_verified) | |
+ { | |
+ printf1(TAG_GREEN, "Not verified, nulling out user details on response\r\n"); | |
+ memset(cred->credential.user.name, 0, USER_NAME_LIMIT); | |
} | |
- /* | |
- * Ask the user to confirm that he wants to authenticate. | |
- * This must be done before we check for credentials so that | |
- * we don't disclose the existance of credentials before the | |
- * user has proven his identity (See 5.2, point 7). | |
- */ | |
- _get_assertion_init_state(&req); | |
- workflow_stack_start_workflow(workflow_unlock(_get_assertion_unlock_cb, NULL)); | |
- return CTAP1_ERR_SUCCESS; | |
+ unsigned int ext_encoder_buf_size = sizeof(getAssertionState.buf.extensions); | |
+ ret = ctap_make_extensions(&getAssertionState.extensions, getAssertionState.buf.extensions, &ext_encoder_buf_size); | |
+ | |
+ if (ret == 0) | |
+ { | |
+ if (ext_encoder_buf_size) | |
+ { | |
+ getAssertionState.buf.authData.flags |= (1 << 7); | |
+ } else { | |
+ getAssertionState.buf.authData.flags &= ~(1 << 7); | |
+ } | |
+ } | |
+ | |
+ ret = ctap_end_get_assertion(&map, cred, | |
+ (uint8_t *)&getAssertionState.buf.authData, | |
+ sizeof(CTAP_authDataHeader) + ext_encoder_buf_size, | |
+ getAssertionState.clientDataHash); | |
+ | |
+ check_retr(ret); | |
+ | |
+ ret = cbor_encoder_close_container(encoder, &map); | |
+ check_ret(ret); | |
+ | |
+ return 0; | |
} | |
-/** | |
- * Generates a new assertion in response to a GetAssertion request. | |
- * Only called when the user has already accepted and identified with the device. | |
- */ | |
-static ctap_request_result_t _get_assertion_select_credential(void) | |
+uint8_t ctap_cred_metadata(CborEncoder * encoder) | |
+{ | |
+ CborEncoder map; | |
+ int ret = cbor_encoder_create_map(encoder, &map, 2); | |
+ check_ret(ret); | |
+ ret = cbor_encode_int(&map, 1); | |
+ check_ret(ret); | |
+ ret = cbor_encode_int(&map, STATE.rk_stored); | |
+ check_ret(ret); | |
+ ret = cbor_encode_int(&map, 2); | |
+ check_ret(ret); | |
+ int remaining_rks = ctap_rk_size() - STATE.rk_stored; | |
+ ret = cbor_encode_int(&map, remaining_rks); | |
+ check_ret(ret); | |
+ ret = cbor_encoder_close_container(encoder, &map); | |
+ check_ret(ret); | |
+ return 0; | |
+} | |
+ | |
+uint8_t ctap_cred_rp(CborEncoder * encoder, int rk_ind, int rp_count) | |
+{ | |
+ CTAP_residentKey rk; | |
+ ctap_load_rk(rk_ind, &rk); | |
+ | |
+ CborEncoder map; | |
+ size_t map_size = rp_count > 0 ? 3 : 2; | |
+ int ret = cbor_encoder_create_map(encoder, &map, map_size); | |
+ check_ret(ret); | |
+ ret = cbor_encode_int(&map, 3); | |
+ check_ret(ret); | |
+ { | |
+ CborEncoder rp; | |
+ ret = cbor_encoder_create_map(&map, &rp, 2); | |
+ check_ret(ret); | |
+ ret = cbor_encode_text_stringz(&rp, "id"); | |
+ check_ret(ret); | |
+ if (rk.rpIdSize <= sizeof(rk.rpId)) | |
+ { | |
+ ret = cbor_encode_text_string(&rp, (const char *)rk.rpId, rk.rpIdSize); | |
+ } | |
+ else | |
+ { | |
+ ret = cbor_encode_text_string(&rp, "", 0); | |
+ } | |
+ check_ret(ret); | |
+ ret = cbor_encode_text_stringz(&rp, "name"); | |
+ check_ret(ret); | |
+ ret = cbor_encode_text_stringz(&rp, (const char *)rk.user.name); | |
+ check_ret(ret); | |
+ ret = cbor_encoder_close_container(&map, &rp); | |
+ check_ret(ret); | |
+ } | |
+ ret = cbor_encode_int(&map, 4); | |
+ check_ret(ret); | |
+ cbor_encode_byte_string(&map, rk.id.rpIdHash, 32); | |
+ check_ret(ret); | |
+ if (rp_count > 0) | |
+ { | |
+ ret = cbor_encode_int(&map, 5); | |
+ check_ret(ret); | |
+ ret = cbor_encode_int(&map, rp_count); | |
+ check_ret(ret); | |
+ } | |
+ ret = cbor_encoder_close_container(encoder, &map); | |
+ check_ret(ret); | |
+ return 0; | |
+} | |
+ | |
+uint8_t ctap_cred_rk(CborEncoder * encoder, int rk_ind, int rk_count) | |
+{ | |
+ CTAP_residentKey rk; | |
+ ctap_load_rk(rk_ind, &rk); | |
+ | |
+ uint32_t cred_protect = read_metadata_from_masked_credential(&rk.id); | |
+ if ( cred_protect == 0 || cred_protect > 3 ) | |
+ { | |
+ // Take default value of userVerificationOptional | |
+ cred_protect = EXT_CRED_PROTECT_OPTIONAL; | |
+ } | |
+ | |
+ CborEncoder map; | |
+ size_t map_size = rk_count > 0 ? 5 : 4; | |
+ int ret = cbor_encoder_create_map(encoder, &map, map_size); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_encode_int(&map, 6); | |
+ check_ret(ret); | |
+ { | |
+ ret = ctap_add_user_entity(&map, &rk.user, 1); | |
+ check_ret(ret); | |
+ } | |
+ | |
+ ret = cbor_encode_int(&map, 7); | |
+ check_ret(ret); | |
+ { | |
+ ret = ctap_add_credential_descriptor(&map, (struct Credential*)&rk, PUB_KEY_CRED_PUB_KEY); | |
+ check_ret(ret); | |
+ } | |
+ | |
+ ret = cbor_encode_int(&map, 8); | |
+ check_ret(ret); | |
+ { | |
+ ctap_generate_cose_key(&map, (uint8_t*)&rk.id, sizeof(CredentialId), PUB_KEY_CRED_PUB_KEY, COSE_ALG_ES256); | |
+ } | |
+ | |
+ if (rk_count > 0) | |
+ { | |
+ ret = cbor_encode_int(&map, 9); | |
+ check_ret(ret); | |
+ ret = cbor_encode_int(&map, rk_count); | |
+ check_ret(ret); | |
+ } | |
+ | |
+ ret = cbor_encode_int(&map, 0x0A); | |
+ check_ret(ret); | |
+ ret = cbor_encode_int(&map, cred_protect); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_encoder_close_container(encoder, &map); | |
+ check_ret(ret); | |
+ return 0; | |
+} | |
+ | |
+uint8_t ctap_cred_mgmt_pinauth(CTAP_credMgmt *CM) | |
+{ | |
+ if (CM->cmd != CM_cmdMetadata && | |
+ CM->cmd != CM_cmdRPBegin && | |
+ CM->cmd != CM_cmdRKBegin && | |
+ CM->cmd != CM_cmdRKDelete) | |
+ { | |
+ // pinAuth is not required for other commands | |
+ return 0; | |
+ } | |
+ | |
+ int8_t ret = verify_pin_auth_ex(CM->pinAuth, (uint8_t*)&CM->hashed, CM->subCommandParamsCborSize + 1); | |
+ | |
+ if (ret == CTAP2_ERR_PIN_AUTH_INVALID) | |
+ { | |
+ ctap_decrement_pin_attempts(); | |
+ if (ctap_device_boot_locked()) | |
+ { | |
+ return CTAP2_ERR_PIN_AUTH_BLOCKED; | |
+ } | |
+ return CTAP2_ERR_PIN_AUTH_INVALID; | |
+ } | |
+ else | |
+ { | |
+ ctap_reset_pin_attempts(); | |
+ } | |
+ | |
+ return ret; | |
+} | |
+ | |
+static int credentialId_to_rk_index(CredentialId * credId){ | |
+ unsigned int i; | |
+ CTAP_residentKey rk; | |
+ | |
+ for (i = 0; i < ctap_rk_size(); i++) | |
+ { | |
+ ctap_load_rk(i, &rk); | |
+ if ( ctap_rk_is_valid(&rk) ) { | |
+ if (memcmp(&rk.id, credId, sizeof(CredentialId)) == 0) | |
+ { | |
+ return i; | |
+ } | |
+ } | |
+ } | |
+ | |
+ return -1; | |
+} | |
+ | |
+// Load the next valid resident key of a different rpIdHash | |
+static int scan_for_next_rp(int index){ | |
+ CTAP_residentKey rk; | |
+ uint8_t nextRpIdHash[32]; | |
+ | |
+ if (index == -1) | |
+ { | |
+ ctap_load_rk(0, &rk); | |
+ if (ctap_rk_is_valid(&rk)) | |
+ { | |
+ return 0; | |
+ } | |
+ else | |
+ { | |
+ index = 0; | |
+ } | |
+ } | |
+ | |
+ int occurs_previously; | |
+ do { | |
+ occurs_previously = 0; | |
+ | |
+ index++; | |
+ if ((unsigned int)index >= ctap_rk_size()) | |
+ { | |
+ return -1; | |
+ } | |
+ | |
+ ctap_load_rk(index, &rk); | |
+ memmove(nextRpIdHash, rk.id.rpIdHash, 32); | |
+ | |
+ if (!ctap_rk_is_valid(&rk)) | |
+ { | |
+ occurs_previously = 1; | |
+ continue; | |
+ } else { | |
+ } | |
+ | |
+ // Check if we have scanned the rpIdHash before. | |
+ int i; | |
+ for (i = 0; i < index; i++) | |
+ { | |
+ ctap_load_rk(i, &rk); | |
+ if (memcmp(rk.id.rpIdHash, nextRpIdHash, 32) == 0) | |
+ { | |
+ occurs_previously = 1; | |
+ break; | |
+ } | |
+ } | |
+ | |
+ } while (occurs_previously); | |
+ | |
+ return index; | |
+} | |
+ | |
+// Load the next valid resident key of the same rpIdHash | |
+static int scan_for_next_rk(int index, uint8_t * initialRpIdHash){ | |
+ CTAP_residentKey rk; | |
+ uint8_t lastRpIdHash[32]; | |
+ | |
+ if (initialRpIdHash != NULL) { | |
+ memmove(lastRpIdHash, initialRpIdHash, 32); | |
+ index = -1; | |
+ } | |
+ else | |
+ { | |
+ ctap_load_rk(index, &rk); | |
+ memmove(lastRpIdHash, rk.id.rpIdHash, 32); | |
+ } | |
+ | |
+ do | |
+ { | |
+ index++; | |
+ if ((unsigned int)index >= ctap_rk_size()) | |
+ { | |
+ return -1; | |
+ } | |
+ ctap_load_rk(index, &rk); | |
+ } | |
+ while ( memcmp( rk.id.rpIdHash, lastRpIdHash, 32 ) != 0 ); | |
+ | |
+ return index; | |
+} | |
+ | |
+ | |
+ | |
+uint8_t ctap_cred_mgmt(CborEncoder * encoder, uint8_t * request, int length) | |
+{ | |
+ CTAP_credMgmt CM; | |
+ int i = 0; | |
+ | |
+ // RP / RK pointers | |
+ static int curr_rp_ind = 0; | |
+ static int curr_rk_ind = 0; | |
+ | |
+ // flags that authenticate whether *Begin was before *Next | |
+ static bool rp_auth = false; | |
+ static bool rk_auth = false; | |
+ | |
+ int rp_count = 0; | |
+ int rk_count = 0; | |
+ | |
+ int ret = ctap_parse_cred_mgmt(&CM, request, length); | |
+ if (ret != 0) | |
+ { | |
+ printf2(TAG_ERR,"error, ctap_parse_cred_mgmt failed\n"); | |
+ return ret; | |
+ } | |
+ ret = ctap_cred_mgmt_pinauth(&CM); | |
+ check_retr(ret); | |
+ if (STATE.rk_stored == 0 && CM.cmd != CM_cmdMetadata) | |
+ { | |
+ printf2(TAG_ERR,"No resident keys\n"); | |
+ return 0; | |
+ } | |
+ if (CM.cmd == CM_cmdRPBegin) | |
+ { | |
+ curr_rk_ind = -1; | |
+ rp_auth = true; | |
+ rk_auth = false; | |
+ curr_rp_ind = scan_for_next_rp(-1); | |
+ | |
+ // Count total unique RP's | |
+ while (curr_rp_ind >= 0) | |
+ { | |
+ curr_rp_ind = scan_for_next_rp(curr_rp_ind); | |
+ rp_count++; | |
+ } | |
+ | |
+ // Reset scan | |
+ curr_rp_ind = scan_for_next_rp(-1); | |
+ | |
+ printf1(TAG_MC, "RP Begin @%d. %d total.\n", curr_rp_ind, rp_count); | |
+ } | |
+ else if (CM.cmd == CM_cmdRKBegin) | |
+ { | |
+ curr_rk_ind = scan_for_next_rk(0, CM.subCommandParams.rpIdHash); | |
+ rk_auth = true; | |
+ | |
+ // Count total RK's associated to RP | |
+ while (curr_rk_ind >= 0) | |
+ { | |
+ curr_rk_ind = scan_for_next_rk(curr_rk_ind, NULL); | |
+ rk_count++; | |
+ } | |
+ | |
+ // Reset scan | |
+ curr_rk_ind = scan_for_next_rk(0, CM.subCommandParams.rpIdHash); | |
+ printf1(TAG_MC, "Cred Begin @%d. %d total.\n", curr_rk_ind, rk_count); | |
+ } | |
+ else if (CM.cmd != CM_cmdRKNext && CM.cmd != CM_cmdRPNext) | |
+ { | |
+ rk_auth = false; | |
+ rp_auth = false; | |
+ curr_rk_ind = -1; | |
+ curr_rp_ind = -1; | |
+ } | |
+ | |
+ switch (CM.cmd) | |
+ { | |
+ case CM_cmdMetadata: | |
+ printf1(TAG_CM, "CM_cmdMetadata\n"); | |
+ ret = ctap_cred_metadata(encoder); | |
+ check_ret(ret); | |
+ break; | |
+ case CM_cmdRPBegin: | |
+ case CM_cmdRPNext: | |
+ printf1(TAG_CM, "Get RP %d\n", curr_rp_ind); | |
+ if (curr_rp_ind < 0 || !rp_auth) { | |
+ rp_auth = false; | |
+ rk_auth = false; | |
+ return CTAP2_ERR_NO_CREDENTIALS; | |
+ } | |
+ | |
+ ret = ctap_cred_rp(encoder, curr_rp_ind, rp_count); | |
+ check_ret(ret); | |
+ curr_rp_ind = scan_for_next_rp(curr_rp_ind); | |
+ | |
+ break; | |
+ case CM_cmdRKBegin: | |
+ case CM_cmdRKNext: | |
+ printf1(TAG_CM, "Get Cred %d\n", curr_rk_ind); | |
+ if (curr_rk_ind < 0 || !rk_auth) { | |
+ rp_auth = false; | |
+ rk_auth = false; | |
+ return CTAP2_ERR_NO_CREDENTIALS; | |
+ } | |
+ | |
+ ret = ctap_cred_rk(encoder, curr_rk_ind, rk_count); | |
+ check_ret(ret); | |
+ | |
+ curr_rk_ind = scan_for_next_rk(curr_rk_ind, NULL); | |
+ | |
+ break; | |
+ case CM_cmdRKDelete: | |
+ printf1(TAG_CM, "CM_cmdRKDelete\n"); | |
+ i = credentialId_to_rk_index(&CM.subCommandParams.credentialDescriptor.credential.id); | |
+ if (i >= 0) { | |
+ ctap_delete_rk(i); | |
+ ctap_decrement_rk_store(); | |
+ printf1(TAG_CM, "Deleted rk %d\n", i); | |
+ } else { | |
+ printf1(TAG_CM, "No Rk by given credId\n"); | |
+ return CTAP2_ERR_NO_CREDENTIALS; | |
+ } | |
+ break; | |
+ default: | |
+ printf2(TAG_ERR, "error, invalid credMgmt cmd: 0x%02x\n", CM.cmd); | |
+ return CTAP1_ERR_INVALID_COMMAND; | |
+ } | |
+ return 0; | |
+} | |
+ | |
+uint8_t ctap_get_assertion(CborEncoder * encoder, uint8_t * request, int length) | |
{ | |
- ctap_get_assertion_state_t* state = &_state.data.get_assertion; | |
- | |
- if (state->req.cred_len) { | |
- // allowlist is present -> check all the credentials that were actually generated by us. | |
- u2f_keyhandle_t* chosen_credential = NULL; | |
- _authenticate_with_allow_list(&state->req, &chosen_credential, state->auth_privkey); | |
- if (!chosen_credential) { | |
- /* No credential selected (or no credential was known to the device). */ | |
- ctap_request_result_t result = {.status = CTAP2_ERR_NO_CREDENTIALS, .request_completed = true}; | |
- return result; | |
+ CTAP_getAssertion GA; | |
+ | |
+ int ret = ctap_parse_get_assertion(&GA,request,length); | |
+ | |
+ if (ret != 0) | |
+ { | |
+ printf2(TAG_ERR,"error, parse_get_assertion failed\n"); | |
+ return ret; | |
+ } | |
+ | |
+ if (GA.pinAuthEmpty) | |
+ { | |
+ ret = ctap2_user_presence_test(); | |
+ check_retr(ret); | |
+ return ctap_is_pin_set() == 1 ? CTAP2_ERR_PIN_AUTH_INVALID : CTAP2_ERR_PIN_NOT_SET; | |
+ } | |
+ if (GA.pinAuthPresent) | |
+ { | |
+ ret = verify_pin_auth(GA.pinAuth, GA.clientDataHash); | |
+ check_retr(ret); | |
+ getAssertionState.user_verified = 1; | |
+ } | |
+ else | |
+ { | |
+ getAssertionState.user_verified = 0; | |
+ } | |
+ | |
+ if (!GA.rp.size || !GA.clientDataHashPresent) | |
+ { | |
+ return CTAP2_ERR_MISSING_PARAMETER; | |
+ } | |
+ CborEncoder map; | |
+ | |
+ int map_size = 3; | |
+ | |
+ printf1(TAG_GA, "ALLOW_LIST has %d creds\n", GA.credLen); | |
+ int validCredCount = ctap_filter_invalid_credentials(&GA); | |
+ | |
+ if (validCredCount == 0) | |
+ { | |
+ printf2(TAG_ERR,"Error, no authentic credential\n"); | |
+ return CTAP2_ERR_NO_CREDENTIALS; | |
+ } | |
+ else if (validCredCount > 1) | |
+ { | |
+ map_size += 1; | |
+ } | |
+ | |
+ | |
+ if (GA.creds[validCredCount - 1].credential.user.id_size) | |
+ { | |
+ map_size += 1; | |
+ } | |
+ if (GA.extensions.hmac_secret_present == EXT_HMAC_SECRET_PARSED) | |
+ { | |
+ printf1(TAG_GA, "hmac-secret is present\r\n"); | |
+ } | |
+ | |
+ ret = cbor_encoder_create_map(encoder, &map, map_size); | |
+ check_ret(ret); | |
+ | |
+ // if only one account for this RP, null out the user details | |
+ if (validCredCount < 2 || !getAssertionState.user_verified) | |
+ { | |
+ printf1(TAG_GREEN, "Only one account, nulling out user details on response\r\n"); | |
+ memset(&GA.creds[0].credential.user.name, 0, USER_NAME_LIMIT); | |
+ } | |
+ | |
+ printf1(TAG_GA,"resulting order of creds:\n"); | |
+ int j; | |
+ for (j = 0; j < GA.credLen; j++) | |
+ { | |
+ printf1(TAG_GA,"CRED ID (# %d)\n", GA.creds[j].credential.id.count); | |
+ } | |
+ | |
+ CTAP_credentialDescriptor * cred = &GA.creds[0]; | |
+ | |
+ GA.extensions.hmac_secret.credential = &cred->credential; | |
+ | |
+ uint32_t auth_data_buf_sz = sizeof(CTAP_authDataHeader); | |
+ | |
+#ifdef ENABLE_U2F_EXTENSIONS | |
+ if ( is_extension_request((uint8_t*)&GA.creds[0].credential.id, sizeof(CredentialId)) ) | |
+ { | |
+ crypto_sha256_init(); | |
+ crypto_sha256_update(GA.rp.id, GA.rp.size); | |
+ crypto_sha256_final(getAssertionState.buf.authData.rpIdHash); | |
+ | |
+ getAssertionState.buf.authData.flags = (1 << 0); | |
+ getAssertionState.buf.authData.flags |= (1 << 2); | |
+ } | |
+ else | |
+#endif | |
+ { | |
+ device_disable_up(GA.up == 0); | |
+ ret = ctap_make_auth_data(&GA.rp, &map, (uint8_t*)&getAssertionState.buf.authData, &auth_data_buf_sz, NULL, &GA.extensions); | |
+ device_disable_up(false); | |
+ check_retr(ret); | |
+ | |
+ getAssertionState.buf.authData.flags &= ~(1 << 2); | |
+ getAssertionState.buf.authData.flags |= (getAssertionState.user_verified << 2); | |
+ | |
+ { | |
+ unsigned int ext_encoder_buf_size = sizeof(getAssertionState.buf.extensions); | |
+ | |
+ ret = ctap_make_extensions(&GA.extensions, getAssertionState.buf.extensions, &ext_encoder_buf_size); | |
+ check_retr(ret); | |
+ if (ext_encoder_buf_size) | |
+ { | |
+ getAssertionState.buf.authData.flags |= (1 << 7); | |
+ auth_data_buf_sz += ext_encoder_buf_size; | |
+ } | |
} | |
- memcpy(&state->auth_credential, chosen_credential, sizeof(state->auth_credential)); | |
- state->state = CTAP_GET_ASSERTION_SELECTED_CREDENTIAL; | |
- } else { | |
- // No allowList, so use all matching RK's matching rpId | |
- bool rk_result = _authenticate_with_rk(&state->req); | |
- if (!rk_result) { | |
- ctap_request_result_t result = {.status = CTAP2_ERR_NO_CREDENTIALS, .request_completed = true}; | |
- return result; | |
+ | |
+ } | |
+ | |
+ ret = ctap_end_get_assertion(&map, cred, (uint8_t*)&getAssertionState.buf, auth_data_buf_sz, GA.clientDataHash); // 1,2,3,4 | |
+ check_retr(ret); | |
+ | |
+ if (validCredCount > 1) | |
+ { | |
+ ret = cbor_encode_int(&map, RESP_numberOfCredentials); // 5 | |
+ check_ret(ret); | |
+ ret = cbor_encode_int(&map, validCredCount); | |
+ check_ret(ret); | |
+ } | |
+ | |
+ ret = cbor_encoder_close_container(encoder, &map); | |
+ check_ret(ret); | |
+ | |
+ ret = save_credential_list( GA.clientDataHash, | |
+ GA.creds + 1 /* skip first credential*/, | |
+ validCredCount - 1, | |
+ &GA.extensions); | |
+ check_retr(ret); | |
+ | |
+ return 0; | |
+} | |
+ | |
+// Return how many trailing zeros in a buffer | |
+static int trailing_zeros(uint8_t * buf, int indx) | |
+{ | |
+ int c = 0; | |
+ while(0==buf[indx] && indx) | |
+ { | |
+ indx--; | |
+ c++; | |
+ } | |
+ return c; | |
+} | |
+ | |
+uint8_t ctap_update_pin_if_verified(uint8_t * pinEnc, int len, uint8_t * platform_pubkey, uint8_t * pinAuth, uint8_t * pinHashEnc) | |
+{ | |
+ uint8_t shared_secret[32]; | |
+ uint8_t hmac[32]; | |
+ int ret; | |
+ | |
+// Validate incoming data packet len | |
+ if (len < 64) | |
+ { | |
+ return CTAP1_ERR_OTHER; | |
+ } | |
+ | |
+// Validate device's state | |
+ if (ctap_is_pin_set()) // Check first, prevent SCA | |
+ { | |
+ if (ctap_device_locked()) | |
+ { | |
+ return CTAP2_ERR_PIN_BLOCKED; | |
} | |
+ if (ctap_device_boot_locked()) | |
+ { | |
+ return CTAP2_ERR_PIN_AUTH_BLOCKED; | |
+ } | |
+ } | |
+ | |
+// calculate shared_secret | |
+ crypto_ecc256_shared_secret(platform_pubkey, KEY_AGREEMENT_PRIV, shared_secret); | |
+ | |
+ crypto_sha256_init(); | |
+ crypto_sha256_update(shared_secret, 32); | |
+ crypto_sha256_final(shared_secret); | |
+ | |
+ crypto_sha256_hmac_init(shared_secret, 32, hmac); | |
+ crypto_sha256_update(pinEnc, len); | |
+ if (pinHashEnc != NULL) | |
+ { | |
+ crypto_sha256_update(pinHashEnc, 16); | |
+ } | |
+ crypto_sha256_hmac_final(shared_secret, 32, hmac); | |
+ | |
+ if (memcmp(hmac, pinAuth, 16) != 0) | |
+ { | |
+ printf2(TAG_ERR,"pinAuth failed for update pin\n"); | |
+ dump_hex1(TAG_ERR, hmac,16); | |
+ dump_hex1(TAG_ERR, pinAuth,16); | |
+ return CTAP2_ERR_PIN_AUTH_INVALID; | |
+ } | |
+ | |
+// decrypt new PIN with shared secret | |
+ crypto_aes256_init(shared_secret, NULL); | |
+ | |
+ while((len & 0xf) != 0) // round up to nearest AES block size multiple | |
+ { | |
+ len++; | |
} | |
- ctap_request_result_t result = {.status = 0, .request_completed = false}; | |
- return result; | |
+ | |
+ crypto_aes256_decrypt(pinEnc, len); | |
+ | |
+// validate new PIN (length) | |
+ | |
+ ret = trailing_zeros(pinEnc, NEW_PIN_ENC_MIN_SIZE - 1); | |
+ ret = NEW_PIN_ENC_MIN_SIZE - ret; | |
+ | |
+ if (ret < NEW_PIN_MIN_SIZE || ret >= NEW_PIN_MAX_SIZE) | |
+ { | |
+ printf2(TAG_ERR,"new PIN is too short or too long [%d bytes]\n", ret); | |
+ return CTAP2_ERR_PIN_POLICY_VIOLATION; | |
+ } | |
+ else | |
+ { | |
+ printf1(TAG_CP,"new pin: %s [%d bytes]\n", pinEnc, ret); | |
+ dump_hex1(TAG_CP, pinEnc, ret); | |
+ } | |
+ | |
+// validate device's state, decrypt and compare pinHashEnc (user provided current PIN hash) with stored PIN_CODE_HASH | |
+ | |
+ if (ctap_is_pin_set()) | |
+ { | |
+ if (ctap_device_locked()) | |
+ { | |
+ return CTAP2_ERR_PIN_BLOCKED; | |
+ } | |
+ if (ctap_device_boot_locked()) | |
+ { | |
+ return CTAP2_ERR_PIN_AUTH_BLOCKED; | |
+ } | |
+ crypto_aes256_reset_iv(NULL); | |
+ crypto_aes256_decrypt(pinHashEnc, 16); | |
+ | |
+ uint8_t pinHashEncSalted[32]; | |
+ crypto_sha256_init(); | |
+ crypto_sha256_update(pinHashEnc, 16); | |
+ crypto_sha256_update(STATE.PIN_SALT, sizeof(STATE.PIN_SALT)); | |
+ crypto_sha256_final(pinHashEncSalted); | |
+ | |
+ if (memcmp(pinHashEncSalted, STATE.PIN_CODE_HASH, 16) != 0) | |
+ { | |
+ ctap_reset_key_agreement(); | |
+ ctap_decrement_pin_attempts(); | |
+ if (ctap_device_boot_locked()) | |
+ { | |
+ return CTAP2_ERR_PIN_AUTH_BLOCKED; | |
+ } | |
+ return CTAP2_ERR_PIN_INVALID; | |
+ } | |
+ else | |
+ { | |
+ ctap_reset_pin_attempts(); | |
+ } | |
+ } | |
+ | |
+// set new PIN (update and store PIN_CODE_HASH) | |
+ ctap_update_pin(pinEnc, ret); | |
+ | |
+ return 0; | |
+} | |
+ | |
+uint8_t ctap_add_pin_if_verified(uint8_t * pinTokenEnc, uint8_t * platform_pubkey, uint8_t * pinHashEnc) | |
+{ | |
+ uint8_t shared_secret[32]; | |
+ | |
+ crypto_ecc256_shared_secret(platform_pubkey, KEY_AGREEMENT_PRIV, shared_secret); | |
+ | |
+ crypto_sha256_init(); | |
+ crypto_sha256_update(shared_secret, 32); | |
+ crypto_sha256_final(shared_secret); | |
+ | |
+ crypto_aes256_init(shared_secret, NULL); | |
+ | |
+ crypto_aes256_decrypt(pinHashEnc, 16); | |
+ | |
+ uint8_t pinHashEncSalted[32]; | |
+ crypto_sha256_init(); | |
+ crypto_sha256_update(pinHashEnc, 16); | |
+ crypto_sha256_update(STATE.PIN_SALT, sizeof(STATE.PIN_SALT)); | |
+ crypto_sha256_final(pinHashEncSalted); | |
+ if (memcmp(pinHashEncSalted, STATE.PIN_CODE_HASH, 16) != 0) | |
+ { | |
+ printf2(TAG_ERR,"Pin does not match!\n"); | |
+ printf2(TAG_ERR,"platform-pin-hash: "); dump_hex1(TAG_ERR, pinHashEnc, 16); | |
+ printf2(TAG_ERR,"authentic-pin-hash: "); dump_hex1(TAG_ERR, STATE.PIN_CODE_HASH, 16); | |
+ printf2(TAG_ERR,"shared-secret: "); dump_hex1(TAG_ERR, shared_secret, 32); | |
+ printf2(TAG_ERR,"platform-pubkey: "); dump_hex1(TAG_ERR, platform_pubkey, 64); | |
+ printf2(TAG_ERR,"device-pubkey: "); dump_hex1(TAG_ERR, KEY_AGREEMENT_PUB, 64); | |
+ // Generate new keyAgreement pair | |
+ ctap_reset_key_agreement(); | |
+ ctap_decrement_pin_attempts(); | |
+ if (ctap_device_boot_locked()) | |
+ { | |
+ return CTAP2_ERR_PIN_AUTH_BLOCKED; | |
+ } | |
+ return CTAP2_ERR_PIN_INVALID; | |
+ } | |
+ | |
+ ctap_reset_pin_attempts(); | |
+ crypto_aes256_reset_iv(NULL); | |
+ | |
+ memmove(pinTokenEnc, PIN_TOKEN, PIN_TOKEN_SIZE); | |
+ crypto_aes256_encrypt(pinTokenEnc, PIN_TOKEN_SIZE); | |
+ | |
+ return 0; | |
} | |
-static uint8_t _get_assertion_complete(buffer_t* out_buf) | |
+uint8_t ctap_client_pin(CborEncoder * encoder, uint8_t * request, int length) | |
{ | |
- size_t actual_auth_data_size; | |
- uint8_t auth_data_buf[sizeof(ctap_auth_data_header_t) + 80]; | |
- ctap_get_assertion_state_t* state = &_state.data.get_assertion; | |
- uint8_t ret = _make_authentication_response(&state->req, auth_data_buf, &actual_auth_data_size); | |
- if (ret != CborNoError) { | |
- return ret; | |
+ CTAP_clientPin CP; | |
+ CborEncoder map; | |
+ uint8_t pinTokenEnc[PIN_TOKEN_SIZE]; | |
+ int ret = ctap_parse_client_pin(&CP,request,length); | |
+ | |
+ | |
+ switch(CP.subCommand) | |
+ { | |
+ case CP_cmdSetPin: | |
+ case CP_cmdChangePin: | |
+ case CP_cmdGetPinToken: | |
+ if (ctap_device_locked()) | |
+ { | |
+ return CTAP2_ERR_PIN_BLOCKED; | |
+ } | |
+ if (ctap_device_boot_locked()) | |
+ { | |
+ return CTAP2_ERR_PIN_AUTH_BLOCKED; | |
+ } | |
} | |
- /* Encode the resulting assertion in the output buffer. */ | |
- CborEncoder encoder; | |
- memset(&encoder, 0, sizeof(CborEncoder)); | |
- cbor_encoder_init(&encoder, out_buf->data, out_buf->max_len, 0); | |
- ret = ctap_end_get_assertion(&encoder, &state->auth_credential, auth_data_buf, actual_auth_data_size, state->auth_privkey, state->req.client_data_hash, state->user_id, state->user_id_size); | |
- if (ret != CborNoError) { | |
+ if (ret != 0) | |
+ { | |
+ printf2(TAG_ERR,"error, parse_client_pin failed\n"); | |
return ret; | |
} | |
- out_buf->len = cbor_encoder_get_buffer_size(&encoder, out_buf->data); | |
+ if (CP.pinProtocol != 1 || CP.subCommand == 0) | |
+ { | |
+ return CTAP1_ERR_OTHER; | |
+ } | |
- return CTAP1_ERR_SUCCESS; | |
-} | |
+ int num_map = (CP.getRetries ? 1 : 0); | |
+ | |
+ switch(CP.subCommand) | |
+ { | |
+ case CP_cmdGetRetries: | |
+ printf1(TAG_CP,"CP_cmdGetRetries\n"); | |
+ ret = cbor_encoder_create_map(encoder, &map, 1); | |
+ check_ret(ret); | |
+ | |
+ CP.getRetries = 1; | |
+ | |
+ break; | |
+ case CP_cmdGetKeyAgreement: | |
+ printf1(TAG_CP,"CP_cmdGetKeyAgreement\n"); | |
+ num_map++; | |
+ ret = cbor_encoder_create_map(encoder, &map, num_map); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_encode_int(&map, RESP_keyAgreement); | |
+ check_ret(ret); | |
+ | |
+ if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_FAST); | |
+ crypto_ecc256_compute_public_key(KEY_AGREEMENT_PRIV, KEY_AGREEMENT_PUB); | |
+ if (device_is_nfc() == NFC_IS_ACTIVE) device_set_clock_rate(DEVICE_LOW_POWER_IDLE); | |
+ | |
+ ret = ctap_add_cose_key(&map, KEY_AGREEMENT_PUB, KEY_AGREEMENT_PUB+32, PUB_KEY_CRED_PUB_KEY, COSE_ALG_ECDH_ES_HKDF_256); | |
+ check_retr(ret); | |
+ | |
+ break; | |
+ case CP_cmdSetPin: | |
+ printf1(TAG_CP,"CP_cmdSetPin\n"); | |
+ | |
+ if (ctap_is_pin_set()) | |
+ { | |
+ return CTAP2_ERR_NOT_ALLOWED; | |
+ } | |
+ if (!CP.newPinEncSize || !CP.pinAuthPresent || !CP.keyAgreementPresent) | |
+ { | |
+ return CTAP2_ERR_MISSING_PARAMETER; | |
+ } | |
+ | |
+ ret = ctap_update_pin_if_verified(CP.newPinEnc, CP.newPinEncSize, (uint8_t*)&CP.keyAgreement.pubkey, CP.pinAuth, NULL); | |
+ check_retr(ret); | |
+ break; | |
+ case CP_cmdChangePin: | |
+ printf1(TAG_CP,"CP_cmdChangePin\n"); | |
+ | |
+ if (! ctap_is_pin_set()) | |
+ { | |
+ return CTAP2_ERR_PIN_NOT_SET; | |
+ } | |
+ | |
+ if (!CP.newPinEncSize || !CP.pinAuthPresent || !CP.keyAgreementPresent || !CP.pinHashEncPresent) | |
+ { | |
+ return CTAP2_ERR_MISSING_PARAMETER; | |
+ } | |
+ | |
+ ret = ctap_update_pin_if_verified(CP.newPinEnc, CP.newPinEncSize, (uint8_t*)&CP.keyAgreement.pubkey, CP.pinAuth, CP.pinHashEnc); | |
+ check_retr(ret); | |
+ break; | |
+ case CP_cmdGetPinToken: | |
+ if (!ctap_is_pin_set()) | |
+ { | |
+ return CTAP2_ERR_PIN_NOT_SET; | |
+ } | |
+ num_map++; | |
+ ret = cbor_encoder_create_map(encoder, &map, num_map); | |
+ check_ret(ret); | |
+ | |
+ printf1(TAG_CP,"CP_cmdGetPinToken\n"); | |
+ if (CP.keyAgreementPresent == 0 || CP.pinHashEncPresent == 0) | |
+ { | |
+ printf2(TAG_ERR,"Error, missing keyAgreement or pinHashEnc for cmdGetPin\n"); | |
+ return CTAP2_ERR_MISSING_PARAMETER; | |
+ } | |
+ ret = cbor_encode_int(&map, RESP_pinToken); | |
+ check_ret(ret); | |
+ | |
+ /*ret = ctap_add_pin_if_verified(&map, (uint8_t*)&CP.keyAgreement.pubkey, CP.pinHashEnc);*/ | |
+ ret = ctap_add_pin_if_verified(pinTokenEnc, (uint8_t*)&CP.keyAgreement.pubkey, CP.pinHashEnc); | |
+ check_retr(ret); | |
+ | |
+ ret = cbor_encode_byte_string(&map, pinTokenEnc, PIN_TOKEN_SIZE); | |
+ check_ret(ret); | |
+ | |
+ | |
+ | |
+ break; | |
-static ctap_request_result_t _get_assertion_continue(buffer_t* out_buf) | |
-{ | |
- ctap_request_result_t result = {.status = 0, .request_completed = true}; | |
- ctap_get_assertion_state_t* state = &_state.data.get_assertion; | |
- switch (state->state) { | |
- case CTAP_GET_ASSERTION_CONFIRMED: | |
- result = _get_assertion_select_credential(); | |
- return result; | |
- case CTAP_GET_ASSERTION_DENIED: | |
- result.status = CTAP2_ERR_OPERATION_DENIED; | |
- return result; | |
- case CTAP_GET_ASSERTION_NO_CREDENTIALS: | |
- workflow_status_blocking("No credentials found on this device.", false); | |
- result.status = CTAP2_ERR_NO_CREDENTIALS; | |
- return result; | |
- case CTAP_GET_ASSERTION_UNLOCKED: | |
- /* | |
- * Request permission to the user. | |
- * This must be done before checking for excluded credentials etc. | |
- * so that we don't reveal the existance of credentials without | |
- * the user's consent. | |
- */ | |
- workflow_stack_start_workflow(_get_assertion_confirm(&_state.data.get_assertion.req.rp)); | |
- state->state = CTAP_MAKE_CREDENTIAL_WAIT_CONFIRM; | |
- result.request_completed = false; | |
- return result; | |
- case CTAP_GET_ASSERTION_SELECTED_CREDENTIAL: | |
- result.status = _get_assertion_complete(out_buf); | |
- return result; | |
- case CTAP_GET_ASSERTION_STARTED: | |
- case CTAP_GET_ASSERTION_WAIT_CONFIRM: | |
- case CTAP_GET_ASSERTION_SELECT_CREDENTIAL: | |
- result.request_completed = false; | |
- return result; | |
default: | |
- Abort("Invalid get_assertion state."); | |
+ printf2(TAG_ERR,"Error, invalid client pin subcommand\n"); | |
+ return CTAP1_ERR_OTHER; | |
+ } | |
+ | |
+ if (CP.getRetries) | |
+ { | |
+ ret = cbor_encode_int(&map, RESP_retries); | |
+ check_ret(ret); | |
+ ret = cbor_encode_int(&map, ctap_leftover_pin_attempts()); | |
+ check_ret(ret); | |
+ } | |
+ | |
+ if (num_map || CP.getRetries) | |
+ { | |
+ ret = cbor_encoder_close_container(encoder, &map); | |
+ check_ret(ret); | |
} | |
+ | |
+ return 0; | |
} | |
-void ctap_response_init(ctap_response_t* resp) | |
+void ctap_response_init(CTAP_RESPONSE * resp) | |
{ | |
- memset(resp, 0, sizeof(*resp)); | |
+ memset(resp, 0, sizeof(CTAP_RESPONSE)); | |
resp->data_size = CTAP_RESPONSE_BUFFER_SIZE; | |
} | |
-ctap_request_result_t ctap_request(const in_buffer_t* in_buf, buffer_t* out_buf) | |
+ | |
+uint8_t ctap_request(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp) | |
{ | |
CborEncoder encoder; | |
memset(&encoder,0,sizeof(CborEncoder)); | |
+ uint8_t status = 0; | |
+ uint8_t cmd = *pkt_raw; | |
+ pkt_raw++; | |
+ length--; | |
- uint8_t cmd = *in_buf->data; | |
- in_buffer_t in_req_data = { | |
- .data = in_buf->data + 1, | |
- .len = in_buf->len - 1 | |
- }; | |
+ uint8_t * buf = resp->data; | |
- cbor_encoder_init(&encoder, out_buf->data, out_buf->max_len, 0); | |
- ctap_request_result_t result = {.status = 0, .request_completed = true}; | |
+ cbor_encoder_init(&encoder, buf, resp->data_size, 0); | |
+ | |
+ printf1(TAG_CTAP,"cbor input structure: %d bytes\n", length); | |
+ printf1(TAG_DUMP,"cbor req: "); dump_hex1(TAG_DUMP, pkt_raw, length); | |
switch(cmd) | |
{ | |
- case CTAP_REQ_MAKE_CREDENTIAL: | |
- result.status = ctap_make_credential(&encoder, &in_req_data); | |
- if (result.status == CTAP1_ERR_SUCCESS) { | |
- /* MakeCredential started successfully, don't reply yet. */ | |
- _state.blocking_op = CTAP_BLOCKING_OP_MAKE_CRED; | |
- result.request_completed = false; | |
+ case CTAP_MAKE_CREDENTIAL: | |
+ case CTAP_GET_ASSERTION: | |
+ case CTAP_CBOR_CRED_MGMT: | |
+ case CTAP_CBOR_CRED_MGMT_PRE: | |
+ if (ctap_device_locked()) | |
+ { | |
+ status = CTAP2_ERR_PIN_BLOCKED; | |
+ goto done; | |
} | |
- break; | |
- case CTAP_REQ_GET_ASSERTION: | |
- result.status = ctap_get_assertion(&in_req_data); | |
- if (result.status == CTAP1_ERR_SUCCESS) { | |
- _state.blocking_op = CTAP_BLOCKING_OP_GET_ASSERTION; | |
- result.request_completed = false; | |
+ if (ctap_device_boot_locked()) | |
+ { | |
+ status = CTAP2_ERR_PIN_AUTH_BLOCKED; | |
+ goto done; | |
} | |
break; | |
- case CTAP_REQ_CANCEL: | |
+ } | |
+ | |
+ switch(cmd) | |
+ { | |
+ case CTAP_MAKE_CREDENTIAL: | |
+ printf1(TAG_CTAP,"CTAP_MAKE_CREDENTIAL\n"); | |
+ timestamp(); | |
+ status = ctap_make_credential(&encoder, pkt_raw, length); | |
+ printf1(TAG_TIME,"make_credential time: %d ms\n", timestamp()); | |
+ | |
+ resp->length = cbor_encoder_get_buffer_size(&encoder, buf); | |
+ dump_hex1(TAG_DUMP, buf, resp->length); | |
+ | |
+ break; | |
+ case CTAP_GET_ASSERTION: | |
+ printf1(TAG_CTAP,"CTAP_GET_ASSERTION\n"); | |
+ timestamp(); | |
+ status = ctap_get_assertion(&encoder, pkt_raw, length); | |
+ printf1(TAG_TIME,"get_assertion time: %d ms\n", timestamp()); | |
+ | |
+ resp->length = cbor_encoder_get_buffer_size(&encoder, buf); | |
+ | |
+ printf1(TAG_DUMP,"cbor [%d]: \n", resp->length); | |
+ dump_hex1(TAG_DUMP,buf, resp->length); | |
+ break; | |
+ case CTAP_CANCEL: | |
+ printf1(TAG_CTAP,"CTAP_CANCEL\n"); | |
+ break; | |
+ case CTAP_GET_INFO: | |
+ printf1(TAG_CTAP,"CTAP_GET_INFO\n"); | |
+ status = ctap_get_info(&encoder); | |
+ | |
+ resp->length = cbor_encoder_get_buffer_size(&encoder, buf); | |
+ | |
+ dump_hex1(TAG_DUMP, buf, resp->length); | |
+ | |
+ break; | |
+ case CTAP_CLIENT_PIN: | |
+ printf1(TAG_CTAP,"CTAP_CLIENT_PIN\n"); | |
+ status = ctap_client_pin(&encoder, pkt_raw, length); | |
+ | |
+ resp->length = cbor_encoder_get_buffer_size(&encoder, buf); | |
+ dump_hex1(TAG_DUMP, buf, resp->length); | |
+ break; | |
+ case CTAP_RESET: | |
+ printf1(TAG_CTAP,"CTAP_RESET\n"); | |
+ status = ctap2_user_presence_test(); | |
+ if (status == CTAP1_ERR_SUCCESS) | |
+ { | |
+ ctap_reset(); | |
+ } | |
break; | |
- case CTAP_REQ_GET_INFO: | |
- result.status = ctap_get_info(&encoder); | |
- out_buf->len = cbor_encoder_get_buffer_size(&encoder, out_buf->data); | |
+ case GET_NEXT_ASSERTION: | |
+ printf1(TAG_CTAP,"CTAP_NEXT_ASSERTION\n"); | |
+ if (getAssertionState.lastcmd == CTAP_GET_ASSERTION) | |
+ { | |
+ status = ctap_get_next_assertion(&encoder); | |
+ resp->length = cbor_encoder_get_buffer_size(&encoder, buf); | |
+ dump_hex1(TAG_DUMP, buf, resp->length); | |
+ if (status == 0) | |
+ { | |
+ cmd = CTAP_GET_ASSERTION; // allow for next assertion | |
+ } | |
+ } | |
+ else | |
+ { | |
+ printf2(TAG_ERR, "unwanted GET_NEXT_ASSERTION. lastcmd == 0x%02x\n", getAssertionState.lastcmd); | |
+ status = CTAP2_ERR_NOT_ALLOWED; | |
+ } | |
break; | |
- case CTAP_REQ_CLIENT_PIN: | |
- case CTAP_REQ_RESET: | |
- case CTAP_REQ_GET_NEXT_ASSERTION: | |
- result.status = CTAP2_ERR_NOT_ALLOWED; | |
+ case CTAP_CBOR_CRED_MGMT: | |
+ case CTAP_CBOR_CRED_MGMT_PRE: | |
+ printf1(TAG_CTAP,"CTAP_CBOR_CRED_MGMT_PRE\n"); | |
+ status = ctap_cred_mgmt(&encoder, pkt_raw, length); | |
+ | |
+ resp->length = cbor_encoder_get_buffer_size(&encoder, buf); | |
+ | |
+ dump_hex1(TAG_DUMP,buf, resp->length); | |
break; | |
default: | |
- result.status = CTAP1_ERR_INVALID_COMMAND; | |
+ status = CTAP1_ERR_INVALID_COMMAND; | |
+ printf2(TAG_ERR,"error, invalid cmd: 0x%02x\n", cmd); | |
} | |
- if (result.status != CTAP1_ERR_SUCCESS || !result.request_completed) { | |
- out_buf->len = 0; | |
+done: | |
+ device_set_status(CTAPHID_STATUS_IDLE); | |
+ getAssertionState.lastcmd = cmd; | |
+ | |
+ if (status != CTAP1_ERR_SUCCESS) | |
+ { | |
+ resp->length = 0; | |
} | |
- return result; | |
+ | |
+ printf1(TAG_CTAP,"cbor output structure: %d bytes. Return 0x%02x\n", resp->length, status); | |
+ | |
+ return status; | |
} | |
-ctap_request_result_t ctap_retry(buffer_t* out_buf) | |
+ | |
+ | |
+static void ctap_state_init() | |
{ | |
- ctap_request_result_t result = {.status = 0, .request_completed = true}; | |
- | |
- switch (_state.blocking_op) { | |
- case CTAP_BLOCKING_OP_MAKE_CRED: | |
- result = _make_credential_continue(out_buf); | |
- if (result.request_completed) { | |
- _state.blocking_op = CTAP_BLOCKING_OP_NONE; | |
- _make_credential_free_state(); | |
- } | |
- break; | |
- case CTAP_BLOCKING_OP_GET_ASSERTION: | |
- result = _get_assertion_continue(out_buf); | |
- if (result.request_completed) { | |
- _state.blocking_op = CTAP_BLOCKING_OP_NONE; | |
- _get_assertion_free_state(); | |
- } | |
+ // Set to 0xff instead of 0x00 to be easier on flash | |
+ memset(&STATE, 0xff, sizeof(AuthenticatorState)); | |
+ // Fresh RNG for key | |
+ ctap_generate_rng(STATE.key_space, KEY_SPACE_BYTES); | |
+ | |
+ STATE.is_initialized = INITIALIZED_MARKER; | |
+ STATE.remaining_tries = PIN_LOCKOUT_ATTEMPTS; | |
+ STATE.is_pin_set = 0; | |
+ STATE.rk_stored = 0; | |
+ STATE.data_version = STATE_VERSION; | |
+ | |
+ ctap_reset_rk(); | |
+ | |
+ if (ctap_generate_rng(STATE.PIN_SALT, sizeof(STATE.PIN_SALT)) != 1) { | |
+ printf2(TAG_ERR, "Error, rng failed\n"); | |
+ exit(1); | |
+ } | |
+ | |
+ printf1(TAG_STOR, "Generated PIN SALT: "); | |
+ dump_hex1(TAG_STOR, STATE.PIN_SALT, sizeof STATE.PIN_SALT); | |
+} | |
+ | |
+/** Overwrite master secret from external source. | |
+ * @param keybytes an array of KEY_SPACE_BYTES length. | |
+ * | |
+ * This function should only be called from a privilege mode. | |
+*/ | |
+void ctap_load_external_keys(uint8_t * keybytes){ | |
+ memmove(STATE.key_space, keybytes, KEY_SPACE_BYTES); | |
+ authenticator_write_state(&STATE); | |
+ crypto_load_master_secret(STATE.key_space); | |
+} | |
+ | |
+#include "version.h" | |
+void ctap_init() | |
+{ | |
+ printf1(TAG_ERR,"Current firmware version address: %p\r\n", &firmware_version); | |
+ printf1(TAG_ERR,"Current firmware version: %d.%d.%d.%d (%02x.%02x.%02x.%02x)\r\n", | |
+ firmware_version.major, firmware_version.minor, firmware_version.patch, firmware_version.reserved, | |
+ firmware_version.major, firmware_version.minor, firmware_version.patch, firmware_version.reserved | |
+ ); | |
+ crypto_ecc256_init(); | |
+ | |
+ int is_init = authenticator_read_state(&STATE); | |
+ | |
+ device_set_status(CTAPHID_STATUS_IDLE); | |
+ | |
+ if (is_init) | |
+ { | |
+ printf1(TAG_STOR,"Auth state is initialized\n"); | |
+ } | |
+ else | |
+ { | |
+ ctap_state_init(); | |
+ authenticator_write_state(&STATE); | |
+ } | |
+ | |
+ do_migration_if_required(&STATE); | |
+ | |
+ crypto_load_master_secret(STATE.key_space); | |
+ | |
+ if (ctap_is_pin_set()) | |
+ { | |
+ printf1(TAG_STOR, "attempts_left: %d\n", STATE.remaining_tries); | |
+ } | |
+ else | |
+ { | |
+ printf1(TAG_STOR,"pin not set.\n"); | |
+ } | |
+ if (ctap_device_locked()) | |
+ { | |
+ printf1(TAG_ERR, "DEVICE LOCKED!\n"); | |
+ } | |
+ | |
+ if (ctap_generate_rng(PIN_TOKEN, PIN_TOKEN_SIZE) != 1) | |
+ { | |
+ printf2(TAG_ERR,"Error, rng failed\n"); | |
+ exit(1); | |
+ } | |
+ | |
+ ctap_reset_key_agreement(); | |
+ | |
+#ifdef BRIDGE_TO_WALLET | |
+ wallet_init(); | |
+#endif | |
+ | |
+ | |
+} | |
+ | |
+uint8_t ctap_is_pin_set() | |
+{ | |
+ return STATE.is_pin_set == 1; | |
+} | |
+ | |
+/** | |
+ * Set new PIN, by updating PIN hash. Save state. | |
+ * Globals: STATE | |
+ * @param pin new PIN (raw) | |
+ * @param len pin array length | |
+ */ | |
+void ctap_update_pin(uint8_t * pin, int len) | |
+{ | |
+ if (len >= NEW_PIN_ENC_MIN_SIZE || len < 4) | |
+ { | |
+ printf2(TAG_ERR, "Update pin fail length\n"); | |
+ exit(1); | |
+ } | |
+ | |
+ crypto_sha256_init(); | |
+ crypto_sha256_update(pin, len); | |
+ uint8_t intermediateHash[32]; | |
+ crypto_sha256_final(intermediateHash); | |
+ | |
+ crypto_sha256_init(); | |
+ crypto_sha256_update(intermediateHash, 16); | |
+ memset(intermediateHash, 0, sizeof(intermediateHash)); | |
+ crypto_sha256_update(STATE.PIN_SALT, sizeof(STATE.PIN_SALT)); | |
+ crypto_sha256_final(STATE.PIN_CODE_HASH); | |
+ | |
+ STATE.is_pin_set = 1; | |
+ | |
+ authenticator_write_state(&STATE); | |
+ | |
+ printf1(TAG_CTAP, "New pin set: %s [%d]\n", pin, len); | |
+ dump_hex1(TAG_ERR, STATE.PIN_CODE_HASH, sizeof(STATE.PIN_CODE_HASH)); | |
+} | |
+ | |
+uint8_t ctap_decrement_pin_attempts() | |
+{ | |
+ if (PIN_BOOT_ATTEMPTS_LEFT > 0) | |
+ { | |
+ PIN_BOOT_ATTEMPTS_LEFT--; | |
+ } | |
+ if (! ctap_device_locked()) | |
+ { | |
+ STATE.remaining_tries--; | |
+ ctap_flush_state(); | |
+ printf1(TAG_CP, "ATTEMPTS left: %d\n", STATE.remaining_tries); | |
+ | |
+ if (ctap_device_locked()) | |
+ { | |
+ lock_device_permanently(); | |
+ } | |
+ } | |
+ else | |
+ { | |
+ printf1(TAG_CP, "Device locked!\n"); | |
+ return -1; | |
+ } | |
+ return 0; | |
+} | |
+ | |
+int8_t ctap_device_locked() | |
+{ | |
+ return STATE.remaining_tries <= 0; | |
+} | |
+ | |
+int8_t ctap_device_boot_locked() | |
+{ | |
+ return PIN_BOOT_ATTEMPTS_LEFT <= 0; | |
+} | |
+ | |
+int8_t ctap_leftover_pin_attempts() | |
+{ | |
+ return STATE.remaining_tries; | |
+} | |
+ | |
+void ctap_reset_pin_attempts() | |
+{ | |
+ STATE.remaining_tries = PIN_LOCKOUT_ATTEMPTS; | |
+ PIN_BOOT_ATTEMPTS_LEFT = PIN_BOOT_ATTEMPTS; | |
+ ctap_flush_state(); | |
+} | |
+ | |
+void ctap_reset_state() | |
+{ | |
+ memset(&getAssertionState, 0, sizeof(getAssertionState)); | |
+} | |
+ | |
+uint16_t ctap_keys_stored() | |
+{ | |
+ int total = 0; | |
+ int i; | |
+ for (i = 0; i < MAX_KEYS; i++) | |
+ { | |
+ if (STATE.key_lens[i] != 0xffff) | |
+ { | |
+ total += 1; | |
+ } | |
+ else | |
+ { | |
break; | |
- case CTAP_BLOCKING_OP_NONE: | |
- default: | |
- Abort("Invalid status in ctap_retry"); | |
+ } | |
+ } | |
+ return total; | |
+} | |
+ | |
+static uint16_t key_addr_offset(int index) | |
+{ | |
+ uint16_t offset = 0; | |
+ int i; | |
+ for (i = 0; i < index; i++) | |
+ { | |
+ if (STATE.key_lens[i] != 0xffff) offset += STATE.key_lens[i]; | |
+ } | |
+ return offset; | |
+} | |
+ | |
+uint16_t ctap_key_len(uint8_t index) | |
+{ | |
+ int i = ctap_keys_stored(); | |
+ if (index >= i || index >= MAX_KEYS) | |
+ { | |
+ return 0; | |
+ } | |
+ if (STATE.key_lens[index] == 0xffff) return 0; | |
+ return STATE.key_lens[index]; | |
+ | |
+} | |
+ | |
+int8_t ctap_store_key(uint8_t index, uint8_t * key, uint16_t len) | |
+{ | |
+ int i = ctap_keys_stored(); | |
+ uint16_t offset; | |
+ if (i >= MAX_KEYS || index >= MAX_KEYS || !len) | |
+ { | |
+ return ERR_NO_KEY_SPACE; | |
+ } | |
+ | |
+ if (STATE.key_lens[index] != 0xffff) | |
+ { | |
+ return ERR_KEY_SPACE_TAKEN; | |
+ } | |
+ | |
+ offset = key_addr_offset(index); | |
+ | |
+ if ((offset + len) > KEY_SPACE_BYTES) | |
+ { | |
+ return ERR_NO_KEY_SPACE; | |
+ } | |
+ | |
+ STATE.key_lens[index] = len; | |
+ | |
+ memmove(STATE.key_space + offset, key, len); | |
+ | |
+ ctap_flush_state(); | |
+ | |
+ return 0; | |
+} | |
+ | |
+int8_t ctap_load_key(uint8_t index, uint8_t * key) | |
+{ | |
+ int i = ctap_keys_stored(); | |
+ uint16_t offset; | |
+ uint16_t len; | |
+ if (index >= i || index >= MAX_KEYS) | |
+ { | |
+ return ERR_NO_KEY_SPACE; | |
+ } | |
+ | |
+ if (STATE.key_lens[index] == 0xffff) | |
+ { | |
+ return ERR_KEY_SPACE_EMPTY; | |
+ } | |
+ | |
+ offset = key_addr_offset(index); | |
+ len = ctap_key_len(index); | |
+ | |
+ if ((offset + len) > KEY_SPACE_BYTES) | |
+ { | |
+ return ERR_NO_KEY_SPACE; | |
+ } | |
+ | |
+ memmove(key, STATE.key_space + offset, len); | |
+ | |
+ return 0; | |
+} | |
+ | |
+static void ctap_reset_key_agreement() | |
+{ | |
+ ctap_generate_rng(KEY_AGREEMENT_PRIV, sizeof(KEY_AGREEMENT_PRIV)); | |
+} | |
+ | |
+void ctap_reset() | |
+{ | |
+ ctap_state_init(); | |
+ | |
+ authenticator_write_state(&STATE); | |
+ | |
+ if (ctap_generate_rng(PIN_TOKEN, PIN_TOKEN_SIZE) != 1) | |
+ { | |
+ printf2(TAG_ERR,"Error, rng failed\n"); | |
+ exit(1); | |
} | |
- return result; | |
+ | |
+ ctap_reset_state(); | |
+ ctap_reset_key_agreement(); | |
+ | |
+ crypto_load_master_secret(STATE.key_space); | |
+} | |
+ | |
+void lock_device_permanently() { | |
+ memset(PIN_TOKEN, 0, sizeof(PIN_TOKEN)); | |
+ memset(STATE.PIN_CODE_HASH, 0, sizeof(STATE.PIN_CODE_HASH)); | |
+ | |
+ printf1(TAG_CP, "Device locked!\n"); | |
+ | |
+ authenticator_write_state(&STATE); | |
} | |
diff --git src/fido2/ctap.h src/fido2/ctap.h | |
index c3435b4..db98c27 100644 | |
--- src/fido2/ctap.h | |
+++ src/fido2/ctap.h | |
@@ -7,41 +7,64 @@ | |
#ifndef _CTAP_H | |
#define _CTAP_H | |
-#include <crypto/sha2/sha256.h> | |
-#include <hardfault.h> | |
-#include <u2f/u2f_keyhandle.h> | |
-#include <util.h> | |
- | |
-#ifdef assert | |
-#undef assert | |
-#endif | |
- | |
-/* | |
- * TinyCBOR will use the default definition for assert. | |
- * The USB driver will include its own definition though... | |
- * | |
- * Replace it with our custom definition (i.e. Abort if condition | |
- * is false). | |
- */ | |
-#define assert(cond) \ | |
- do { \ | |
- if (!(cond)) { \ | |
- Abort("Assertion failed:\n" #cond); \ | |
- } \ | |
- } while(0); | |
- | |
-#include <cbor.h> | |
- | |
-#undef assert | |
- | |
-#include <usb/usb_packet.h> | |
- | |
-/** | |
- * Authenticator Status, transmitted through keepalive messages. | |
- */ | |
-#define CTAPHID_STATUS_IDLE 0 | |
-#define CTAPHID_STATUS_PROCESSING 1 | |
-#define CTAPHID_STATUS_UPNEEDED 2 | |
+#include "cbor.h" | |
+ | |
+#define CTAP_MAKE_CREDENTIAL 0x01 | |
+#define CTAP_GET_ASSERTION 0x02 | |
+#define CTAP_CANCEL 0x03 | |
+#define CTAP_GET_INFO 0x04 | |
+#define CTAP_CLIENT_PIN 0x06 | |
+#define CTAP_RESET 0x07 | |
+#define GET_NEXT_ASSERTION 0x08 | |
+#define CTAP_CBOR_CRED_MGMT 0x0A | |
+#define CTAP_VENDOR_FIRST 0x40 | |
+#define CTAP_CBOR_CRED_MGMT_PRE 0x41 | |
+#define CTAP_VENDOR_LAST 0xBF | |
+ | |
+#define MC_clientDataHash 0x01 | |
+#define MC_rp 0x02 | |
+#define MC_user 0x03 | |
+#define MC_pubKeyCredParams 0x04 | |
+#define MC_excludeList 0x05 | |
+#define MC_extensions 0x06 | |
+#define MC_options 0x07 | |
+#define MC_pinAuth 0x08 | |
+#define MC_pinProtocol 0x09 | |
+ | |
+#define GA_rpId 0x01 | |
+#define GA_clientDataHash 0x02 | |
+#define GA_allowList 0x03 | |
+#define GA_extensions 0x04 | |
+#define GA_options 0x05 | |
+#define GA_pinAuth 0x06 | |
+#define GA_pinProtocol 0x07 | |
+ | |
+#define CM_cmd 0x01 | |
+ #define CM_cmdMetadata 0x01 | |
+ #define CM_cmdRPBegin 0x02 | |
+ #define CM_cmdRPNext 0x03 | |
+ #define CM_cmdRKBegin 0x04 | |
+ #define CM_cmdRKNext 0x05 | |
+ #define CM_cmdRKDelete 0x06 | |
+#define CM_subCommandParams 0x02 | |
+ #define CM_subCommandRpId 0x01 | |
+ #define CM_subCommandCred 0x02 | |
+#define CM_pinProtocol 0x03 | |
+#define CM_pinAuth 0x04 | |
+ | |
+#define CP_pinProtocol 0x01 | |
+#define CP_subCommand 0x02 | |
+ #define CP_cmdGetRetries 0x01 | |
+ #define CP_cmdGetKeyAgreement 0x02 | |
+ #define CP_cmdSetPin 0x03 | |
+ #define CP_cmdChangePin 0x04 | |
+ #define CP_cmdGetPinToken 0x05 | |
+#define CP_keyAgreement 0x03 | |
+#define CP_pinAuth 0x04 | |
+#define CP_newPinEnc 0x05 | |
+#define CP_pinHashEnc 0x06 | |
+#define CP_getKeyAgreement 0x07 | |
+#define CP_getRetries 0x08 | |
#define EXT_HMAC_SECRET_COSE_KEY 0x01 | |
#define EXT_HMAC_SECRET_SALT_ENC 0x02 | |
@@ -50,195 +73,158 @@ | |
#define EXT_HMAC_SECRET_REQUESTED 0x01 | |
#define EXT_HMAC_SECRET_PARSED 0x02 | |
-#define CLIENT_DATA_HASH_SIZE (SHA256_LEN) | |
+#define EXT_CRED_PROTECT_INVALID 0x00 | |
+#define EXT_CRED_PROTECT_OPTIONAL 0x01 | |
+#define EXT_CRED_PROTECT_OPTIONAL_WITH_CREDID 0x02 | |
+#define EXT_CRED_PROTECT_REQUIRED 0x03 | |
+ | |
+#define RESP_versions 0x1 | |
+#define RESP_extensions 0x2 | |
+#define RESP_aaguid 0x3 | |
+#define RESP_options 0x4 | |
+#define RESP_maxMsgSize 0x5 | |
+#define RESP_pinProtocols 0x6 | |
+ | |
+#define RESP_fmt 0x01 | |
+#define RESP_authData 0x02 | |
+#define RESP_attStmt 0x03 | |
+ | |
+#define RESP_credential 0x01 | |
+#define RESP_signature 0x03 | |
+#define RESP_publicKeyCredentialUserEntity 0x04 | |
+#define RESP_numberOfCredentials 0x05 | |
+ | |
+#define RESP_keyAgreement 0x01 | |
+#define RESP_pinToken 0x02 | |
+#define RESP_retries 0x03 | |
+ | |
+#define PARAM_clientDataHash (1 << 0) | |
+#define PARAM_rp (1 << 1) | |
+#define PARAM_user (1 << 2) | |
+#define PARAM_pubKeyCredParams (1 << 3) | |
+#define PARAM_excludeList (1 << 4) | |
+#define PARAM_extensions (1 << 5) | |
+#define PARAM_options (1 << 6) | |
+#define PARAM_pinAuth (1 << 7) | |
+#define PARAM_pinProtocol (1 << 8) | |
+#define PARAM_rpId (1 << 9) | |
+#define PARAM_allowList (1 << 10) | |
+ | |
+#define MC_requiredMask (0x0f) | |
+ | |
+ | |
+#define CLIENT_DATA_HASH_SIZE 32 //sha256 hash | |
#define DOMAIN_NAME_MAX_SIZE 253 | |
#define RP_NAME_LIMIT 32 // application limit, name parameter isn't needed. | |
-#define CTAP_USER_ID_MAX_SIZE 64 | |
- | |
-/** | |
- * Maximum length of the CTAP username getting stored | |
- * in a resident credential. | |
- * Can be longer than 32B, but we only store this | |
- * data for displaying it when authenticating. | |
- * So if the actual length is longer we can just display | |
- * a truncated string. | |
- */ | |
-#define CTAP_STORAGE_USER_NAME_LIMIT (20) | |
- | |
-/** | |
- * Maximum length of the CTAP username getting stored | |
- * in a resident credential. | |
- * Can be longer than 32B, but we only store this | |
- * data for displaying it when authenticating. | |
- * So if the actual length is longer we can just display | |
- * a truncated string. | |
- */ | |
-#define CTAP_STORAGE_RP_ID_MAX_SIZE (20) | |
- | |
-/** | |
- * Maximum length of the CTAP username getting stored | |
- * in a resident credential. It could be truncated. | |
- */ | |
-#define CTAP_STORAGE_DISPLAY_NAME_LIMIT (20) | |
- | |
-/** Maximum length of the CTAP username. */ | |
-#define CTAP_USER_NAME_LIMIT (64) | |
+#define USER_ID_MAX_SIZE 64 | |
+#define USER_NAME_LIMIT 65 // Must be minimum of 64 bytes but can be more. | |
#define DISPLAY_NAME_LIMIT 32 // Must be minimum of 64 bytes but can be more. | |
#define ICON_LIMIT 128 // Must be minimum of 64 bytes but can be more. | |
#define CTAP_MAX_MESSAGE_SIZE 1200 | |
#define CREDENTIAL_RK_FLASH_PAD 2 // size of RK should be 8-byte aligned to store in flash easily. | |
- #define CREDENTIAL_TAG_SIZE 16 | |
- #define CREDENTIAL_NONCE_SIZE (16 + CREDENTIAL_RK_FLASH_PAD) | |
- #define CREDENTIAL_COUNTER_SIZE (4) | |
- #define CREDENTIAL_ENC_SIZE 176 // pad to multiple of 16 bytes | |
+#define CREDENTIAL_TAG_SIZE 16 | |
+#define CREDENTIAL_NONCE_SIZE (16 + CREDENTIAL_RK_FLASH_PAD) | |
+#define CREDENTIAL_COUNTER_SIZE (4) | |
+#define CREDENTIAL_ENC_SIZE 176 // pad to multiple of 16 bytes | |
- #define PUB_KEY_CRED_PUB_KEY 0x01 | |
- #define PUB_KEY_CRED_CTAP1 0x41 | |
- #define PUB_KEY_CRED_CUSTOM 0x42 | |
- #define PUB_KEY_CRED_UNKNOWN 0x3F | |
+#define PUB_KEY_CRED_PUB_KEY 0x01 | |
+#define PUB_KEY_CRED_CTAP1 0x41 | |
+#define PUB_KEY_CRED_CUSTOM 0x42 | |
+#define PUB_KEY_CRED_UNKNOWN 0x3F | |
- #define CREDENTIAL_IS_SUPPORTED 1 | |
- #define CREDENTIAL_NOT_SUPPORTED 0 | |
+#define CREDENTIAL_IS_SUPPORTED 1 | |
+#define CREDENTIAL_NOT_SUPPORTED 0 | |
- #define CTAP_CREDENTIAL_LIST_MAX_SIZE 20 | |
+#define ALLOW_LIST_MAX_SIZE 20 | |
- #define NEW_PIN_ENC_MAX_SIZE 256 // includes NULL terminator | |
- #define NEW_PIN_ENC_MIN_SIZE 64 | |
- #define NEW_PIN_MAX_SIZE 64 | |
- #define NEW_PIN_MIN_SIZE 4 | |
+#define NEW_PIN_ENC_MAX_SIZE 256 // includes NULL terminator | |
+#define NEW_PIN_ENC_MIN_SIZE 64 | |
+#define NEW_PIN_MAX_SIZE 64 | |
+#define NEW_PIN_MIN_SIZE 4 | |
- #define CTAP_RESPONSE_BUFFER_SIZE 4096 | |
+#define CTAP_RESPONSE_BUFFER_SIZE 4096 | |
- #define PIN_LOCKOUT_ATTEMPTS 8 // Number of attempts total | |
- #define PIN_BOOT_ATTEMPTS 3 // number of attempts per boot | |
+#define PIN_LOCKOUT_ATTEMPTS 8 // Number of attempts total | |
+#define PIN_BOOT_ATTEMPTS 3 // number of attempts per boot | |
- #define CTAP2_UP_DELAY_MS 29000 | |
+#define CTAP2_UP_DELAY_MS 29000 | |
- #pragma GCC diagnostic push | |
- #pragma GCC diagnostic ignored "-Wpacked" | |
- #pragma GCC diagnostic ignored "-Wattributes" | |
- | |
-typedef struct { | |
- uint8_t id[CTAP_USER_ID_MAX_SIZE]; | |
+typedef struct | |
+{ | |
+ uint8_t id[USER_ID_MAX_SIZE]; | |
uint8_t id_size; | |
- uint8_t name[CTAP_USER_NAME_LIMIT]; | |
- uint8_t display_name[DISPLAY_NAME_LIMIT]; | |
+ uint8_t name[USER_NAME_LIMIT]; | |
+ uint8_t displayName[DISPLAY_NAME_LIMIT]; | |
uint8_t icon[ICON_LIMIT]; | |
-} ctap_user_entity_t; | |
- | |
-#define CTAP_RESIDENT_KEY_VALID (0x01) | |
- | |
-/** | |
- * Invalid keys have their "valid" field | |
- * set to 0xFF so that erased flash is invalid. | |
- */ | |
-#define CTAP_RESIDENT_KEY_INVALID (0xff) | |
- | |
-typedef struct __attribute__((__packed__)) { | |
- uint8_t valid; | |
- | |
- /** Key handle (credential ID) */ | |
- u2f_keyhandle_t key_handle; | |
- | |
- /** | |
- * Human-readable ID of the RP that created the credential. | |
- * This is a NULL-terminated string. | |
- */ | |
- uint8_t rp_id[CTAP_STORAGE_RP_ID_MAX_SIZE]; | |
- /** | |
- * sha256 hash of the original RP id. | |
- * This is necessary (together with the ID) so | |
- * that we can make check RP ids for matching values | |
- * even if the actual RP id is longer than 32 bytes. | |
- */ | |
- uint8_t rp_id_hash[32]; | |
- /** | |
- * User ID that the RP has assigned to our user. | |
- * We need to store this and send it back together | |
- * with our keyhandle when we're asked to authenticate. | |
- */ | |
- uint8_t user_id[CTAP_USER_ID_MAX_SIZE]; | |
- /** | |
- * Size of user_id. | |
- */ | |
- uint8_t user_id_size; | |
- /** | |
- * Username belonging to this credential. | |
- * This is a NULL terminated string. | |
- * Side effect: if a credential is created | |
- * which matches the first CTAP_STORAGE_USER_NAME_LIMIT | |
- * characters of the user and display name of an existing | |
- * credential, the latter is going to be overwritten. | |
- * Can this be used for evil purposes? (it shouldn't). | |
- */ | |
- uint8_t user_name[CTAP_STORAGE_USER_NAME_LIMIT]; | |
- /** | |
- * Display name of the user. Same considerations apply | |
- * as for the user_name. | |
- */ | |
- uint8_t display_name[CTAP_STORAGE_DISPLAY_NAME_LIMIT]; | |
- /** | |
- * Creation "time" of the key. | |
- * This is the value that the U2F counter had when the | |
- * key got created. We must not store any real timestamp, | |
- * but we must be able to sort keys by creation time. | |
- */ | |
- uint32_t creation_time; | |
-} ctap_resident_key_t; | |
- | |
-/** | |
- * Attested credential data, defined | |
- * in [WebAuthn] 6.4.1. | |
- */ | |
-typedef struct __attribute__((packed)) { | |
- /** The AAGUID of the authenticator. */ | |
+}__attribute__((packed)) CTAP_userEntity; | |
+ | |
+typedef struct { | |
+ uint8_t tag[CREDENTIAL_TAG_SIZE]; | |
+ union { | |
+ uint8_t nonce[CREDENTIAL_NONCE_SIZE]; | |
+ struct { | |
+ uint8_t _pad[CREDENTIAL_NONCE_SIZE - 4]; | |
+ uint32_t value; | |
+ }__attribute__((packed)) metadata; | |
+ }__attribute__((packed)) entropy; | |
+ uint8_t rpIdHash[32]; | |
+ uint32_t count; | |
+}__attribute__((packed)) CredentialId; | |
+ | |
+struct __attribute__((packed)) Credential { | |
+ CredentialId id; | |
+ CTAP_userEntity user; | |
+}; | |
+typedef struct { | |
+ CredentialId id; | |
+ CTAP_userEntity user; | |
+ | |
+ // Maximum amount of "extra" space in resident key. | |
+ uint8_t rpId[48]; | |
+ uint8_t rpIdSize; | |
+} __attribute__((packed)) CTAP_residentKey; | |
+ | |
+typedef struct | |
+{ | |
+ uint8_t type; | |
+ struct Credential credential; | |
+} CTAP_credentialDescriptor; | |
+ | |
+typedef struct | |
+{ | |
uint8_t aaguid[16]; | |
- /** Length of the credential ID (big-endian) */ | |
- uint8_t cred_len[2]; | |
- /** Credential ID */ | |
- u2f_keyhandle_t id; | |
-} ctap_attest_data_t; | |
- | |
-/** | |
- * Authenticator data structure, to use | |
- * for authentication operations. It is | |
- * missing the attestedCredentialData | |
- * field. Defined in The WebAuthn specs, 6.1. | |
- */ | |
-typedef struct __attribute__((packed)) { | |
- uint8_t rp_id_hash[32]; | |
+ uint8_t credLenH; | |
+ uint8_t credLenL; | |
+ CredentialId id; | |
+} __attribute__((packed)) CTAP_attestHeader; | |
+ | |
+typedef struct | |
+{ | |
+ uint8_t rpIdHash[32]; | |
uint8_t flags; | |
uint32_t signCount; | |
-} ctap_auth_data_header_t; | |
- | |
-/** | |
- * Authenticator data structure, including | |
- * the attestedCredentialData field. | |
- * Defined in The WebAuthn specs, 6.1. | |
- */ | |
-typedef struct __attribute__((packed)) { | |
- ctap_auth_data_header_t head; | |
- ctap_attest_data_t attest; | |
- /* COSE-encoded pubkey and extension data */ | |
- uint8_t other[310 - sizeof(ctap_auth_data_header_t) - sizeof(ctap_attest_data_t)]; | |
-} ctap_auth_data_t; | |
- | |
-#pragma GCC diagnostic pop | |
+} __attribute__((packed)) CTAP_authDataHeader; | |
+ | |
+typedef struct | |
+{ | |
+ CTAP_authDataHeader head; | |
+ CTAP_attestHeader attest; | |
+} __attribute__((packed)) CTAP_authData; | |
typedef struct | |
{ | |
uint8_t data[CTAP_RESPONSE_BUFFER_SIZE]; | |
uint16_t data_size; | |
uint16_t length; | |
-} ctap_response_t; | |
+} CTAP_RESPONSE; | |
-typedef struct { | |
+struct rpId | |
+{ | |
uint8_t id[DOMAIN_NAME_MAX_SIZE + 1]; // extra for NULL termination | |
- /* TODO change to id_size */ | |
size_t size; | |
uint8_t name[RP_NAME_LIMIT]; | |
-} ctap_rp_id_t; | |
+}; | |
typedef struct | |
{ | |
@@ -257,112 +243,180 @@ typedef struct | |
uint8_t saltEnc[64]; | |
uint8_t saltAuth[32]; | |
COSE_key keyAgreement; | |
- u2f_keyhandle_t* key_handle; | |
+ struct Credential * credential; | |
} CTAP_hmac_secret; | |
typedef struct | |
{ | |
uint8_t hmac_secret_present; | |
CTAP_hmac_secret hmac_secret; | |
-} ctap_extensions_t; | |
+ uint32_t cred_protect; | |
+} CTAP_extensions; | |
typedef struct | |
{ | |
- ctap_user_entity_t user; | |
+ CTAP_userEntity user; | |
uint8_t publicKeyCredentialType; | |
int32_t COSEAlgorithmIdentifier; | |
uint8_t rk; | |
-} ctap_cred_info_t; | |
+} CTAP_credInfo; | |
typedef struct | |
{ | |
- uint32_t param_parsed; | |
- uint8_t client_data_hash[CLIENT_DATA_HASH_SIZE]; | |
- ctap_rp_id_t rp; | |
+ uint32_t paramsParsed; | |
+ uint8_t clientDataHash[CLIENT_DATA_HASH_SIZE]; | |
+ struct rpId rp; | |
- ctap_cred_info_t cred_info; | |
+ CTAP_credInfo credInfo; | |
- CborValue exclude_list; | |
- size_t exclude_list_size; | |
+ CborValue excludeList; | |
+ size_t excludeListSize; | |
uint8_t uv; | |
uint8_t up; | |
- uint8_t pin_auth[16]; | |
- uint8_t pin_auth_present; | |
- // pin_auth_empty is true iff an empty bytestring was provided as pin_auth. | |
- // This is exclusive with |pin_auth_present|. It exists because an empty | |
- // pin_auth is a special signal to block for touch. See | |
+ uint8_t pinAuth[16]; | |
+ uint8_t pinAuthPresent; | |
+ // pinAuthEmpty is true iff an empty bytestring was provided as pinAuth. | |
+ // This is exclusive with |pinAuthPresent|. It exists because an empty | |
+ // pinAuth is a special signal to block for touch. See | |
// https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#using-pinToken-in-authenticatorMakeCredential | |
- uint8_t pin_auth_empty; | |
- int pin_protocol; | |
- ctap_extensions_t extensions; | |
+ uint8_t pinAuthEmpty; | |
+ int pinProtocol; | |
+ CTAP_extensions extensions; | |
-} ctap_make_credential_req_t; | |
+} CTAP_makeCredential; | |
typedef struct | |
{ | |
- uint32_t param_parsed; | |
- uint8_t client_data_hash[CLIENT_DATA_HASH_SIZE]; | |
- uint8_t client_data_hash_present; | |
+ uint32_t paramsParsed; | |
+ uint8_t clientDataHash[CLIENT_DATA_HASH_SIZE]; | |
+ uint8_t clientDataHashPresent; | |
- ctap_rp_id_t rp; | |
+ struct rpId rp; | |
+ | |
+ int credLen; | |
uint8_t rk; | |
uint8_t uv; | |
uint8_t up; | |
- /* TODO remove pin_auth, we don't use it anyway. */ | |
- uint8_t pin_auth[16]; | |
- uint8_t pin_auth_present; | |
- /** | |
- * pin_auth_empty is true iff an empty bytestring was provided as pin_auth. | |
- * This is exclusive with |pin_auth_present|. It exists because an empty | |
- * pin_auth is a special signal to block for touch. See | |
- * https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#using-pinToken-in-authenticatorGetAssertion | |
- */ | |
- uint8_t pin_auth_empty; | |
- int pin_protocol; | |
- | |
- /** | |
- * List of allowed credential descriptors for authentication. | |
- * If this parameter is present, then the authenticator MUST | |
- * use one of these credentials to authenticate. | |
- */ | |
- u2f_keyhandle_t creds[CTAP_CREDENTIAL_LIST_MAX_SIZE]; | |
- /** Number of credential descriptors present in this request. */ | |
- int cred_len; | |
+ uint8_t pinAuth[16]; | |
+ uint8_t pinAuthPresent; | |
+ // pinAuthEmpty is true iff an empty bytestring was provided as pinAuth. | |
+ // This is exclusive with |pinAuthPresent|. It exists because an empty | |
+ // pinAuth is a special signal to block for touch. See | |
+ // https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#using-pinToken-in-authenticatorGetAssertion | |
+ uint8_t pinAuthEmpty; | |
+ int pinProtocol; | |
+ CTAP_credentialDescriptor * creds; | |
uint8_t allowListPresent; | |
- ctap_extensions_t extensions; | |
-} ctap_get_assertion_req_t; | |
+ CTAP_extensions extensions; | |
-typedef struct { | |
- /** CTAP_* success/error code. */ | |
- uint8_t status; | |
- /** If true, a response for this request can be sent. */ | |
- bool request_completed; | |
-} ctap_request_result_t; | |
+} CTAP_getAssertion; | |
-void ctap_response_init(ctap_response_t* resp); | |
+typedef struct | |
+{ | |
+ int cmd; | |
+ struct { | |
+ uint8_t rpIdHash[32]; | |
+ CTAP_credentialDescriptor credentialDescriptor; | |
+ } subCommandParams; | |
+ | |
+ struct { | |
+ uint8_t cmd; | |
+ uint8_t subCommandParamsCborCopy[sizeof(CTAP_credentialDescriptor) + 16]; | |
+ } hashed; | |
+ uint32_t subCommandParamsCborSize; | |
-ctap_request_result_t ctap_request(const in_buffer_t* in_buf, buffer_t* out_buf); | |
+ uint8_t pinAuth[16]; | |
+ uint8_t pinAuthPresent; | |
+ int pinProtocol; | |
+} CTAP_credMgmt; | |
-/** | |
- * Polls an outstanding operation for completion. | |
- * | |
- * @param out_data Buffer to fill with a response (if any is ready). | |
- * @param out_len[out] Length of the response contained in out_data. | |
- * @return Request status. | |
- */ | |
-ctap_request_result_t ctap_retry(buffer_t* out_buf); | |
+ | |
+typedef struct | |
+{ | |
+ int pinProtocol; | |
+ int subCommand; | |
+ COSE_key keyAgreement; | |
+ uint8_t keyAgreementPresent; | |
+ uint8_t pinAuth[16]; | |
+ uint8_t pinAuthPresent; | |
+ uint8_t newPinEnc[NEW_PIN_ENC_MAX_SIZE]; | |
+ int newPinEncSize; | |
+ uint8_t pinHashEnc[16]; | |
+ uint8_t pinHashEncPresent; | |
+ _Bool getKeyAgreement; | |
+ _Bool getRetries; | |
+} CTAP_clientPin; | |
+ | |
+ | |
+struct _getAssertionState { | |
+ // Room for both authData struct and extensions | |
+ struct { | |
+ CTAP_authDataHeader authData; | |
+ uint8_t extensions[80]; | |
+ } __attribute__((packed)) buf; | |
+ CTAP_extensions extensions; | |
+ uint8_t clientDataHash[CLIENT_DATA_HASH_SIZE]; | |
+ CTAP_credentialDescriptor creds[ALLOW_LIST_MAX_SIZE]; | |
+ uint8_t lastcmd; | |
+ uint32_t count; | |
+ uint32_t index; | |
+ uint32_t time; | |
+ uint8_t user_verified; | |
+ uint8_t customCredId[256]; | |
+ uint8_t customCredIdSize; | |
+}; | |
+ | |
+void ctap_response_init(CTAP_RESPONSE * resp); | |
+ | |
+uint8_t ctap_request(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp); | |
+ | |
+// Encodes R,S signature to 2 der sequence of two integers. Sigder must be at least 72 bytes. | |
+// @return length of der signature | |
+int ctap_encode_der_sig(uint8_t const * const in_sigbuf, uint8_t * const out_sigder); | |
// Run ctap related power-up procedures (init pinToken, generate shared secret) | |
-void ctap_init(void); | |
+void ctap_init(); | |
+ | |
+// Resets state between different accesses of different applications | |
+void ctap_reset_state(); | |
+ | |
+uint8_t ctap_add_pin_if_verified(uint8_t * pinTokenEnc, uint8_t * platform_pubkey, uint8_t * pinHashEnc); | |
+uint8_t ctap_update_pin_if_verified(uint8_t * pinEnc, int len, uint8_t * platform_pubkey, uint8_t * pinAuth, uint8_t * pinHashEnc); | |
+ | |
+void ctap_update_pin(uint8_t * pin, int len); | |
+uint8_t ctap_decrement_pin_attempts(); | |
+int8_t ctap_leftover_pin_attempts(); | |
+void ctap_reset_pin_attempts(); | |
+uint8_t ctap_is_pin_set(); | |
+uint8_t ctap_pin_matches(uint8_t * pin, int len); | |
+void ctap_reset(); | |
+int8_t ctap_device_locked(); | |
+int8_t ctap_device_boot_locked(); | |
+ | |
+// Key storage API | |
-void make_auth_tag(uint8_t* rp_id_hash, uint8_t* nonce, uint32_t count, uint8_t* tag); | |
+// Return length of key at index. 0 if not exist. | |
+uint16_t ctap_key_len(uint8_t index); | |
-#endif // _CTAP_H | |
+// See error codes in storage.h | |
+int8_t ctap_store_key(uint8_t index, uint8_t * key, uint16_t len); | |
+int8_t ctap_load_key(uint8_t index, uint8_t * key); | |
+uint16_t ctap_key_len(uint8_t index); | |
+ | |
+#define PIN_TOKEN_SIZE 16 | |
+extern uint8_t PIN_TOKEN[PIN_TOKEN_SIZE]; | |
+extern uint8_t KEY_AGREEMENT_PUB[64]; | |
+ | |
+void lock_device_permanently(); | |
+ | |
+void ctap_load_external_keys(uint8_t * keybytes); | |
+ | |
+#endif | |
diff --git src/fido2/ctap_parse.c src/fido2/ctap_parse.c | |
index ad52797..a6e53f2 100644 | |
--- src/fido2/ctap_parse.c | |
+++ src/fido2/ctap_parse.c | |
@@ -4,70 +4,79 @@ | |
// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or | |
// http://opensource.org/licenses/MIT>, at your option. This file may not be | |
// copied, modified, or distributed except according to those terms. | |
-#include <inttypes.h> | |
#include <stdint.h> | |
#include "cbor.h" | |
#include "ctap.h" | |
+#include "u2f.h" | |
#include "ctap_parse.h" | |
#include "ctap_errors.h" | |
#include "cose_key.h" | |
#include "util.h" | |
+#include "log.h" | |
-#include <u2f/u2f_keyhandle.h> | |
- | |
-#define check_retr(r) \ | |
- do { \ | |
- if ((r) != CborNoError) { \ | |
- return r; \ | |
- } \ | |
- } while(0); | |
- | |
-/** | |
- * Field tags used in MakeCredential requests. | |
- */ | |
-#define MAKE_CREDENTIAL_TAG_CLIENT_DATA_HASH (0x01) | |
-#define MAKE_CREDENTIAL_TAG_RELYING_PARTY (0x02) | |
-#define MAKE_CREDENTIAL_TAG_USER (0x03) | |
-#define MAKE_CREDENTIAL_TAG_PUB_KEY_CRED_PARAMS (0x04) | |
-#define MAKE_CREDENTIAL_TAG_EXCLUDE_LIST (0x05) | |
-#define MAKE_CREDENTIAL_TAG_EXTENSIONS (0x06) | |
-#define MAKE_CREDENTIAL_TAG_OPTIONS (0x07) | |
-#define MAKE_CREDENTIAL_TAG_PIN_AUTH (0x08) | |
-#define MAKE_CREDENTIAL_TAG_PIN_PROTOCOL (0x09) | |
- | |
-/** | |
- * Field tags used in MakeCredential requests. | |
- */ | |
-#define GET_ASSERTION_TAG_RPID (0x01) | |
-#define GET_ASSERTION_TAG_CLIENT_DATA_HASH (0x02) | |
-#define GET_ASSERTION_TAG_ALLOW_LIST (0x03) | |
-#define GET_ASSERTION_TAG_EXTENSIONS (0x04) | |
-#define GET_ASSERTION_TAG_OPTIONS (0x05) | |
-#define GET_ASSERTION_TAG_PIN_AUTH (0x06) | |
-#define GET_ASSERTION_TAG_PIN_PROTOCOL (0x07) | |
- | |
-/** | |
- * Parameters contained in the requests. | |
- */ | |
-#define PARAM_CLIENT_DATA_HASH (1 << 0) | |
-#define PARAM_RP (1 << 1) | |
-#define PARAM_USER (1 << 2) | |
-#define PARAM_PUB_KEY_CRED_PARAMS (1 << 3) | |
-#define PARAM_EXCLUDE_LIST (1 << 4) | |
-#define PARAM_EXTENSIONS (1 << 5) | |
-#define PARAM_OPTIONS (1 << 6) | |
-#define PARAM_PIN_AUTH (1 << 7) | |
-#define PARAM_PIN_PROTOCOL (1 << 8) | |
-#define PARAM_RP_ID (1 << 9) | |
-#define PARAM_ALLOW_LIST (1 << 10) | |
- | |
-/** Required parameters for a MakeCredential request. */ | |
-static const uint8_t MAKE_CREDENTIAL_REQUIRED_PARAM_MASK = | |
- PARAM_CLIENT_DATA_HASH | PARAM_RP | PARAM_USER | PARAM_PUB_KEY_CRED_PARAMS; | |
- | |
-static uint8_t _parse_user(ctap_make_credential_req_t * MC, CborValue * val) | |
+extern struct _getAssertionState getAssertionState; | |
+ | |
+void _check_ret(CborError ret, int line, const char * filename) | |
+{ | |
+ if (ret != CborNoError) | |
+ { | |
+ printf1(TAG_ERR,"CborError: 0x%x: %s: %d: %s\n", ret, filename, line, cbor_error_string(ret)); | |
+ /*exit(1);*/ | |
+ } | |
+} | |
+ | |
+const char * cbor_value_get_type_string(const CborValue *value) | |
+{ | |
+ switch(cbor_value_get_type(value)) | |
+ { | |
+ case CborIntegerType: | |
+ return "CborIntegerType"; | |
+ break; | |
+ case CborByteStringType: | |
+ return "CborByteStringType"; | |
+ break; | |
+ case CborTextStringType: | |
+ return "CborTextStringType"; | |
+ break; | |
+ case CborArrayType: | |
+ return "CborArrayType"; | |
+ break; | |
+ case CborMapType: | |
+ return "CborMapType"; | |
+ break; | |
+ case CborTagType: | |
+ return "CborTagType"; | |
+ break; | |
+ case CborSimpleType: | |
+ return "CborSimpleType"; | |
+ break; | |
+ case CborBooleanType: | |
+ return "CborBooleanType"; | |
+ break; | |
+ case CborNullType: | |
+ return "CborNullType"; | |
+ break; | |
+ case CborUndefinedType: | |
+ return "CborUndefinedType"; | |
+ break; | |
+ case CborHalfFloatType: | |
+ return "CborHalfFloatType"; | |
+ break; | |
+ case CborFloatType: | |
+ return "CborFloatType"; | |
+ break; | |
+ case CborDoubleType: | |
+ return "CborDoubleType"; | |
+ break; | |
+ default: | |
+ return "Invalid type"; | |
+ } | |
+} | |
+ | |
+ | |
+uint8_t parse_user(CTAP_makeCredential * MC, CborValue * val) | |
{ | |
size_t sz, map_length; | |
uint8_t key[24]; | |
@@ -78,6 +87,7 @@ static uint8_t _parse_user(ctap_make_credential_req_t * MC, CborValue * val) | |
if (cbor_value_get_type(val) != CborMapType) | |
{ | |
+ printf2(TAG_ERR,"error, wrong type\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
@@ -91,6 +101,7 @@ static uint8_t _parse_user(ctap_make_credential_req_t * MC, CborValue * val) | |
{ | |
if (cbor_value_get_type(&map) != CborTextStringType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting text string type for user map key, got %s\n", cbor_value_get_type_string(&map)); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
@@ -99,6 +110,7 @@ static uint8_t _parse_user(ctap_make_credential_req_t * MC, CborValue * val) | |
if (ret == CborErrorOutOfMemory) | |
{ | |
+ printf2(TAG_ERR,"Error, rp map key is too large\n"); | |
return CTAP2_ERR_LIMIT_EXCEEDED; | |
} | |
@@ -113,74 +125,83 @@ static uint8_t _parse_user(ctap_make_credential_req_t * MC, CborValue * val) | |
if (cbor_value_get_type(&map) != CborByteStringType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting byte string type for rp map value\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
- sz = CTAP_USER_ID_MAX_SIZE; | |
- ret = cbor_value_copy_byte_string(&map, MC->cred_info.user.id, &sz, NULL); | |
+ sz = USER_ID_MAX_SIZE; | |
+ ret = cbor_value_copy_byte_string(&map, MC->credInfo.user.id, &sz, NULL); | |
if (ret == CborErrorOutOfMemory) | |
{ | |
+ printf2(TAG_ERR,"Error, USER_ID is too large\n"); | |
return CTAP2_ERR_LIMIT_EXCEEDED; | |
} | |
- MC->cred_info.user.id_size = sz; | |
+ MC->credInfo.user.id_size = sz; | |
check_ret(ret); | |
} | |
else if (strcmp((const char *)key, "name") == 0) | |
{ | |
if (cbor_value_get_type(&map) != CborTextStringType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting text string type for user.name value\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
- sz = CTAP_USER_NAME_LIMIT; | |
- ret = cbor_value_copy_text_string(&map, (char *)MC->cred_info.user.name, &sz, NULL); | |
+ sz = USER_NAME_LIMIT; | |
+ ret = cbor_value_copy_text_string(&map, (char *)MC->credInfo.user.name, &sz, NULL); | |
if (ret != CborErrorOutOfMemory) | |
{ // Just truncate the name it's okay | |
check_ret(ret); | |
} | |
- MC->cred_info.user.name[CTAP_USER_NAME_LIMIT - 1] = 0; | |
+ MC->credInfo.user.name[USER_NAME_LIMIT - 1] = 0; | |
} | |
else if (strcmp((const char *)key, "displayName") == 0) | |
{ | |
if (cbor_value_get_type(&map) != CborTextStringType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting text string type for user.displayName value\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
sz = DISPLAY_NAME_LIMIT; | |
- ret = cbor_value_copy_text_string(&map, (char *)MC->cred_info.user.display_name, &sz, NULL); | |
+ ret = cbor_value_copy_text_string(&map, (char *)MC->credInfo.user.displayName, &sz, NULL); | |
if (ret != CborErrorOutOfMemory) | |
{ // Just truncate the name it's okay | |
check_ret(ret); | |
} | |
- MC->cred_info.user.display_name[DISPLAY_NAME_LIMIT - 1] = 0; | |
+ MC->credInfo.user.displayName[DISPLAY_NAME_LIMIT - 1] = 0; | |
} | |
else if (strcmp((const char *)key, "icon") == 0) | |
{ | |
if (cbor_value_get_type(&map) != CborTextStringType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting text string type for user.icon value\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
sz = ICON_LIMIT; | |
- ret = cbor_value_copy_text_string(&map, (char *)MC->cred_info.user.icon, &sz, NULL); | |
+ ret = cbor_value_copy_text_string(&map, (char *)MC->credInfo.user.icon, &sz, NULL); | |
if (ret != CborErrorOutOfMemory) | |
{ // Just truncate the name it's okay | |
check_ret(ret); | |
} | |
- MC->cred_info.user.icon[ICON_LIMIT - 1] = 0; | |
+ MC->credInfo.user.icon[ICON_LIMIT - 1] = 0; | |
} | |
+ else | |
+ { | |
+ printf1(TAG_PARSE,"ignoring key %s for user map\n", key); | |
+ } | |
ret = cbor_value_advance(&map); | |
check_ret(ret); | |
} | |
- MC->param_parsed |= PARAM_USER; | |
+ MC->paramsParsed |= PARAM_user; | |
return 0; | |
} | |
-static uint8_t _parse_pub_key_cred_param(CborValue * val, uint8_t * cred_type, int32_t * alg_type) | |
+uint8_t parse_pub_key_cred_param(CborValue * val, uint8_t * cred_type, int32_t * alg_type) | |
{ | |
CborValue cred; | |
CborValue alg; | |
@@ -190,6 +211,7 @@ static uint8_t _parse_pub_key_cred_param(CborValue * val, uint8_t * cred_type, i | |
if (cbor_value_get_type(val) != CborMapType) | |
{ | |
+ printf2(TAG_ERR,"error, expecting map type, got %s\n", cbor_value_get_type_string(val)); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
@@ -200,10 +222,12 @@ static uint8_t _parse_pub_key_cred_param(CborValue * val, uint8_t * cred_type, i | |
if (cbor_value_get_type(&cred) != CborTextStringType) | |
{ | |
+ printf2(TAG_ERR,"Error, parse_pub_key could not find credential param\n"); | |
return CTAP2_ERR_MISSING_PARAMETER; | |
} | |
if (cbor_value_get_type(&alg) != CborIntegerType) | |
{ | |
+ printf2(TAG_ERR,"Error, parse_pub_key could not find alg param\n"); | |
return CTAP2_ERR_MISSING_PARAMETER; | |
} | |
@@ -228,7 +252,7 @@ static uint8_t _parse_pub_key_cred_param(CborValue * val, uint8_t * cred_type, i | |
} | |
// Check if public key credential+algorithm type is supported | |
-static int _pub_key_cred_param_supported(uint8_t cred, int32_t alg) | |
+static int pub_key_cred_param_supported(uint8_t cred, int32_t alg) | |
{ | |
if (cred == PUB_KEY_CRED_PUB_KEY) | |
{ | |
@@ -241,7 +265,7 @@ static int _pub_key_cred_param_supported(uint8_t cred, int32_t alg) | |
return CREDENTIAL_NOT_SUPPORTED; | |
} | |
-static uint8_t _parse_pub_key_cred_params(ctap_make_credential_req_t * MC, CborValue * val) | |
+uint8_t parse_pub_key_cred_params(CTAP_makeCredential * MC, CborValue * val) | |
{ | |
size_t arr_length; | |
uint8_t cred_type; | |
@@ -253,6 +277,7 @@ static uint8_t _parse_pub_key_cred_params(ctap_make_credential_req_t * MC, CborV | |
if (cbor_value_get_type(val) != CborArrayType) | |
{ | |
+ printf2(TAG_ERR,"error, expecting array type\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
@@ -264,7 +289,7 @@ static uint8_t _parse_pub_key_cred_params(ctap_make_credential_req_t * MC, CborV | |
for (i = 0; i < arr_length; i++) | |
{ | |
- if ((ret = _parse_pub_key_cred_param(&arr, &cred_type, &alg_type)) != 0) | |
+ if ((ret = parse_pub_key_cred_param(&arr, &cred_type, &alg_type)) != 0) | |
{ | |
return ret; | |
} | |
@@ -277,13 +302,13 @@ static uint8_t _parse_pub_key_cred_params(ctap_make_credential_req_t * MC, CborV | |
for (i = 0; i < arr_length; i++) | |
{ | |
- if ((ret = _parse_pub_key_cred_param(&arr, &cred_type, &alg_type)) == 0) | |
+ if ((ret = parse_pub_key_cred_param(&arr, &cred_type, &alg_type)) == 0) | |
{ | |
- if (_pub_key_cred_param_supported(cred_type, alg_type) == CREDENTIAL_IS_SUPPORTED) | |
+ if (pub_key_cred_param_supported(cred_type, alg_type) == CREDENTIAL_IS_SUPPORTED) | |
{ | |
- MC->cred_info.publicKeyCredentialType = cred_type; | |
- MC->cred_info.COSEAlgorithmIdentifier = alg_type; | |
- MC->param_parsed |= PARAM_PUB_KEY_CRED_PARAMS; | |
+ MC->credInfo.publicKeyCredentialType = cred_type; | |
+ MC->credInfo.COSEAlgorithmIdentifier = alg_type; | |
+ MC->paramsParsed |= PARAM_pubKeyCredParams; | |
return 0; | |
} | |
} | |
@@ -291,10 +316,11 @@ static uint8_t _parse_pub_key_cred_params(ctap_make_credential_req_t * MC, CborV | |
check_ret(ret); | |
} | |
+ printf2(TAG_ERR,"Error, no public key credential parameters are supported!\n"); | |
return CTAP2_ERR_UNSUPPORTED_ALGORITHM; | |
} | |
-static uint8_t _parse_fixed_byte_string(CborValue * map, uint8_t * dst, unsigned int len) | |
+uint8_t parse_fixed_byte_string(CborValue * map, uint8_t * dst, unsigned int len) | |
{ | |
size_t sz; | |
int ret; | |
@@ -305,25 +331,28 @@ static uint8_t _parse_fixed_byte_string(CborValue * map, uint8_t * dst, unsigned | |
check_ret(ret); | |
if (sz != len) | |
{ | |
+ printf2(TAG_ERR, "error byte string is different length (%d vs %d)\r\n", len, sz); | |
return CTAP1_ERR_INVALID_LENGTH; | |
} | |
} | |
else | |
{ | |
+ printf2(TAG_ERR, "error, CborByteStringType expected\r\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
return 0; | |
} | |
-static uint8_t parse_verify_exclude_list(CborValue * val) | |
+uint8_t parse_verify_exclude_list(CborValue * val) | |
{ | |
unsigned int i; | |
int ret; | |
CborValue arr; | |
size_t size; | |
- u2f_keyhandle_t cred; | |
+ CTAP_credentialDescriptor cred; | |
if (cbor_value_get_type(val) != CborArrayType) | |
{ | |
+ printf2(TAG_ERR,"error, exclude list is not a map\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
ret = cbor_value_get_array_length(val, &size); | |
@@ -332,11 +361,7 @@ static uint8_t parse_verify_exclude_list(CborValue * val) | |
check_ret(ret); | |
for (i = 0; i < size; i++) | |
{ | |
- bool cred_valid; | |
- ret = ctap_parse_credential_descriptor(&arr, &cred, &cred_valid); | |
- if (!cred_valid) { | |
- return CTAP2_ERR_INVALID_CBOR; | |
- } | |
+ ret = parse_credential_descriptor(&arr, &cred); | |
check_ret(ret); | |
ret = cbor_value_advance(&arr); | |
check_ret(ret); | |
@@ -345,7 +370,7 @@ static uint8_t parse_verify_exclude_list(CborValue * val) | |
return 0; | |
} | |
-static uint8_t _parse_rp_id(ctap_rp_id_t * rp, CborValue * val) | |
+uint8_t parse_rp_id(struct rpId * rp, CborValue * val) | |
{ | |
size_t sz = DOMAIN_NAME_MAX_SIZE; | |
if (cbor_value_get_type(val) != CborTextStringType) | |
@@ -355,6 +380,7 @@ static uint8_t _parse_rp_id(ctap_rp_id_t * rp, CborValue * val) | |
int ret = cbor_value_copy_text_string(val, (char*)rp->id, &sz, NULL); | |
if (ret == CborErrorOutOfMemory) | |
{ | |
+ printf2(TAG_ERR,"Error, RP_ID is too large\n"); | |
return CTAP2_ERR_LIMIT_EXCEEDED; | |
} | |
check_ret(ret); | |
@@ -363,7 +389,7 @@ static uint8_t _parse_rp_id(ctap_rp_id_t * rp, CborValue * val) | |
return 0; | |
} | |
-static uint8_t _parse_rp(ctap_rp_id_t * rp, CborValue * val) | |
+uint8_t parse_rp(struct rpId * rp, CborValue * val) | |
{ | |
size_t sz, map_length; | |
char key[8]; | |
@@ -374,6 +400,7 @@ static uint8_t _parse_rp(ctap_rp_id_t * rp, CborValue * val) | |
if (cbor_value_get_type(val) != CborMapType) | |
{ | |
+ printf2(TAG_ERR,"error, wrong type\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
@@ -389,6 +416,7 @@ static uint8_t _parse_rp(ctap_rp_id_t * rp, CborValue * val) | |
{ | |
if (cbor_value_get_type(&map) != CborTextStringType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting text string type for rp map key, got %s\n", cbor_value_get_type_string(&map)); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
@@ -397,6 +425,7 @@ static uint8_t _parse_rp(ctap_rp_id_t * rp, CborValue * val) | |
if (ret == CborErrorOutOfMemory) | |
{ | |
+ printf2(TAG_ERR,"Error, rp map key is too large\n"); | |
return CTAP2_ERR_LIMIT_EXCEEDED; | |
} | |
check_ret(ret); | |
@@ -407,12 +436,13 @@ static uint8_t _parse_rp(ctap_rp_id_t * rp, CborValue * val) | |
if (cbor_value_get_type(&map) != CborTextStringType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting text string type for rp map value\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
if (strcmp(key, "id") == 0) | |
{ | |
- ret = _parse_rp_id(rp, &map); | |
+ ret = parse_rp_id(rp, &map); | |
if (ret != 0) | |
{ | |
return ret; | |
@@ -428,6 +458,10 @@ static uint8_t _parse_rp(ctap_rp_id_t * rp, CborValue * val) | |
} | |
rp->name[RP_NAME_LIMIT - 1] = 0; | |
} | |
+ else | |
+ { | |
+ printf1(TAG_PARSE,"ignoring key %s for RP map\n", key); | |
+ } | |
ret = cbor_value_advance(&map); | |
check_ret(ret); | |
@@ -435,6 +469,7 @@ static uint8_t _parse_rp(ctap_rp_id_t * rp, CborValue * val) | |
} | |
if (rp->size == 0) | |
{ | |
+ printf2(TAG_ERR,"Error, no RPID provided\n"); | |
return CTAP2_ERR_MISSING_PARAMETER; | |
} | |
@@ -442,7 +477,7 @@ static uint8_t _parse_rp(ctap_rp_id_t * rp, CborValue * val) | |
return 0; | |
} | |
-static uint8_t _parse_options(CborValue * val, uint8_t * rk, uint8_t * uv, uint8_t * up) | |
+uint8_t parse_options(CborValue * val, uint8_t * rk, uint8_t * uv, uint8_t * up) | |
{ | |
size_t sz, map_length; | |
char key[8]; | |
@@ -453,6 +488,7 @@ static uint8_t _parse_options(CborValue * val, uint8_t * rk, uint8_t * uv, uint8 | |
if (cbor_value_get_type(val) != CborMapType) | |
{ | |
+ printf2(TAG_ERR,"error, wrong type\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
@@ -467,6 +503,7 @@ static uint8_t _parse_options(CborValue * val, uint8_t * rk, uint8_t * uv, uint8 | |
{ | |
if (cbor_value_get_type(&map) != CborTextStringType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting text string type for options map key, got %s\n", cbor_value_get_type_string(&map)); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
sz = sizeof(key); | |
@@ -474,6 +511,7 @@ static uint8_t _parse_options(CborValue * val, uint8_t * rk, uint8_t * uv, uint8 | |
if (ret == CborErrorOutOfMemory) | |
{ | |
+ printf2(TAG_ERR,"Error, rp map key is too large\n"); | |
return CTAP2_ERR_LIMIT_EXCEEDED; | |
} | |
check_ret(ret); | |
@@ -484,6 +522,7 @@ static uint8_t _parse_options(CborValue * val, uint8_t * rk, uint8_t * uv, uint8 | |
if (cbor_value_get_type(&map) != CborBooleanType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting bool type for option map value\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
@@ -491,116 +530,34 @@ static uint8_t _parse_options(CborValue * val, uint8_t * rk, uint8_t * uv, uint8 | |
{ | |
ret = cbor_value_get_boolean(&map, &b); | |
check_ret(ret); | |
+ printf1(TAG_GA, "rk: %d\r\n",b); | |
*rk = b; | |
} | |
else if (strncmp(key, "uv",2) == 0) | |
{ | |
ret = cbor_value_get_boolean(&map, &b); | |
check_ret(ret); | |
+ printf1(TAG_GA, "uv: %d\r\n",b); | |
*uv = b; | |
} | |
else if (strncmp(key, "up",2) == 0) | |
{ | |
ret = cbor_value_get_boolean(&map, &b); | |
check_ret(ret); | |
+ printf1(TAG_GA, "up: %d\r\n",b); | |
*up = b; | |
} | |
- ret = cbor_value_advance(&map); | |
- check_ret(ret); | |
- } | |
- return 0; | |
-} | |
- | |
-static uint8_t _parse_cose_key(CborValue * it, COSE_key * cose) | |
-{ | |
- CborValue map; | |
- size_t map_length; | |
- int ret,key; | |
- unsigned int i; | |
- int xkey = 0,ykey = 0; | |
- cose->kty = 0; | |
- cose->crv = 0; | |
- | |
- | |
- CborType type = cbor_value_get_type(it); | |
- if (type != CborMapType) | |
- { | |
- return CTAP2_ERR_INVALID_CBOR_TYPE; | |
- } | |
- | |
- ret = cbor_value_enter_container(it,&map); | |
- check_ret(ret); | |
- | |
- ret = cbor_value_get_map_length(it, &map_length); | |
- check_ret(ret); | |
- | |
- | |
- for (i = 0; i < map_length; i++) | |
- { | |
- if (cbor_value_get_type(&map) != CborIntegerType) | |
- { | |
- return CTAP2_ERR_INVALID_CBOR_TYPE; | |
- } | |
- | |
- ret = cbor_value_get_int_checked(&map, &key); | |
- check_ret(ret); | |
- | |
- ret = cbor_value_advance(&map); | |
- check_ret(ret); | |
- | |
- switch(key) | |
+ else | |
{ | |
- case COSE_KEY_LABEL_KTY: | |
- if (cbor_value_get_type(&map) == CborIntegerType) | |
- { | |
- ret = cbor_value_get_int_checked(&map, &cose->kty); | |
- check_ret(ret); | |
- } | |
- else | |
- { | |
- return CTAP2_ERR_INVALID_CBOR_TYPE; | |
- } | |
- break; | |
- case COSE_KEY_LABEL_ALG: | |
- break; | |
- case COSE_KEY_LABEL_CRV: | |
- if (cbor_value_get_type(&map) == CborIntegerType) | |
- { | |
- ret = cbor_value_get_int_checked(&map, &cose->crv); | |
- check_ret(ret); | |
- } | |
- else | |
- { | |
- return CTAP2_ERR_INVALID_CBOR_TYPE; | |
- } | |
- break; | |
- case COSE_KEY_LABEL_X: | |
- ret = _parse_fixed_byte_string(&map, cose->pubkey.x, 32); | |
- check_retr(ret); | |
- xkey = 1; | |
- | |
- break; | |
- case COSE_KEY_LABEL_Y: | |
- ret = _parse_fixed_byte_string(&map, cose->pubkey.y, 32); | |
- check_retr(ret); | |
- ykey = 1; | |
- | |
- break; | |
- default: | |
- break; | |
+ printf2(TAG_PARSE,"ignoring option specified %s\n", key); | |
} | |
- | |
ret = cbor_value_advance(&map); | |
check_ret(ret); | |
} | |
- if (xkey == 0 || ykey == 0 || cose->kty == 0 || cose->crv == 0) | |
- { | |
- return CTAP2_ERR_MISSING_PARAMETER; | |
- } | |
return 0; | |
} | |
-static uint8_t ctap_parse_hmac_secret(CborValue * val, CTAP_hmac_secret * hs) | |
+uint8_t ctap_parse_hmac_secret(CborValue * val, CTAP_hmac_secret * hs) | |
{ | |
size_t map_length; | |
size_t salt_len; | |
@@ -612,6 +569,7 @@ static uint8_t ctap_parse_hmac_secret(CborValue * val, CTAP_hmac_secret * hs) | |
if (cbor_value_get_type(val) != CborMapType) | |
{ | |
+ printf2(TAG_ERR,"error, wrong type\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
@@ -625,6 +583,7 @@ static uint8_t ctap_parse_hmac_secret(CborValue * val, CTAP_hmac_secret * hs) | |
{ | |
if (cbor_value_get_type(&map) != CborIntegerType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting CborIntegerTypefor hmac-secret map key, got %s\n", cbor_value_get_type_string(&map)); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
ret = cbor_value_get_int(&map, &key); | |
@@ -636,7 +595,7 @@ static uint8_t ctap_parse_hmac_secret(CborValue * val, CTAP_hmac_secret * hs) | |
switch(key) | |
{ | |
case EXT_HMAC_SECRET_COSE_KEY: | |
- ret = _parse_cose_key(&map, &hs->keyAgreement); | |
+ ret = parse_cose_key(&map, &hs->keyAgreement); | |
check_retr(ret); | |
parsed_count++; | |
break; | |
@@ -657,8 +616,6 @@ static uint8_t ctap_parse_hmac_secret(CborValue * val, CTAP_hmac_secret * hs) | |
check_ret(ret); | |
parsed_count++; | |
break; | |
- default: | |
- Abort("ctap_parse_hmac_secret: bad key"); | |
} | |
ret = cbor_value_advance(&map); | |
@@ -667,6 +624,7 @@ static uint8_t ctap_parse_hmac_secret(CborValue * val, CTAP_hmac_secret * hs) | |
if (parsed_count != 3) | |
{ | |
+ printf2(TAG_ERR, "ctap_parse_hmac_secret missing parameter. Got %d.\r\n", parsed_count); | |
return CTAP2_ERR_MISSING_PARAMETER; | |
} | |
@@ -674,7 +632,7 @@ static uint8_t ctap_parse_hmac_secret(CborValue * val, CTAP_hmac_secret * hs) | |
} | |
-static uint8_t ctap_parse_extensions(CborValue* val, ctap_extensions_t* ext) | |
+uint8_t ctap_parse_extensions(CborValue * val, CTAP_extensions * ext) | |
{ | |
CborValue map; | |
size_t sz, map_length; | |
@@ -685,6 +643,7 @@ static uint8_t ctap_parse_extensions(CborValue* val, ctap_extensions_t* ext) | |
if (cbor_value_get_type(val) != CborMapType) | |
{ | |
+ printf2(TAG_ERR,"error, wrong type\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
@@ -698,6 +657,7 @@ static uint8_t ctap_parse_extensions(CborValue* val, ctap_extensions_t* ext) | |
{ | |
if (cbor_value_get_type(&map) != CborTextStringType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting text string type for options map key, got %s\n", cbor_value_get_type_string(&map)); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
sz = sizeof(key); | |
@@ -705,8 +665,9 @@ static uint8_t ctap_parse_extensions(CborValue* val, ctap_extensions_t* ext) | |
if (ret == CborErrorOutOfMemory) | |
{ | |
- cbor_value_advance(&map); | |
- cbor_value_advance(&map); | |
+ printf2(TAG_ERR,"Error, rp map key is too large. Ignoring.\n"); | |
+ check_ret( cbor_value_advance(&map) ); | |
+ check_ret( cbor_value_advance(&map) ); | |
continue; | |
} | |
check_ret(ret); | |
@@ -723,12 +684,26 @@ static uint8_t ctap_parse_extensions(CborValue* val, ctap_extensions_t* ext) | |
ret = cbor_value_get_boolean(&map, &b); | |
check_ret(ret); | |
if (b) ext->hmac_secret_present = EXT_HMAC_SECRET_REQUESTED; | |
+ printf1(TAG_CTAP, "set hmac_secret_present to %d\r\n", b); | |
} | |
else if (cbor_value_get_type(&map) == CborMapType) | |
{ | |
ret = ctap_parse_hmac_secret(&map, &ext->hmac_secret); | |
check_retr(ret); | |
ext->hmac_secret_present = EXT_HMAC_SECRET_PARSED; | |
+ printf1(TAG_CTAP, "parsed hmac_secret request\r\n"); | |
+ } | |
+ else | |
+ { | |
+ printf1(TAG_RED, "warning: hmac_secret request ignored for being wrong type\r\n"); | |
+ } | |
+ } | |
+ else if (strncmp(key, "credProtect",11) == 0) { | |
+ if (cbor_value_get_type(&map) == CborIntegerType) { | |
+ ret = cbor_value_get_int(&map, (int*)&ext->cred_protect); | |
+ check_ret(ret); | |
+ } else { | |
+ printf1(TAG_RED, "warning: credProtect request ignored for being wrong type\r\n"); | |
} | |
} | |
@@ -738,9 +713,8 @@ static uint8_t ctap_parse_extensions(CborValue* val, ctap_extensions_t* ext) | |
return 0; | |
} | |
-uint8_t ctap_parse_make_credential(ctap_make_credential_req_t * MC, CborEncoder * encoder, const in_buffer_t* in_buffer) | |
+uint8_t ctap_parse_make_credential(CTAP_makeCredential * MC, CborEncoder * encoder, uint8_t * request, int length) | |
{ | |
- (void)encoder; | |
int ret; | |
unsigned int i; | |
int key; | |
@@ -748,14 +722,15 @@ uint8_t ctap_parse_make_credential(ctap_make_credential_req_t * MC, CborEncoder | |
CborParser parser; | |
CborValue it,map; | |
- memset(MC, 0, sizeof(*MC)); | |
+ memset(MC, 0, sizeof(CTAP_makeCredential)); | |
MC->up = 0xff; | |
- ret = cbor_parser_init(in_buffer->data, in_buffer->len, CborValidateCanonicalFormat, &parser, &it); | |
+ ret = cbor_parser_init(request, length, CborValidateCanonicalFormat, &parser, &it); | |
check_retr(ret); | |
CborType type = cbor_value_get_type(&it); | |
if (type != CborMapType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting cbor map\n"); | |
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; | |
} | |
@@ -765,12 +740,14 @@ uint8_t ctap_parse_make_credential(ctap_make_credential_req_t * MC, CborEncoder | |
ret = cbor_value_get_map_length(&it, &map_length); | |
check_ret(ret); | |
+ printf1(TAG_MC,"map has %d elements\n",map_length); | |
for (i = 0; i < map_length; i++) | |
{ | |
type = cbor_value_get_type(&map); | |
if (type != CborIntegerType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting int for map key\n"); | |
return CTAP2_ERR_CBOR_UNEXPECTED_TYPE; | |
} | |
ret = cbor_value_get_int_checked(&map, &key); | |
@@ -783,50 +760,64 @@ uint8_t ctap_parse_make_credential(ctap_make_credential_req_t * MC, CborEncoder | |
switch(key) | |
{ | |
- case MAKE_CREDENTIAL_TAG_CLIENT_DATA_HASH: | |
+ case MC_clientDataHash: | |
+ printf1(TAG_MC,"CTAP_clientDataHash\n"); | |
- ret = _parse_fixed_byte_string(&map, MC->client_data_hash, CLIENT_DATA_HASH_SIZE); | |
+ ret = parse_fixed_byte_string(&map, MC->clientDataHash, CLIENT_DATA_HASH_SIZE); | |
if (ret == 0) | |
{ | |
- MC->param_parsed |= PARAM_CLIENT_DATA_HASH; | |
+ MC->paramsParsed |= PARAM_clientDataHash; | |
} | |
+ printf1(TAG_MC," "); dump_hex1(TAG_MC,MC->clientDataHash, 32); | |
break; | |
- case MAKE_CREDENTIAL_TAG_RELYING_PARTY: | |
+ case MC_rp: | |
+ printf1(TAG_MC,"CTAP_rp\n"); | |
- ret = _parse_rp(&MC->rp, &map); | |
+ ret = parse_rp(&MC->rp, &map); | |
if (ret == 0) | |
{ | |
- MC->param_parsed |= PARAM_RP; | |
+ MC->paramsParsed |= PARAM_rp; | |
} | |
+ printf1(TAG_MC," ID: %s\n", MC->rp.id); | |
+ printf1(TAG_MC," name: %s\n", MC->rp.name); | |
break; | |
- case MAKE_CREDENTIAL_TAG_USER: | |
+ case MC_user: | |
+ printf1(TAG_MC,"CTAP_user\n"); | |
- ret = _parse_user(MC, &map); | |
+ ret = parse_user(MC, &map); | |
+ printf1(TAG_MC," ID: "); dump_hex1(TAG_MC, MC->credInfo.user.id, MC->credInfo.user.id_size); | |
+ printf1(TAG_MC," name: %s\n", MC->credInfo.user.name); | |
break; | |
- case MAKE_CREDENTIAL_TAG_PUB_KEY_CRED_PARAMS: | |
+ case MC_pubKeyCredParams: | |
+ printf1(TAG_MC,"CTAP_pubKeyCredParams\n"); | |
- ret = _parse_pub_key_cred_params(MC, &map); | |
+ ret = parse_pub_key_cred_params(MC, &map); | |
+ printf1(TAG_MC," cred_type: 0x%02x\n", MC->credInfo.publicKeyCredentialType); | |
+ printf1(TAG_MC," alg_type: %d\n", MC->credInfo.COSEAlgorithmIdentifier); | |
break; | |
- case MAKE_CREDENTIAL_TAG_EXCLUDE_LIST: | |
+ case MC_excludeList: | |
+ printf1(TAG_MC,"CTAP_excludeList\n"); | |
ret = parse_verify_exclude_list(&map); | |
check_ret(ret); | |
- ret = cbor_value_enter_container(&map, &MC->exclude_list); | |
+ ret = cbor_value_enter_container(&map, &MC->excludeList); | |
check_ret(ret); | |
- ret = cbor_value_get_array_length(&map, &MC->exclude_list_size); | |
+ ret = cbor_value_get_array_length(&map, &MC->excludeListSize); | |
check_ret(ret); | |
+ printf1(TAG_MC,"CTAP_excludeList done\n"); | |
break; | |
- case MAKE_CREDENTIAL_TAG_EXTENSIONS: | |
+ case MC_extensions: | |
+ printf1(TAG_MC,"CTAP_extensions\n"); | |
type = cbor_value_get_type(&map); | |
if (type != CborMapType) | |
{ | |
@@ -836,22 +827,24 @@ uint8_t ctap_parse_make_credential(ctap_make_credential_req_t * MC, CborEncoder | |
check_retr(ret); | |
break; | |
- case MAKE_CREDENTIAL_TAG_OPTIONS: | |
- ret = _parse_options(&map, &MC->cred_info.rk, &MC->uv, &MC->up); | |
+ case MC_options: | |
+ printf1(TAG_MC,"CTAP_options\n"); | |
+ ret = parse_options(&map, &MC->credInfo.rk, &MC->uv, &MC->up); | |
check_retr(ret); | |
break; | |
- case MAKE_CREDENTIAL_TAG_PIN_AUTH: { | |
+ case MC_pinAuth: { | |
+ printf1(TAG_MC,"CTAP_pinAuth\n"); | |
size_t pinSize; | |
if (cbor_value_get_type(&map) == CborByteStringType && | |
cbor_value_get_string_length(&map, &pinSize) == CborNoError && | |
pinSize == 0) | |
{ | |
- MC->pin_auth_empty = 1; | |
+ MC->pinAuthEmpty = 1; | |
break; | |
} | |
- ret = _parse_fixed_byte_string(&map, MC->pin_auth, 16); | |
+ ret = parse_fixed_byte_string(&map, MC->pinAuth, 16); | |
if (CTAP1_ERR_INVALID_LENGTH != ret) // damn microsoft | |
{ | |
check_retr(ret); | |
@@ -860,14 +853,16 @@ uint8_t ctap_parse_make_credential(ctap_make_credential_req_t * MC, CborEncoder | |
{ | |
ret = 0; | |
} | |
- MC->pin_auth_present = 1; | |
+ MC->pinAuthPresent = 1; | |
break; | |
} | |
- case MAKE_CREDENTIAL_TAG_PIN_PROTOCOL: | |
+ case MC_pinProtocol: | |
+ printf1(TAG_MC,"CTAP_pinProtocol\n"); | |
if (cbor_value_get_type(&map) == CborIntegerType) | |
{ | |
- ret = cbor_value_get_int_checked(&map, &MC->pin_protocol); | |
+ ret = cbor_value_get_int_checked(&map, &MC->pinProtocol); | |
check_ret(ret); | |
+ printf1(TAG_MC," == %d\n",MC->pinProtocol); | |
} | |
else | |
{ | |
@@ -877,98 +872,109 @@ uint8_t ctap_parse_make_credential(ctap_make_credential_req_t * MC, CborEncoder | |
break; | |
default: | |
- break; | |
+ printf1(TAG_MC,"invalid key %d\n", key); | |
} | |
if (ret != 0) | |
{ | |
return ret; | |
} | |
- cbor_value_advance(&map); | |
+ ret = cbor_value_advance(&map); | |
check_ret(ret); | |
} | |
- /* Check if all the mandatory parameters are present in the request. */ | |
- if ((MC->param_parsed & MAKE_CREDENTIAL_REQUIRED_PARAM_MASK) != MAKE_CREDENTIAL_REQUIRED_PARAM_MASK) { | |
- return CTAP2_ERR_MISSING_PARAMETER; | |
- } | |
return 0; | |
} | |
-uint8_t ctap_parse_credential_descriptor(CborValue* arr, u2f_keyhandle_t* cred, bool* cred_valid_out) | |
+uint8_t parse_credential_descriptor(CborValue * arr, CTAP_credentialDescriptor * cred) | |
{ | |
int ret; | |
size_t buflen; | |
char type[12]; | |
CborValue val; | |
+ cred->type = 0; | |
- if (cbor_value_get_type(arr) != CborMapType) { | |
+ if (cbor_value_get_type(arr) != CborMapType) | |
+ { | |
+ printf2(TAG_ERR,"Error, CborMapType expected in credential\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
- /* Fetch the key handle. */ | |
ret = cbor_value_map_find_value(arr, "id", &val); | |
check_ret(ret); | |
- if (cbor_value_get_type(&val) != CborByteStringType) { | |
+ if (cbor_value_get_type(&val) != CborByteStringType) | |
+ { | |
+ printf2(TAG_ERR,"Error, No valid ID field (%s)\n", cbor_value_get_type_string(&val)); | |
return CTAP2_ERR_MISSING_PARAMETER; | |
} | |
- buflen = sizeof(*cred); | |
- ret = cbor_value_copy_byte_string(&val, (uint8_t*)cred, &buflen, NULL); | |
+ buflen = sizeof(CredentialId); | |
+ ret = cbor_value_copy_byte_string(&val, (uint8_t*)&cred->credential.id, &buflen, NULL); | |
- if (buflen < sizeof(*cred)) { | |
- /* Not enough bytes to be a credential that we've generated. Skip it. */ | |
- *cred_valid_out = false; | |
- return 0; | |
+ if (buflen == U2F_KEY_HANDLE_SIZE) | |
+ { | |
+ printf2(TAG_PARSE,"CTAP1 credential\n"); | |
+ cred->type = PUB_KEY_CRED_CTAP1; | |
+ } | |
+ else if (buflen != sizeof(CredentialId)) | |
+ { | |
+ printf2(TAG_ERR,"Ignoring credential is incorrect length, treating as custom\n"); | |
+ cred->type = PUB_KEY_CRED_CUSTOM; | |
+ buflen = 256; | |
+ ret = cbor_value_copy_byte_string(&val, getAssertionState.customCredId, &buflen, NULL); | |
+ getAssertionState.customCredIdSize = buflen; | |
} | |
check_ret(ret); | |
- /* Now check the "type" field. */ | |
ret = cbor_value_map_find_value(arr, "type", &val); | |
check_ret(ret); | |
- if (cbor_value_get_type(&val) != CborTextStringType) { | |
- *cred_valid_out = false; | |
+ if (cbor_value_get_type(&val) != CborTextStringType) | |
+ { | |
+ printf2(TAG_ERR,"Error, No valid type field\n"); | |
return CTAP2_ERR_MISSING_PARAMETER; | |
} | |
buflen = sizeof(type); | |
ret = cbor_value_copy_text_string(&val, type, &buflen, NULL); | |
- if (ret == CborErrorOutOfMemory) { | |
- /* | |
- * The type string is too big, so type type of the key | |
- * is not something we know about. | |
- */ | |
- *cred_valid_out = false; | |
- return 0; | |
- } else { | |
+ if (ret == CborErrorOutOfMemory) | |
+ { | |
+ cred->type = PUB_KEY_CRED_UNKNOWN; | |
+ } | |
+ else | |
+ { | |
check_ret(ret); | |
} | |
- if (strncmp(type, "public-key", 11) != 0) { | |
- /* Not a keytype we know. */ | |
- *cred_valid_out = false; | |
- return 0; | |
+ | |
+ if (strncmp(type, "public-key",11) == 0) | |
+ { | |
+ if (0 == cred->type) | |
+ { | |
+ cred->type = PUB_KEY_CRED_PUB_KEY; | |
+ } | |
} | |
- *cred_valid_out = true; | |
+ else | |
+ { | |
+ cred->type = PUB_KEY_CRED_UNKNOWN; | |
+ printf1(TAG_RED, "Unknown type: %s\r\n", type); | |
+ } | |
+ | |
return 0; | |
} | |
-/** | |
- * Parses the list of allowed credentials into GA->creds. | |
- * Updates GA->creds and GA->cred_len. | |
- * @return CTAP status code (0 is success). | |
- */ | |
-static uint8_t parse_allow_list(ctap_get_assertion_req_t* GA, CborValue * it) | |
+uint8_t parse_allow_list(CTAP_getAssertion * GA, CborValue * it) | |
{ | |
CborValue arr; | |
size_t len; | |
int ret; | |
unsigned int i; | |
+ CTAP_credentialDescriptor * cred; | |
if (cbor_value_get_type(it) != CborArrayType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting cbor array\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
@@ -978,46 +984,207 @@ static uint8_t parse_allow_list(ctap_get_assertion_req_t* GA, CborValue * it) | |
ret = cbor_value_get_array_length(it, &len); | |
check_ret(ret); | |
- GA->cred_len = 0; | |
+ GA->credLen = 0; | |
- for (i = 0; i < len; i++) { | |
- if (GA->cred_len >= CTAP_CREDENTIAL_LIST_MAX_SIZE) { | |
+ for(i = 0; i < len; i++) | |
+ { | |
+ if (i >= ALLOW_LIST_MAX_SIZE) | |
+ { | |
+ printf1(TAG_PARSE,"Error, out of memory for allow list.\n"); | |
return CTAP2_ERR_TOO_MANY_ELEMENTS; | |
} | |
- /* Check if this is a credential we should consider. */ | |
- bool cred_valid = false; | |
- u2f_keyhandle_t* cred = &GA->creds[GA->cred_len]; | |
- ret = ctap_parse_credential_descriptor(&arr, cred, &cred_valid); | |
+ GA->credLen += 1; | |
+ cred = &GA->creds[i]; | |
+ ret = parse_credential_descriptor(&arr,cred); | |
check_retr(ret); | |
- if (cred_valid) { | |
- GA->cred_len += 1; | |
- } | |
ret = cbor_value_advance(&arr); | |
check_ret(ret); | |
+ | |
} | |
return 0; | |
} | |
-uint8_t ctap_parse_get_assertion(ctap_get_assertion_req_t * GA, const in_buffer_t* in_buffer) | |
+static uint8_t parse_cred_mgmt_subcommandparams(CborValue * val, CTAP_credMgmt * CM) | |
{ | |
+ size_t map_length; | |
+ int key; | |
int ret; | |
+ unsigned int i; | |
+ CborValue map; | |
+ size_t sz = 32; | |
+ | |
+ if (cbor_value_get_type(val) != CborMapType) | |
+ { | |
+ printf2(TAG_ERR,"error, wrong type\n"); | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ | |
+ | |
+ ret = cbor_value_enter_container(val,&map); | |
+ check_ret(ret); | |
+ | |
+ const uint8_t * start_byte = cbor_value_get_next_byte(&map) - 1; | |
+ | |
+ ret = cbor_value_get_map_length(val, &map_length); | |
+ check_ret(ret); | |
+ | |
+ for (i = 0; i < map_length; i++) | |
+ { | |
+ if (cbor_value_get_type(&map) != CborIntegerType) | |
+ { | |
+ printf2(TAG_ERR,"Error, expecting integer type for map key, got %s\n", cbor_value_get_type_string(&map)); | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ ret = cbor_value_get_int(&map, &key); | |
+ check_ret(ret); | |
+ ret = cbor_value_advance(&map); | |
+ check_ret(ret); | |
+ switch(key) | |
+ { | |
+ case CM_subCommandRpId: | |
+ ret = cbor_value_copy_byte_string(&map, CM->subCommandParams.rpIdHash, &sz, NULL); | |
+ if (ret == CborErrorOutOfMemory) | |
+ { | |
+ printf2(TAG_ERR,"Error, map key is too large\n"); | |
+ return CTAP2_ERR_LIMIT_EXCEEDED; | |
+ } | |
+ check_ret(ret); | |
+ break; | |
+ case CM_subCommandCred: | |
+ ret = parse_credential_descriptor(&map, &CM->subCommandParams.credentialDescriptor); | |
+ check_ret(ret);; | |
+ break; | |
+ } | |
+ ret = cbor_value_advance(&map); | |
+ check_ret(ret); | |
+ } | |
+ | |
+ const uint8_t * end_byte = cbor_value_get_next_byte(&map); | |
+ | |
+ uint32_t length = (uint32_t)end_byte - (uint32_t)start_byte; | |
+ if (length > sizeof(CM->hashed.subCommandParamsCborCopy)) | |
+ { | |
+ return CTAP2_ERR_LIMIT_EXCEEDED; | |
+ } | |
+ // Copy the details that were hashed so they can be verified later. | |
+ memmove(CM->hashed.subCommandParamsCborCopy, start_byte, length); | |
+ CM->subCommandParamsCborSize = length; | |
+ | |
+ return 0; | |
+} | |
+ | |
+uint8_t ctap_parse_cred_mgmt(CTAP_credMgmt * CM, uint8_t * request, int length) | |
+{ | |
+ int ret; | |
+ unsigned int i; | |
int key; | |
size_t map_length; | |
CborParser parser; | |
CborValue it,map; | |
- memset(GA, 0, sizeof(ctap_get_assertion_req_t)); | |
+ memset(CM, 0, sizeof(CTAP_credMgmt)); | |
+ ret = cbor_parser_init(request, length, CborValidateCanonicalFormat, &parser, &it); | |
+ check_ret(ret); | |
+ | |
+ CborType type = cbor_value_get_type(&it); | |
+ if (type != CborMapType) | |
+ { | |
+ printf2(TAG_ERR,"Error, expecting cbor map\n"); | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ | |
+ | |
+ ret = cbor_value_enter_container(&it,&map); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_value_get_map_length(&it, &map_length); | |
+ check_ret(ret); | |
+ | |
+ printf1(TAG_PARSE, "CM map has %d elements\n", map_length); | |
+ | |
+ for (i = 0; i < map_length; i++) | |
+ { | |
+ type = cbor_value_get_type(&map); | |
+ if (type != CborIntegerType) | |
+ { | |
+ printf2(TAG_ERR,"Error, expecting int for map key\n"); | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ ret = cbor_value_get_int_checked(&map, &key); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_value_advance(&map); | |
+ check_ret(ret); | |
+ | |
+ switch(key) | |
+ { | |
+ case CM_cmd: | |
+ printf1(TAG_PARSE, "CM_cmd\n"); | |
+ if (cbor_value_get_type(&map) == CborIntegerType) | |
+ { | |
+ ret = cbor_value_get_int_checked(&map, &CM->cmd); | |
+ check_ret(ret); | |
+ CM->hashed.cmd = CM->cmd; | |
+ } | |
+ else | |
+ { | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ break; | |
+ case CM_subCommandParams: | |
+ printf1(TAG_PARSE, "CM_subCommandParams\n"); | |
+ ret = parse_cred_mgmt_subcommandparams(&map, CM); | |
+ check_ret(ret); | |
+ break; | |
+ case CM_pinProtocol: | |
+ printf1(TAG_PARSE, "CM_pinProtocol\n"); | |
+ if (cbor_value_get_type(&map) == CborIntegerType) | |
+ { | |
+ ret = cbor_value_get_int_checked(&map, &CM->pinProtocol); | |
+ check_ret(ret); | |
+ } | |
+ else | |
+ { | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ break; | |
+ case CM_pinAuth: | |
+ printf1(TAG_PARSE, "CM_pinAuth\n"); | |
+ ret = parse_fixed_byte_string(&map, CM->pinAuth, 16); | |
+ check_retr(ret); | |
+ CM->pinAuthPresent = 1; | |
+ break; | |
+ } | |
+ ret = cbor_value_advance(&map); | |
+ check_ret(ret); | |
+ } | |
+ | |
+ return 0; | |
+} | |
+ | |
+uint8_t ctap_parse_get_assertion(CTAP_getAssertion * GA, uint8_t * request, int length) | |
+{ | |
+ int ret; | |
+ unsigned int i; | |
+ int key; | |
+ size_t map_length; | |
+ CborParser parser; | |
+ CborValue it,map; | |
+ | |
+ memset(GA, 0, sizeof(CTAP_getAssertion)); | |
+ GA->creds = getAssertionState.creds; // Save stack memory | |
GA->up = 0xff; | |
- ret = cbor_parser_init(in_buffer->data, in_buffer->len, CborValidateCanonicalFormat, &parser, &it); | |
+ ret = cbor_parser_init(request, length, CborValidateCanonicalFormat, &parser, &it); | |
check_ret(ret); | |
CborType type = cbor_value_get_type(&it); | |
if (type != CborMapType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting cbor map\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
@@ -1027,11 +1194,14 @@ uint8_t ctap_parse_get_assertion(ctap_get_assertion_req_t * GA, const in_buffer_ | |
ret = cbor_value_get_map_length(&it, &map_length); | |
check_ret(ret); | |
+ printf1(TAG_GA,"GA map has %d elements\n",map_length); | |
- for (size_t i = 0; i < map_length; i++) { | |
+ for (i = 0; i < map_length; i++) | |
+ { | |
type = cbor_value_get_type(&map); | |
if (type != CborIntegerType) | |
{ | |
+ printf2(TAG_ERR,"Error, expecting int for map key\n"); | |
return CTAP2_ERR_INVALID_CBOR_TYPE; | |
} | |
ret = cbor_value_get_int_checked(&map, &key); | |
@@ -1044,45 +1214,53 @@ uint8_t ctap_parse_get_assertion(ctap_get_assertion_req_t * GA, const in_buffer_ | |
switch(key) | |
{ | |
- case GET_ASSERTION_TAG_CLIENT_DATA_HASH: | |
+ case GA_clientDataHash: | |
+ printf1(TAG_GA,"GA_clientDataHash\n"); | |
- ret = _parse_fixed_byte_string(&map, GA->client_data_hash, CLIENT_DATA_HASH_SIZE); | |
+ ret = parse_fixed_byte_string(&map, GA->clientDataHash, CLIENT_DATA_HASH_SIZE); | |
check_retr(ret); | |
- GA->client_data_hash_present = 1; | |
+ GA->clientDataHashPresent = 1; | |
+ printf1(TAG_GA," "); dump_hex1(TAG_GA, GA->clientDataHash, 32); | |
break; | |
- case GET_ASSERTION_TAG_RPID: | |
+ case GA_rpId: | |
+ printf1(TAG_GA,"GA_rpId\n"); | |
- ret = _parse_rp_id(&GA->rp, &map); | |
+ ret = parse_rp_id(&GA->rp, &map); | |
+ printf1(TAG_GA," ID: %s\n", GA->rp.id); | |
break; | |
- case GET_ASSERTION_TAG_ALLOW_LIST: | |
+ case GA_allowList: | |
+ printf1(TAG_GA,"GA_allowList\n"); | |
ret = parse_allow_list(GA, &map); | |
check_ret(ret); | |
GA->allowListPresent = 1; | |
break; | |
- case GET_ASSERTION_TAG_EXTENSIONS: | |
+ case GA_extensions: | |
+ printf1(TAG_GA,"GA_extensions\n"); | |
ret = ctap_parse_extensions(&map, &GA->extensions); | |
check_retr(ret); | |
break; | |
- case GET_ASSERTION_TAG_OPTIONS: | |
- ret = _parse_options(&map, &GA->rk, &GA->uv, &GA->up); | |
+ case GA_options: | |
+ printf1(TAG_GA,"CTAP_options\n"); | |
+ ret = parse_options(&map, &GA->rk, &GA->uv, &GA->up); | |
check_retr(ret); | |
break; | |
- case GET_ASSERTION_TAG_PIN_AUTH: { | |
+ case GA_pinAuth: { | |
+ printf1(TAG_GA,"CTAP_pinAuth\n"); | |
size_t pinSize; | |
if (cbor_value_get_type(&map) == CborByteStringType && | |
cbor_value_get_string_length(&map, &pinSize) == CborNoError && | |
pinSize == 0) | |
{ | |
- GA->pin_auth_empty = 1; | |
+ GA->pinAuthEmpty = 1; | |
break; | |
} | |
- ret = _parse_fixed_byte_string(&map, GA->pin_auth, 16); | |
+ ret = parse_fixed_byte_string(&map, GA->pinAuth, 16); | |
if (CTAP1_ERR_INVALID_LENGTH != ret) // damn microsoft | |
{ | |
check_retr(ret); | |
@@ -1094,14 +1272,15 @@ uint8_t ctap_parse_get_assertion(ctap_get_assertion_req_t * GA, const in_buffer_ | |
} | |
check_retr(ret); | |
- GA->pin_auth_present = 1; | |
+ GA->pinAuthPresent = 1; | |
break; | |
} | |
- case GET_ASSERTION_TAG_PIN_PROTOCOL: | |
+ case GA_pinProtocol: | |
+ printf1(TAG_GA,"CTAP_pinProtocol\n"); | |
if (cbor_value_get_type(&map) == CborIntegerType) | |
{ | |
- ret = cbor_value_get_int_checked(&map, &GA->pin_protocol); | |
+ ret = cbor_value_get_int_checked(&map, &GA->pinProtocol); | |
check_ret(ret); | |
} | |
else | |
@@ -1111,15 +1290,14 @@ uint8_t ctap_parse_get_assertion(ctap_get_assertion_req_t * GA, const in_buffer_ | |
break; | |
- default: | |
- Abort("ctap_parse_get_assertion: bad key."); | |
} | |
if (ret != 0) | |
{ | |
+ printf2(TAG_ERR,"error, parsing failed\n"); | |
return ret; | |
} | |
- cbor_value_advance(&map); | |
+ ret = cbor_value_advance(&map); | |
check_ret(ret); | |
} | |
@@ -1127,4 +1305,246 @@ uint8_t ctap_parse_get_assertion(ctap_get_assertion_req_t * GA, const in_buffer_ | |
return 0; | |
} | |
+uint8_t parse_cose_key(CborValue * it, COSE_key * cose) | |
+{ | |
+ CborValue map; | |
+ size_t map_length; | |
+ int ret,key; | |
+ unsigned int i; | |
+ int xkey = 0,ykey = 0; | |
+ cose->kty = 0; | |
+ cose->crv = 0; | |
+ | |
+ | |
+ CborType type = cbor_value_get_type(it); | |
+ if (type != CborMapType) | |
+ { | |
+ printf2(TAG_ERR,"Error, expecting cbor map\n"); | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ | |
+ ret = cbor_value_enter_container(it,&map); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_value_get_map_length(it, &map_length); | |
+ check_ret(ret); | |
+ | |
+ printf1(TAG_PARSE,"cose key has %d elements\n",map_length); | |
+ | |
+ for (i = 0; i < map_length; i++) | |
+ { | |
+ if (cbor_value_get_type(&map) != CborIntegerType) | |
+ { | |
+ printf2(TAG_ERR,"Error, expecting int for map key\n"); | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ | |
+ ret = cbor_value_get_int_checked(&map, &key); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_value_advance(&map); | |
+ check_ret(ret); | |
+ | |
+ switch(key) | |
+ { | |
+ case COSE_KEY_LABEL_KTY: | |
+ printf1(TAG_PARSE,"COSE_KEY_LABEL_KTY\n"); | |
+ if (cbor_value_get_type(&map) == CborIntegerType) | |
+ { | |
+ ret = cbor_value_get_int_checked(&map, &cose->kty); | |
+ check_ret(ret); | |
+ } | |
+ else | |
+ { | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ break; | |
+ case COSE_KEY_LABEL_ALG: | |
+ printf1(TAG_PARSE,"COSE_KEY_LABEL_ALG\n"); | |
+ break; | |
+ case COSE_KEY_LABEL_CRV: | |
+ printf1(TAG_PARSE,"COSE_KEY_LABEL_CRV\n"); | |
+ if (cbor_value_get_type(&map) == CborIntegerType) | |
+ { | |
+ ret = cbor_value_get_int_checked(&map, &cose->crv); | |
+ check_ret(ret); | |
+ } | |
+ else | |
+ { | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ break; | |
+ case COSE_KEY_LABEL_X: | |
+ printf1(TAG_PARSE,"COSE_KEY_LABEL_X\n"); | |
+ ret = parse_fixed_byte_string(&map, cose->pubkey.x, 32); | |
+ check_retr(ret); | |
+ xkey = 1; | |
+ | |
+ break; | |
+ case COSE_KEY_LABEL_Y: | |
+ printf1(TAG_PARSE,"COSE_KEY_LABEL_Y\n"); | |
+ ret = parse_fixed_byte_string(&map, cose->pubkey.y, 32); | |
+ check_retr(ret); | |
+ ykey = 1; | |
+ | |
+ break; | |
+ default: | |
+ printf1(TAG_PARSE,"Warning, unrecognized cose key option %d\n", key); | |
+ } | |
+ | |
+ ret = cbor_value_advance(&map); | |
+ check_ret(ret); | |
+ } | |
+ if (xkey == 0 || ykey == 0 || cose->kty == 0 || cose->crv == 0) | |
+ { | |
+ return CTAP2_ERR_MISSING_PARAMETER; | |
+ } | |
+ return 0; | |
+} | |
+ | |
+uint8_t ctap_parse_client_pin(CTAP_clientPin * CP, uint8_t * request, int length) | |
+{ | |
+ int ret; | |
+ unsigned int i; | |
+ int key; | |
+ size_t map_length; | |
+ size_t sz; | |
+ CborParser parser; | |
+ CborValue it,map; | |
+ | |
+ memset(CP, 0, sizeof(CTAP_clientPin)); | |
+ ret = cbor_parser_init(request, length, CborValidateCanonicalFormat, &parser, &it); | |
+ check_ret(ret); | |
+ CborType type = cbor_value_get_type(&it); | |
+ if (type != CborMapType) | |
+ { | |
+ printf2(TAG_ERR,"Error, expecting cbor map\n"); | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ | |
+ ret = cbor_value_enter_container(&it,&map); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_value_get_map_length(&it, &map_length); | |
+ check_ret(ret); | |
+ | |
+ printf1(TAG_CP,"CP map has %d elements\n",map_length); | |
+ | |
+ for (i = 0; i < map_length; i++) | |
+ { | |
+ type = cbor_value_get_type(&map); | |
+ if (type != CborIntegerType) | |
+ { | |
+ printf2(TAG_ERR,"Error, expecting int for map key\n"); | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ ret = cbor_value_get_int_checked(&map, &key); | |
+ check_ret(ret); | |
+ | |
+ ret = cbor_value_advance(&map); | |
+ check_ret(ret); | |
+ ret = 0; | |
+ | |
+ switch(key) | |
+ { | |
+ case CP_pinProtocol: | |
+ printf1(TAG_CP,"CP_pinProtocol\n"); | |
+ if (cbor_value_get_type(&map) == CborIntegerType) | |
+ { | |
+ cbor_value_get_int_checked(&map, &CP->pinProtocol); | |
+ check_ret(ret); | |
+ } | |
+ else | |
+ { | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ break; | |
+ case CP_subCommand: | |
+ printf1(TAG_CP,"CP_subCommand\n"); | |
+ if (cbor_value_get_type(&map) == CborIntegerType) | |
+ { | |
+ cbor_value_get_int_checked(&map, &CP->subCommand); | |
+ check_ret(ret); | |
+ } | |
+ else | |
+ { | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ | |
+ break; | |
+ case CP_keyAgreement: | |
+ printf1(TAG_CP,"CP_keyAgreement\n"); | |
+ ret = parse_cose_key(&map, &CP->keyAgreement); | |
+ check_retr(ret); | |
+ CP->keyAgreementPresent = 1; | |
+ break; | |
+ case CP_pinAuth: | |
+ printf1(TAG_CP,"CP_pinAuth\n"); | |
+ | |
+ ret = parse_fixed_byte_string(&map, CP->pinAuth, 16); | |
+ check_retr(ret); | |
+ CP->pinAuthPresent = 1; | |
+ break; | |
+ case CP_newPinEnc: | |
+ printf1(TAG_CP,"CP_newPinEnc\n"); | |
+ if (cbor_value_get_type(&map) == CborByteStringType) | |
+ { | |
+ ret = cbor_value_calculate_string_length(&map, &sz); | |
+ check_ret(ret); | |
+ if (sz > NEW_PIN_ENC_MAX_SIZE || sz < NEW_PIN_ENC_MIN_SIZE) | |
+ { | |
+ return CTAP2_ERR_PIN_POLICY_VIOLATION; | |
+ } | |
+ | |
+ CP->newPinEncSize = sz; | |
+ sz = NEW_PIN_ENC_MAX_SIZE; | |
+ ret = cbor_value_copy_byte_string(&map, CP->newPinEnc, &sz, NULL); | |
+ check_ret(ret); | |
+ } | |
+ else | |
+ { | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ | |
+ break; | |
+ case CP_pinHashEnc: | |
+ printf1(TAG_CP,"CP_pinHashEnc\n"); | |
+ | |
+ ret = parse_fixed_byte_string(&map, CP->pinHashEnc, 16); | |
+ check_retr(ret); | |
+ CP->pinHashEncPresent = 1; | |
+ | |
+ break; | |
+ case CP_getKeyAgreement: | |
+ printf1(TAG_CP,"CP_getKeyAgreement\n"); | |
+ if (cbor_value_get_type(&map) != CborBooleanType) | |
+ { | |
+ printf2(TAG_ERR,"Error, expecting cbor boolean\n"); | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ ret = cbor_value_get_boolean(&map, &CP->getKeyAgreement); | |
+ check_ret(ret); | |
+ break; | |
+ case CP_getRetries: | |
+ printf1(TAG_CP,"CP_getRetries\n"); | |
+ if (cbor_value_get_type(&map) != CborBooleanType) | |
+ { | |
+ printf2(TAG_ERR,"Error, expecting cbor boolean\n"); | |
+ return CTAP2_ERR_INVALID_CBOR_TYPE; | |
+ } | |
+ ret = cbor_value_get_boolean(&map, &CP->getRetries); | |
+ check_ret(ret); | |
+ break; | |
+ default: | |
+ printf1(TAG_CP,"Unknown key %d\n", key); | |
+ } | |
+ | |
+ ret = cbor_value_advance(&map); | |
+ check_ret(ret); | |
+ | |
+ } | |
+ | |
+ | |
+ return 0; | |
+} | |
diff --git src/fido2/ctap_parse.h src/fido2/ctap_parse.h | |
index 707ead4..e0be807 100644 | |
--- src/fido2/ctap_parse.h | |
+++ src/fido2/ctap_parse.h | |
@@ -7,19 +7,37 @@ | |
#ifndef _CTAP_PARSE_H | |
#define _CTAP_PARSE_H | |
-#include "ctap_errors.h" | |
-#define check_ret(r) \ | |
- do { \ | |
- if ((r) != CborNoError) { \ | |
- return CTAP2_ERR_CBOR_PARSING; \ | |
- } \ | |
- } while(0); | |
+#define check_ret(r) _check_ret(r,__LINE__, __FILE__);\ | |
+ if ((r) != CborNoError) return CTAP2_ERR_CBOR_PARSING; | |
-const char* cbor_value_get_type_string(const CborValue *value); | |
+#define check_retr(r) _check_ret(r,__LINE__, __FILE__);\ | |
+ if ((r) != CborNoError) return r; | |
+ | |
+ | |
+extern void _check_ret(CborError ret, int line, const char * filename); | |
+ | |
+ | |
+const char * cbor_value_get_type_string(const CborValue *value); | |
+ | |
+ | |
+uint8_t parse_user(CTAP_makeCredential * MC, CborValue * val); | |
+uint8_t parse_pub_key_cred_param(CborValue * val, uint8_t * cred_type, int32_t * alg_type); | |
+uint8_t parse_pub_key_cred_params(CTAP_makeCredential * MC, CborValue * val); | |
+uint8_t parse_fixed_byte_string(CborValue * map, uint8_t * dst, unsigned int len); | |
+uint8_t parse_rp_id(struct rpId * rp, CborValue * val); | |
+uint8_t parse_rp(struct rpId * rp, CborValue * val); | |
+uint8_t parse_options(CborValue * val, uint8_t * rk, uint8_t * uv, uint8_t * up); | |
+ | |
+uint8_t parse_allow_list(CTAP_getAssertion * GA, CborValue * it); | |
+uint8_t parse_cose_key(CborValue * it, COSE_key * cose); | |
+ | |
+ | |
+uint8_t ctap_parse_make_credential(CTAP_makeCredential * MC, CborEncoder * encoder, uint8_t * request, int length); | |
+uint8_t ctap_parse_get_assertion(CTAP_getAssertion * GA, uint8_t * request, int length); | |
+uint8_t ctap_parse_cred_mgmt(CTAP_credMgmt * CM, uint8_t * request, int length); | |
+uint8_t ctap_parse_client_pin(CTAP_clientPin * CP, uint8_t * request, int length); | |
+uint8_t parse_credential_descriptor(CborValue * arr, CTAP_credentialDescriptor * cred); | |
-uint8_t ctap_parse_make_credential(ctap_make_credential_req_t* MC, CborEncoder * encoder, const in_buffer_t* in_buffer); | |
-uint8_t ctap_parse_get_assertion(ctap_get_assertion_req_t* GA, const in_buffer_t* in_buffer); | |
-uint8_t ctap_parse_credential_descriptor(CborValue * arr, u2f_keyhandle_t* cred, bool* cred_valid_out); | |
#endif | |
diff --git src/fido2/device.c src/fido2/device.c | |
index aa335f1..b7d3861 100644 | |
--- src/fido2/device.c | |
+++ src/fido2/device.c | |
@@ -1,35 +1,225 @@ | |
-#include "device.h" | |
- | |
-#include <random.h> | |
-#include <screen.h> | |
-#include <workflow/confirm.h> | |
- | |
-/* | |
- * Get the AAGUID (identifier of the type of device authenticating). | |
- */ | |
-void device_read_aaguid(uint8_t * dst) { | |
- /* | |
- * Hack: | |
- * For now, return the AAGUID of a YubiKey 5 (USB-A, No NFC) - ee882879-721c-4913-9775-3dfcce97072a | |
- * See https://support.yubico.com/support/solutions/articles/15000028710-yubikey-hardware-fido2-aaguids | |
- */ | |
- const char yubikey_aaguid[16] = {0xee, 0x88, 0x28, 0x79, 0x72, 0x1c, 0x49, 0x13, 0x97, 0x75, 0x3d, 0xfc, 0xce, 0x97, 0x07, 0x2a}; | |
- memcpy(dst, yubikey_aaguid, 16); | |
-} | |
- | |
-int ctap_generate_rng(uint8_t* dst, size_t num) { | |
- /* Generate bytes in chunks of 32 bytes into the destination buffer. */ | |
- size_t n_32bytes_chunks = num / 32; | |
- for (size_t i = 0; i < n_32bytes_chunks; ++i) { | |
- random_32_bytes(dst + i * 32); | |
- } | |
- /* Generate the last N bytes as needed. */ | |
- int bytes_missing = num % 32; | |
- if (bytes_missing) { | |
- int final_word_offset = num - bytes_missing; | |
- uint8_t last_bytes[32]; | |
- random_32_bytes(last_bytes); | |
- memcpy(dst + final_word_offset, last_bytes, bytes_missing); | |
+// Copyright 2019 SoloKeys Developers | |
+// | |
+// Licensed under the Apache License, Version 2.0, <LICENSE-APACHE or | |
+// http://apache.org/licenses/LICENSE-2.0> or the MIT license <LICENSE-MIT or | |
+// http://opensource.org/licenses/MIT>, at your option. This file may not be | |
+// copied, modified, or distributed except according to those terms. | |
+ | |
+/** device.c | |
+ * | |
+ * This contains (weak) implementations | |
+ * to get FIDO2 working initially on a device. They probably | |
+ * aren't what you want to keep, but are designed to be replaced | |
+ * with some other platform specific implementation. | |
+ * | |
+ * For real examples, see the STM32L4 implementation and the PC implementation of device.c. | |
+ * | |
+*/ | |
+#include <stdint.h> | |
+#include <stdlib.h> | |
+#include <string.h> | |
+#include "ctaphid.h" | |
+#include "log.h" | |
+#include APP_CONFIG | |
+ | |
+#define RK_NUM 50 | |
+ | |
+struct ResidentKeyStore { | |
+ CTAP_residentKey rks[RK_NUM]; | |
+} RK_STORE; | |
+ | |
+ | |
+static bool _up_disabled = false; | |
+ | |
+static uint8_t _attestation_cert_der[] = | |
+"\x30\x82\x01\xfb\x30\x82\x01\xa1\xa0\x03\x02\x01\x02\x02\x01\x00\x30\x0a\x06\x08" | |
+"\x2a\x86\x48\xce\x3d\x04\x03\x02\x30\x2c\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13" | |
+"\x02\x55\x53\x31\x0b\x30\x09\x06\x03\x55\x04\x08\x0c\x02\x4d\x44\x31\x10\x30\x0e" | |
+"\x06\x03\x55\x04\x0a\x0c\x07\x54\x45\x53\x54\x20\x43\x41\x30\x20\x17\x0d\x31\x38" | |
+"\x30\x35\x31\x30\x30\x33\x30\x36\x32\x30\x5a\x18\x0f\x32\x30\x36\x38\x30\x34\x32" | |
+"\x37\x30\x33\x30\x36\x32\x30\x5a\x30\x7c\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13" | |
+"\x02\x55\x53\x31\x0b\x30\x09\x06\x03\x55\x04\x08\x0c\x02\x4d\x44\x31\x0f\x30\x0d" | |
+"\x06\x03\x55\x04\x07\x0c\x06\x4c\x61\x75\x72\x65\x6c\x31\x15\x30\x13\x06\x03\x55" | |
+"\x04\x0a\x0c\x0c\x54\x45\x53\x54\x20\x43\x4f\x4d\x50\x41\x4e\x59\x31\x22\x30\x20" | |
+"\x06\x03\x55\x04\x0b\x0c\x19\x41\x75\x74\x68\x65\x6e\x74\x69\x63\x61\x74\x6f\x72" | |
+"\x20\x41\x74\x74\x65\x73\x74\x61\x74\x69\x6f\x6e\x31\x14\x30\x12\x06\x03\x55\x04" | |
+"\x03\x0c\x0b\x63\x6f\x6e\x6f\x72\x70\x70\x2e\x63\x6f\x6d\x30\x59\x30\x13\x06\x07" | |
+"\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00" | |
+"\x04\x45\xa9\x02\xc1\x2e\x9c\x0a\x33\xfa\x3e\x84\x50\x4a\xb8\x02\xdc\x4d\xb9\xaf" | |
+"\x15\xb1\xb6\x3a\xea\x8d\x3f\x03\x03\x55\x65\x7d\x70\x3f\xb4\x02\xa4\x97\xf4\x83" | |
+"\xb8\xa6\xf9\x3c\xd0\x18\xad\x92\x0c\xb7\x8a\x5a\x3e\x14\x48\x92\xef\x08\xf8\xca" | |
+"\xea\xfb\x32\xab\x20\xa3\x62\x30\x60\x30\x46\x06\x03\x55\x1d\x23\x04\x3f\x30\x3d" | |
+"\xa1\x30\xa4\x2e\x30\x2c\x31\x0b\x30\x09\x06\x03\x55\x04\x06\x13\x02\x55\x53\x31" | |
+"\x0b\x30\x09\x06\x03\x55\x04\x08\x0c\x02\x4d\x44\x31\x10\x30\x0e\x06\x03\x55\x04" | |
+"\x0a\x0c\x07\x54\x45\x53\x54\x20\x43\x41\x82\x09\x00\xf7\xc9\xec\x89\xf2\x63\x94" | |
+"\xd9\x30\x09\x06\x03\x55\x1d\x13\x04\x02\x30\x00\x30\x0b\x06\x03\x55\x1d\x0f\x04" | |
+"\x04\x03\x02\x04\xf0\x30\x0a\x06\x08\x2a\x86\x48\xce\x3d\x04\x03\x02\x03\x48\x00" | |
+"\x30\x45\x02\x20\x18\x38\xb0\x45\x03\x69\xaa\xa7\xb7\x38\x62\x01\xaf\x24\x97\x5e" | |
+"\x7e\x74\x64\x1b\xa3\x7b\xf7\xe6\xd3\xaf\x79\x28\xdb\xdc\xa5\x88\x02\x21\x00\xcd" | |
+"\x06\xf1\xe3\xab\x16\x21\x8e\xd8\xc0\x14\xaf\x09\x4f\x5b\x73\xef\x5e\x9e\x4b\xe7" | |
+"\x35\xeb\xdd\x9b\x6d\x8f\x7d\xf3\xc4\x3a\xd7"; | |
+ | |
+ | |
+__attribute__((weak)) void device_attestation_read_cert_der(uint8_t * dst){ | |
+ memmove(dst, _attestation_cert_der, device_attestation_cert_der_get_size()); | |
+} | |
+ | |
+__attribute__((weak)) uint8_t * device_get_attestation_key(){ | |
+ static uint8_t attestation_key[] = | |
+ "\xcd\x67\xaa\x31\x0d\x09\x1e\xd1\x6e\x7e\x98\x92\xaa" | |
+ "\x07\x0e\x19\x94\xfc\xd7\x14\xae\x7c\x40\x8f\xb9\x46" | |
+ "\xb7\x2e\x5f\xe7\x5d\x30"; | |
+ return attestation_key; | |
+} | |
+ | |
+__attribute__((weak)) uint16_t device_attestation_cert_der_get_size(){ | |
+ return sizeof(_attestation_cert_der)-1; | |
+} | |
+ | |
+__attribute__((weak)) void device_reboot() | |
+{ | |
+ printf1(TAG_RED, "REBOOT command recieved!\r\n"); | |
+ exit(100); | |
+} | |
+ | |
+__attribute__((weak)) void device_set_status(uint32_t status) | |
+{ | |
+ static uint32_t __device_status = 0; | |
+ if (status != CTAPHID_STATUS_IDLE && __device_status != status) | |
+ { | |
+ ctaphid_update_status(status); | |
+ } | |
+ __device_status = status; | |
+} | |
+ | |
+ | |
+__attribute__((weak)) void usbhid_close(){/**/} | |
+ | |
+ | |
+__attribute__((weak)) void device_init(int argc, char *argv[]){/**/} | |
+ | |
+__attribute__((weak)) void device_disable_up(bool disable) | |
+{ | |
+ _up_disabled = disable; | |
+} | |
+ | |
+__attribute__((weak)) int ctap_user_presence_test(uint32_t d) | |
+{ | |
+ if (_up_disabled) | |
+ { | |
+ return 2; | |
} | |
return 1; | |
} | |
+ | |
+__attribute__((weak)) int ctap_user_verification(uint8_t arg) | |
+{ | |
+ return 1; | |
+} | |
+ | |
+__attribute__((weak)) uint32_t ctap_atomic_count(uint32_t amount) | |
+{ | |
+ static uint32_t counter1 = 25; | |
+ counter1 += (amount + 1); | |
+ return counter1; | |
+} | |
+ | |
+ | |
+__attribute__((weak)) int ctap_generate_rng(uint8_t * dst, size_t num) | |
+{ | |
+ int i; | |
+ printf1(TAG_ERR, "Insecure RNG being used.\r\n"); | |
+ for (i = 0; i < num; i++){ | |
+ dst[i] = (uint8_t)rand(); | |
+ } | |
+} | |
+ | |
+__attribute__((weak)) int device_is_nfc() | |
+{ | |
+ return 0; | |
+} | |
+ | |
+__attribute__((weak)) void device_wink() | |
+{ | |
+ printf1(TAG_GREEN,"*WINK*\n"); | |
+} | |
+ | |
+__attribute__((weak)) void device_set_clock_rate(DEVICE_CLOCK_RATE param){/**/} | |
+ | |
+static AuthenticatorState _tmp_state = {0}; | |
+__attribute__((weak)) int authenticator_read_state(AuthenticatorState * s){ | |
+ if (_tmp_state.is_initialized != INITIALIZED_MARKER){ | |
+ return 0; | |
+ } | |
+ else { | |
+ memmove(s, &_tmp_state, sizeof(AuthenticatorState)); | |
+ return 1; | |
+ } | |
+} | |
+ | |
+__attribute__((weak)) void authenticator_write_state(AuthenticatorState * s){ | |
+ memmove(&_tmp_state, s, sizeof(AuthenticatorState)); | |
+} | |
+ | |
+__attribute__((weak)) void ctap_reset_rk() | |
+{ | |
+ memset(&RK_STORE,0xff,sizeof(RK_STORE)); | |
+} | |
+ | |
+__attribute__((weak)) uint32_t ctap_rk_size() | |
+{ | |
+ return RK_NUM; | |
+} | |
+ | |
+ | |
+__attribute__((weak)) void ctap_store_rk(int index, CTAP_residentKey * rk) | |
+{ | |
+ if (index < RK_NUM) | |
+ { | |
+ memmove(RK_STORE.rks + index, rk, sizeof(CTAP_residentKey)); | |
+ } | |
+ else | |
+ { | |
+ printf1(TAG_ERR,"Out of bounds for store_rk\r\n"); | |
+ } | |
+ | |
+} | |
+ | |
+__attribute__((weak)) void ctap_delete_rk(int index) | |
+{ | |
+ CTAP_residentKey rk; | |
+ memset(&rk, 0xff, sizeof(CTAP_residentKey)); | |
+ | |
+ if (index < RK_NUM) | |
+ { | |
+ memmove(RK_STORE.rks + index, &rk, sizeof(CTAP_residentKey)); | |
+ } | |
+ else | |
+ { | |
+ printf1(TAG_ERR,"Out of bounds for delete_rk\r\n"); | |
+ } | |
+ | |
+} | |
+ | |
+__attribute__((weak)) void ctap_load_rk(int index, CTAP_residentKey * rk) | |
+{ | |
+ memmove(rk, RK_STORE.rks + index, sizeof(CTAP_residentKey)); | |
+} | |
+ | |
+__attribute__((weak)) void ctap_overwrite_rk(int index, CTAP_residentKey * rk) | |
+{ | |
+ if (index < RK_NUM) | |
+ { | |
+ memmove(RK_STORE.rks + index, rk, sizeof(CTAP_residentKey)); | |
+ } | |
+ else | |
+ { | |
+ printf1(TAG_ERR,"Out of bounds for store_rk\r\n"); | |
+ } | |
+} | |
+ | |
+__attribute__((weak)) void device_read_aaguid(uint8_t * dst){ | |
+ uint8_t * aaguid = (uint8_t *)"\x00\x76\x63\x1b\xd4\xa0\x42\x7f\x57\x73\x0e\xc7\x1c\x9e\x02\x79"; | |
+ memmove(dst, aaguid, 16); | |
+} | |
+ | |
diff --git src/fido2/device.h src/fido2/device.h | |
index 3197abd..0c96c73 100644 | |
--- src/fido2/device.h | |
+++ src/fido2/device.h | |
@@ -12,7 +12,7 @@ | |
/** Return a millisecond timestamp. Does not need to be synchronized to anything. | |
* *Optional* to compile, but will not calculate delays correctly without a correct implementation. | |
*/ | |
-uint32_t millis(void); | |
+uint32_t millis(); | |
/** Called by HIDUSB layer to write bytes to the USB HID interface endpoint. | |
@@ -28,7 +28,7 @@ void usbhid_send(uint8_t * msg); | |
/** Reboot / power reset the device. | |
* **Optional** this is not used for FIDO2, and simply won't do anything if not implemented. | |
*/ | |
-void device_reboot(void); | |
+void device_reboot(); | |
/** Read AuthenticatorState from nonvolatile memory. | |
* @param s pointer to AuthenticatorState buffer to be overwritten with contents from NV memory. | |
@@ -68,7 +68,7 @@ void device_set_status(uint32_t status); | |
* | |
* *Optional* to compile and run, but just returns one by default. | |
*/ | |
-int device_is_button_pressed(void); | |
+int device_is_button_pressed(); | |
// | |
// Return 2 for disabled, 1 for user is present, 0 user not present, -1 if cancel is requested. | |
@@ -85,7 +85,7 @@ int device_is_button_pressed(void); | |
* | |
* *Optional*, the default implementation will return 1, unless a FIDO2 operation calls for no UP, where this will then return 2. | |
*/ | |
-int ctap_user_presence_test(const char* title, const char* prompt, uint32_t delay); | |
+int ctap_user_presence_test(uint32_t delay); | |
/** Disable the next user presence test. This is called by FIDO2 layer when a transaction | |
* requests UP to be disabled. The next call to ctap_user_presence_test should return 2, | |
@@ -121,14 +121,14 @@ uint32_t ctap_atomic_count(uint32_t amount); | |
* | |
* *Optional*, if not implemented, operates on non-persistant RK's. | |
*/ | |
-void ctap_reset_rk(void); | |
+void ctap_reset_rk(); | |
/** Return the maximum amount of resident keys that can be stored. | |
* @return max number of resident keys that can be stored, including already stored RK's. | |
* | |
* *Optional*, if not implemented, returns 50. | |
*/ | |
-uint32_t ctap_rk_size(void); | |
+uint32_t ctap_rk_size(); | |
/** Store a resident key into an index between [ 0, ctap_rk_size() ). | |
* Storage should be in non-volatile memory. | |
@@ -138,7 +138,14 @@ uint32_t ctap_rk_size(void); | |
* | |
* *Optional*, if not implemented, operates on non-persistant RK's. | |
*/ | |
-void ctap_store_rk(int index, ctap_resident_key_t* rk); | |
+void ctap_store_rk(int index,CTAP_residentKey * rk); | |
+ | |
+/** Delete a resident key from an index. | |
+ * @param index to delete resident key from. Has no effect if no RK exists at index. | |
+ * | |
+ * *Optional*, if not implemented, operates on non-persistant RK's. | |
+*/ | |
+void ctap_delete_rk(int index); | |
/** Read a resident key from an index into memory | |
* @param index to read resident key from. | |
@@ -146,7 +153,7 @@ void ctap_store_rk(int index, ctap_resident_key_t* rk); | |
* | |
* *Optional*, if not implemented, operates on non-persistant RK's. | |
*/ | |
-void ctap_load_rk(int index, ctap_resident_key_t* rk); | |
+void ctap_load_rk(int index,CTAP_residentKey * rk); | |
/** Overwrite the RK located in index with a new RK. | |
* @param index to write resident key to. | |
@@ -154,7 +161,7 @@ void ctap_load_rk(int index, ctap_resident_key_t* rk); | |
* | |
* *Optional*, if not implemented, operates on non-persistant RK's. | |
*/ | |
-void ctap_overwrite_rk(int index, ctap_resident_key_t* rk); | |
+void ctap_overwrite_rk(int index,CTAP_residentKey * rk); | |
/** Called by HID layer to indicate that a wink behavior should be performed. | |
@@ -162,7 +169,7 @@ void ctap_overwrite_rk(int index, ctap_resident_key_t* rk); | |
* | |
* *Optional*. | |
*/ | |
-void device_wink(void); | |
+void device_wink(); | |
typedef enum { | |
DEVICE_LOW_POWER_IDLE = 0, | |
@@ -191,13 +198,13 @@ void device_set_clock_rate(DEVICE_CLOCK_RATE param); | |
* 1 - NFC is active, and is powering the chip for a transaction. | |
* 2 - NFC is available, but not currently being used. | |
*/ | |
-int device_is_nfc(void); | |
+int device_is_nfc(); | |
/** Return pointer to attestation key. | |
* @return pointer to attestation private key, raw encoded. For P256, this is 32 bytes. | |
*/ | |
-uint8_t * device_get_attestation_key(void); | |
+uint8_t * device_get_attestation_key(); | |
/** Read the device's attestation certificate into buffer @dst. | |
* @param dst the destination to write the certificate. | |
@@ -209,16 +216,11 @@ void device_attestation_read_cert_der(uint8_t * dst); | |
/** Returns the size in bytes of attestation_cert_der. | |
* @return number of bytes in attestation_cert_der, not including any C string null byte. | |
*/ | |
-uint16_t device_attestation_cert_der_get_size(void); | |
+uint16_t device_attestation_cert_der_get_size(); | |
/** Read the device's 16 byte AAGUID into a buffer. | |
* @param dst buffer to write 16 byte AAGUID into. | |
* */ | |
void device_read_aaguid(uint8_t * dst); | |
-/** | |
- * @return whether the device has been configured | |
- * properly and can verify the user. | |
- */ | |
-bool device_is_uv_initialized(void); | |
#endif | |
diff --git src/fido2/extensions.c src/fido2/extensions.c | |
index 894931e..ceaed72 100644 | |
--- src/fido2/extensions.c | |
+++ src/fido2/extensions.c | |
@@ -6,14 +6,10 @@ | |
// copied, modified, or distributed except according to those terms. | |
#include <stdint.h> | |
-#include <string.h> | |
#include "extensions.h" | |
-#if 0 | |
#include "u2f.h" | |
#include "ctap.h" | |
-#endif | |
#include "wallet.h" | |
-#if 0 | |
#include "solo.h" | |
#include "device.h" | |
@@ -21,7 +17,6 @@ | |
#define htonl(x) (((x & 0xff) << 24) | ((x & 0xff00) << 8) \ | |
| ((x & 0xff0000) >> 8) | ((x & 0xff000000) >> 24) ) | |
-#endif | |
int is_extension_request(uint8_t * kh, int len) | |
{ | |
@@ -33,7 +28,6 @@ int is_extension_request(uint8_t * kh, int len) | |
return memcmp(req->tag, WALLET_TAG, sizeof(WALLET_TAG)-1) == 0; | |
} | |
-#if 0 | |
int extension_needs_atomic_count(uint8_t klen, uint8_t * keyh) | |
{ | |
@@ -172,4 +166,3 @@ int16_t extend_u2f(APDU_HEADER * req, uint8_t * payload, uint32_t len) | |
end: | |
return rcode; | |
} | |
-#endif | |
diff --git src/fido2/extensions.h src/fido2/extensions.h | |
index 66898c4..9bdb203 100644 | |
--- src/fido2/extensions.h | |
+++ src/fido2/extensions.h | |
@@ -7,16 +7,13 @@ | |
#ifndef EXTENSIONS_H_ | |
#define EXTENSIONS_H_ | |
#include "u2f.h" | |
-//#include "apdu.h" | |
+#include "apdu.h" | |
-#if 0 | |
int16_t bridge_u2f_to_extensions(uint8_t * chal, uint8_t * appid, uint8_t klen, uint8_t * keyh); | |
-#endif | |
// return 1 if request is a wallet request | |
int is_extension_request(uint8_t * req, int len); | |
-#if 0 | |
int16_t extend_u2f(APDU_HEADER * req, uint8_t * payload, uint32_t len); | |
int16_t extend_fido2(CredentialId * credid, uint8_t * output); | |
@@ -28,6 +25,5 @@ int is_extension_request(uint8_t * kh, int len); | |
void extension_writeback_init(uint8_t * buffer, uint8_t size); | |
void extension_writeback(uint8_t * buf, uint8_t size); | |
-#endif | |
#endif /* EXTENSIONS_H_ */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment