-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
9 changed files
with
701 additions
and
2 deletions.
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
#include <string.h> | ||
#include <stdlib.h> | ||
#include "ctap.h" | ||
#include "utils.h" | ||
#include "ctap_pin.h" | ||
|
||
int ctap_generate_rng(uint8_t *buffer, size_t length) { | ||
for (size_t i = 0; i < length; i++) { | ||
buffer[i] = (uint8_t) rand(); | ||
} | ||
return 1; | ||
} | ||
|
||
static void ctap_state_init(ctap_persistent_state_t *state) { | ||
|
||
debug_log("ctap_state_init" nl); | ||
|
||
// set to 0xff instead of 0x00 to be easier on flash | ||
memset(state, 0xff, sizeof(ctap_persistent_state_t)); | ||
// fresh RNG for key | ||
ctap_generate_rng(state->master_keys, KEY_SPACE_BYTES); | ||
debug_log("generated master_keys: "); | ||
dump_hex(state->master_keys, KEY_SPACE_BYTES); | ||
|
||
state->is_initialized = INITIALIZED_MARKER; | ||
state->pin_total_remaining_attempts = PIN_TOTAL_ATTEMPTS; | ||
state->is_pin_set = 0; | ||
state->num_rk_stored = 0; | ||
|
||
// ctap_reset_rk(); | ||
|
||
if (ctap_generate_rng(state->PIN_SALT, sizeof(state->PIN_SALT)) != 1) { | ||
error_log(red("ctap_generate_rng failed") nl); | ||
// exit(1); // TODO | ||
} | ||
|
||
debug_log("generated PIN SALT: "); | ||
dump_hex(state->PIN_SALT, sizeof(state->PIN_SALT)); | ||
|
||
} | ||
|
||
void authenticator_write_state(ctap_state_t *state) { | ||
|
||
} | ||
|
||
bool ctap_is_pin_set(ctap_state_t *state) { | ||
return state->persistent.is_pin_set == 1; | ||
} | ||
|
||
bool ctap_device_locked(ctap_state_t *state) { | ||
return state->persistent.pin_total_remaining_attempts <= 0; | ||
} | ||
|
||
bool ctap_device_boot_locked(ctap_state_t *state) { | ||
return state->pin_boot_remaining_attempts <= 0; | ||
} | ||
|
||
int8_t ctap_leftover_pin_attempts(ctap_state_t *state) { | ||
return state->persistent.pin_total_remaining_attempts; | ||
} | ||
|
||
static void ctap_reset_key_agreement(ctap_state_t *state) { | ||
static_assert(sizeof(state->KEY_AGREEMENT_PRIV) == 32, "unexpected sizeof(state->KEY_AGREEMENT_PRIV)"); | ||
ctap_generate_rng(state->KEY_AGREEMENT_PRIV, sizeof(state->KEY_AGREEMENT_PRIV)); | ||
} | ||
|
||
void ctap_init(ctap_state_t *state) { | ||
|
||
printf("ctap_init" nl); | ||
|
||
// crypto_ecc256_init(); | ||
|
||
// int is_init = authenticator_read_state(&state); | ||
|
||
// device_set_status(CTAPHID_STATUS_IDLE); | ||
|
||
// if (false) { | ||
// printf( | ||
// "auth state is initialized" nl | ||
// " is_pin_set = %" wPRIu8 nl | ||
// " remaining_tries = %" wPRId8 nl | ||
// " num_rk_stored = %" PRIu16 nl | ||
// " is_initialized = 0x%08" PRIx32 nl | ||
// " is_invalid = 0x%08" PRIx32 nl, | ||
// state.is_pin_set, | ||
// state.remaining_tries, | ||
// state.num_rk_stored, | ||
// state.is_initialized, | ||
// state.is_invalid | ||
// ); | ||
// debug_log(" master_keys = "); | ||
// dump_hex(state->persistent.master_keys, KEY_SPACE_BYTES); | ||
// debug_log(" PIN_SALT = "); | ||
// dump_hex(state->persistent.PIN_SALT, sizeof(state->persistent.PIN_SALT)); | ||
// } else { | ||
ctap_state_init(&state->persistent); | ||
authenticator_write_state(state); | ||
// } | ||
|
||
// do_migration_if_required(&state); | ||
|
||
// crypto_load_master_secret(state->persistent->master_keys); | ||
|
||
if (ctap_is_pin_set(state)) { | ||
info_log("pin remaining_tries=%" wPRId8 nl, state->persistent.pin_total_remaining_attempts); | ||
} else { | ||
info_log("pin not set" nl); | ||
} | ||
|
||
if (ctap_device_locked(state)) { | ||
error_log(red("DEVICE LOCKED!") nl); | ||
} | ||
|
||
if (ctap_generate_rng(state->PIN_TOKEN, PIN_TOKEN_SIZE) != 1) { | ||
error_log(red("ctap_generate_rng failed") nl); | ||
// exit(1); // TODO | ||
} | ||
|
||
ctap_reset_key_agreement(state); | ||
|
||
} | ||
|
||
uint8_t ctap_request( | ||
ctap_state_t *state, | ||
uint16_t request_data_length, | ||
const uint8_t *request_data, | ||
uint8_t *response_status_code, | ||
uint16_t *response_data_length, | ||
uint8_t **response_data | ||
) { | ||
|
||
CborEncoder *encoder = &state->response.encoder; | ||
|
||
uint8_t status = 0; | ||
uint8_t cmd = *request_data; | ||
request_data++; | ||
request_data_length--; | ||
|
||
cbor_encoder_init(encoder, state->response.data, sizeof(state->response.data), 0); | ||
|
||
debug_log("cbor input structure: %d bytes" nl, request_data_length); | ||
debug_log("cbor req: "); | ||
dump_hex(request_data, request_data_length); | ||
|
||
switch (cmd) { | ||
case CTAP_CMD_CLIENT_PIN: | ||
info_log(magenta("CTAP_CLIENT_PIN") nl); | ||
status = ctap_client_pin(state, request_data, request_data_length); | ||
break; | ||
default: | ||
status = CTAP1_ERR_INVALID_COMMAND; | ||
error_log(red("error: invalid cmd: 0x%02" wPRIx8) nl, cmd); | ||
} | ||
|
||
if (status == CTAP2_OK) { | ||
state->response.length = cbor_encoder_get_buffer_size(encoder, state->response.data); | ||
assert(state->response.length > 0); | ||
}else { | ||
state->response.length = 0; | ||
} | ||
|
||
debug_log( | ||
"cbor output structure length %zu bytes, status code 0x%02" wPRIx8 nl, | ||
state->response.length, | ||
status | ||
); | ||
|
||
*response_status_code = status; | ||
*response_data = state->response.data; | ||
*response_data_length = state->response.length; | ||
|
||
// TODO: return value is no longer used (status code is returned via the response_status_code reference) | ||
return status; | ||
|
||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
#ifndef POKUSEW_CTAP_H | ||
#define POKUSEW_CTAP_H | ||
|
||
#include "ctap_parse.h" | ||
#include <cbor.h> | ||
|
||
#define CTAP_CMD_MAKE_CREDENTIAL 0x01 | ||
#define CTAP_CMD_GET_ASSERTION 0x02 | ||
#define CTAP_CMD_GET_NEXT_ASSERTION 0x08 | ||
#define CTAP_CMD_GET_INFO 0x04 | ||
#define CTAP_CMD_CLIENT_PIN 0x06 | ||
#define CTAP_CMD_RESET 0x07 | ||
#define CTAP_CMD_BIO_ENROLLMENT 0x09 | ||
#define CTAP_CMD_CREDENTIAL_MANAGEMENT 0x0A | ||
#define CTAP_CMD_SELECTION 0x0B | ||
#define CTAP_CMD_LARGE_BLOBS 0x0C | ||
#define CTAP_CMD_CONFIG 0x0D | ||
// https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-errata-20220621.html#commands | ||
#define CTAP_VENDOR_FIRST 0x40 | ||
#define CTAP_VENDOR_LAST 0xBF | ||
|
||
|
||
#define KEY_SPACE_BYTES 128 | ||
#define PIN_SALT_LEN (32) | ||
|
||
#define EMPTY_MARKER 0xFFFFFFFF | ||
#define INITIALIZED_MARKER 0xA5A5A5A5 | ||
#define INVALID_MARKER 0xDDDDDDDD | ||
|
||
#define PIN_TOTAL_ATTEMPTS 8 | ||
#define PIN_PER_BOOT_ATTEMPTS 3 | ||
static_assert( | ||
PIN_PER_BOOT_ATTEMPTS < PIN_TOTAL_ATTEMPTS, | ||
"PIN_TOTAL_ATTEMPTS must be greater than or equal to PIN_PER_BOOT_ATTEMPTS" | ||
); | ||
|
||
typedef struct ctap_persistent_state { | ||
|
||
// PIN information | ||
uint8_t is_pin_set; | ||
uint8_t PIN_CODE_HASH[32]; | ||
uint8_t PIN_SALT[PIN_SALT_LEN]; | ||
int8_t pin_total_remaining_attempts; | ||
|
||
// number of stored client-side discoverable credentials | ||
// aka resident credentials aka resident keys (RK) | ||
uint16_t num_rk_stored; | ||
|
||
// master keys data | ||
uint8_t master_keys[KEY_SPACE_BYTES]; | ||
|
||
uint8_t _alignment1; | ||
|
||
// this field must be WORD (32 bytes) aligned | ||
uint32_t is_invalid; | ||
// this field must be WORD (32 bytes) aligned | ||
// note: in order for the data loss prevention logic to work, is_initialized must be the last field | ||
uint32_t is_initialized; | ||
|
||
} ctap_persistent_state_t; | ||
|
||
#define CTAP_RESPONSE_BUFFER_SIZE 4096 | ||
|
||
typedef struct ctap_response { | ||
CborEncoder encoder; | ||
uint8_t data[CTAP_RESPONSE_BUFFER_SIZE]; | ||
size_t length; | ||
} ctap_response_t; | ||
|
||
#define PIN_TOKEN_SIZE 16 | ||
|
||
typedef struct ctap_state { | ||
|
||
ctap_persistent_state_t persistent; | ||
|
||
ctap_response_t response; | ||
|
||
uint8_t PIN_TOKEN[PIN_TOKEN_SIZE]; | ||
uint8_t KEY_AGREEMENT_PUB[64]; | ||
uint8_t KEY_AGREEMENT_PRIV[32]; | ||
int8_t pin_boot_remaining_attempts; | ||
|
||
} ctap_state_t; | ||
|
||
uint8_t ctap_request( | ||
ctap_state_t *state, | ||
uint16_t request_data_length, | ||
const uint8_t *request_data, | ||
uint8_t *response_status_code, | ||
uint16_t *response_data_length, | ||
uint8_t **response_data | ||
); | ||
|
||
void ctap_init(ctap_state_t *state); | ||
|
||
#endif // POKUSEW_CTAP_H |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
#include "ctap_pin.h" | ||
|
||
// 6.5.4. PIN/UV Auth Protocol Abstract Definition | ||
|
||
// A specific PIN/UV auth protocol defines an implementation of two interfaces to cryptographic services: | ||
// one for the authenticator, and one for the platform. | ||
// | ||
// The authenticator interface is: | ||
|
||
/// This process is run by the authenticator at power-on. | ||
static void initialize(); | ||
|
||
/// Generates a fresh public key. | ||
static void regenerate(); | ||
|
||
/// Generates a fresh pinUvAuthToken. | ||
static void resetPinUvAuthToken(); | ||
|
||
/// Returns the authenticator’s public key as a COSE_Key structure. | ||
static void getPublicKey(COSE_Key *cose_key); | ||
|
||
/// Processes the output of encapsulate from the peer and produces a shared secret, | ||
/// known to both platform and authenticator. | ||
static uint8_t decapsulate(const uint8_t *peer_cose_key, uint8_t *shared_secret); | ||
|
||
/// Decrypts a ciphertext, using sharedSecret as a key, and returns the plaintext. | ||
static uint8_t decrypt(const uint8_t *shared_secret, const uint8_t *ciphertext, const uint8_t *plaintext); | ||
|
||
/// Verifies that the signature is a valid MAC for the given message. | ||
/// If the key parameter value is the current pinUvAuthToken, | ||
/// it also checks whether the pinUvAuthToken is in use or not. | ||
static uint8_t verify(const uint8_t *key, const uint8_t *message, const uint8_t *signature); | ||
|
||
|
||
// The platform interface is: | ||
// | ||
// initialize() | ||
// This is run by the platform when starting a series of transactions with a specific authenticator. | ||
// | ||
// encapsulate(peerCoseKey) → (coseKey, sharedSecret) | error | ||
// Generates an encapsulation for the authenticator’s public key and returns the message to transmit and the shared secret. | ||
// | ||
// encrypt(key, demPlaintext) → ciphertext | ||
// Encrypts a plaintext to produce a ciphertext, which may be longer than the plaintext. The plaintext is restricted to being a multiple of the AES block size (16 bytes) in length. | ||
// | ||
// decrypt(key, ciphertext) → plaintext | error | ||
// Decrypts a ciphertext and returns the plaintext. | ||
// | ||
// authenticate(key, message) → signature | ||
// Computes a MAC of the given message. | ||
|
||
uint8_t ctap_client_pin(ctap_state_t *state, const uint8_t *request, size_t length) { | ||
|
||
CborError err; | ||
|
||
CTAP_clientPIN cp; | ||
uint8_t status = ctap_parse_client_pin(request, length, &cp); | ||
if (status != CTAP2_OK) { | ||
return status; | ||
} | ||
|
||
if (cp.pinUvAuthProtocol != 1 && cp.pinUvAuthProtocol != 2) { | ||
return CTAP1_ERR_INVALID_PARAMETER; | ||
} | ||
|
||
CborEncoder *encoder = &state->response.encoder; | ||
CborEncoder map; | ||
|
||
switch (cp.subCommand) { | ||
case CTAP_clientPIN_subCmd_getPINRetries: | ||
// start response map | ||
cbor_encoding_check(cbor_encoder_create_map(encoder, &map, 2)); | ||
// 1. pinRetries | ||
cbor_encoding_check(cbor_encode_int(&map, CTAP_clientPIN_res_pinRetries)); | ||
cbor_encoding_check(cbor_encode_int(&map, state->persistent.pin_total_remaining_attempts)); | ||
// 2. powerCycleState | ||
cbor_encoding_check(cbor_encode_int(&map, CTAP_clientPIN_res_powerCycleState)); | ||
cbor_encoding_check(cbor_encode_boolean(&map, state->pin_boot_remaining_attempts > 0)); | ||
// close response map | ||
cbor_encoding_check(cbor_encoder_close_container(encoder, &map)); | ||
break; | ||
case CTAP_clientPIN_subCmd_getKeyAgreement: | ||
|
||
break; | ||
default: | ||
return CTAP2_ERR_INVALID_SUBCOMMAND; | ||
} | ||
|
||
return CTAP2_OK; | ||
|
||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
#ifndef POKUSEW_CTAP_PIN_H | ||
#define POKUSEW_CTAP_PIN_H | ||
|
||
#include "ctap.h" | ||
#include "ctap_parse.h" | ||
|
||
typedef struct CTAP_pinUvAuthToken { | ||
int rpId; | ||
bool rpIdSet; | ||
int permissions; | ||
// usage timer | ||
bool in_use; | ||
// initial usage time limit | ||
// user present time limit | ||
// max usage time period | ||
bool user_verified; | ||
bool user_present; | ||
} CTAP_pinUvAuthToken; | ||
|
||
typedef struct CTAP_pinState { | ||
bool is_pin_set; | ||
// uint8_t PIN_CODE_HASH[32]; | ||
// uint8_t PIN_SALT[PIN_SALT_LEN]; | ||
uint8_t remaining_tries; | ||
} CTAP_pinState; | ||
|
||
// PIN information | ||
|
||
uint8_t ctap_client_pin(ctap_state_t *state, const uint8_t *request, size_t length); | ||
|
||
|
||
#endif // POKUSEW_CTAP_PIN_H |
Oops, something went wrong.