Skip to content

Commit

Permalink
Implement ctap_client_pin (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
pokusew committed Jan 29, 2025
1 parent ed901c3 commit 2926e0e
Show file tree
Hide file tree
Showing 9 changed files with 701 additions and 2 deletions.
175 changes: 175 additions & 0 deletions core/ctap.c
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;

}
96 changes: 96 additions & 0 deletions core/ctap.h
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
91 changes: 91 additions & 0 deletions core/ctap_pin.c
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;

}
32 changes: 32 additions & 0 deletions core/ctap_pin.h
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
Loading

0 comments on commit 2926e0e

Please sign in to comment.