From 51f8ef2e7606e3a33a32515d6bed51bd6e50aac4 Mon Sep 17 00:00:00 2001 From: Conor Patrick Date: Sun, 6 May 2018 14:54:42 -0400 Subject: [PATCH] parsed make_credential --- Makefile | 4 +- ctap.c | 519 ++++++++++++++++++++++++++++++++++++++++++++++++++ ctap.h | 109 +++++++++++ ctap_device.c | 27 +++ 4 files changed, 657 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 2d16c00..5352685 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ -src = $(wildcard *.c) +src = $(wildcard *.c) $(wildcard crypto/*.c) obj = $(src:.c=.o) LDFLAGS = -Wl,--gc-sections ./tinycbor/lib/libtinycbor.a -CFLAGS = -O2 -fdata-sections -ffunction-sections -I./tinycbor/src +CFLAGS = -O2 -fdata-sections -ffunction-sections -I./tinycbor/src -I./crypto name = main diff --git a/ctap.c b/ctap.c index 173d3f9..129d368 100644 --- a/ctap.c +++ b/ctap.c @@ -5,6 +5,7 @@ #include "cbor.h" #include "ctap.h" +#include "crypto.h" #include "util.h" @@ -18,6 +19,53 @@ static void check_ret(CborError ret) } } +static 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; + } + return "Invalid type"; +} + void ctap_get_info(CborEncoder * encoder) { int ret; @@ -54,10 +102,476 @@ void ctap_get_info(CborEncoder * encoder) } ret = cbor_encoder_close_container(encoder, &map); check_ret(ret); +} + +static int parse_client_data_hash(CTAP_makeCredential * MC, CborValue * val) +{ + size_t sz; + int ret; + if (cbor_value_get_type(val) != CborByteStringType) + { + printf("error, wrong type\n"); + return -1; + } + ret = cbor_value_calculate_string_length(val, &sz); + check_ret(ret); + if (sz != CLIENT_DATA_HASH_SIZE) + { + printf("error, wrong size for client data hash\n"); + return -1; + } + ret = cbor_value_copy_byte_string(val, MC->clientDataHash, &sz, NULL); + check_ret(ret); + + MC->paramsParsed |= PARAM_clientDataHash; + + return 0; +} + + +static int parse_user(CTAP_makeCredential * MC, CborValue * val) +{ + size_t sz, map_length; + uint8_t key[8]; + int ret; + int i; + CborValue map; + + + if (cbor_value_get_type(val) != CborMapType) + { + printf("error, wrong type\n"); + return -1; + } + + ret = cbor_value_enter_container(val,&map); + check_ret(ret); + + 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) != CborTextStringType) + { + printf("Error, expecting text string type for user map key, got %s\n", cbor_value_get_type_string(&map)); + return -1; + } + + sz = sizeof(key); + ret = cbor_value_copy_text_string(&map, key, &sz, NULL); + + if (ret == CborErrorOutOfMemory) + { + printf("Error, rp map key is too large\n"); + return -1; + } + check_ret(ret); + key[sizeof(key) - 1] = 0; + + ret = cbor_value_advance(&map); + check_ret(ret); + + if (strcmp(key, "id") == 0) + { + + if (cbor_value_get_type(&map) != CborByteStringType) + { + printf("Error, expecting byte string type for rp map value\n"); + return -1; + } + + sz = USER_ID_MAX_SIZE; + ret = cbor_value_copy_byte_string(&map, MC->userId, &sz, NULL); + if (ret == CborErrorOutOfMemory) + { + printf("Error, USER_ID is too large\n"); + return -1; + } + MC->userIdSize = sz; + check_ret(ret); + } + else if (strcmp(key, "name") == 0) + { + sz = USER_NAME_LIMIT; + ret = cbor_value_copy_text_string(&map, MC->userName, &sz, NULL); + if (ret != CborErrorOutOfMemory) + { // Just truncate the name it's okay + check_ret(ret); + } + MC->userName[USER_NAME_LIMIT - 1] = 0; + } + else + { + printf("ignoring key %s for user map\n", key); + } + + ret = cbor_value_advance(&map); + check_ret(ret); + + } + + MC->paramsParsed |= PARAM_user; + + return 0; +} + +static int parse_pub_key_cred_param(CborValue * val, uint8_t * cred_type, int32_t * alg_type) +{ + CborValue map; + CborValue cred; + CborValue alg; + int ret; + uint8_t type_str[16]; + size_t sz = sizeof(type_str); + + if (cbor_value_get_type(val) != CborMapType) + { + printf("error, expecting map type\n"); + return -1; + } + + ret = cbor_value_map_find_value(val, "type", &cred); + check_ret(ret); + ret = cbor_value_map_find_value(val, "alg", &alg); + check_ret(ret); + + if (cbor_value_get_type(&cred) != CborTextStringType) + { + printf("Error, parse_pub_key could not find credential param\n"); + return -1; + } + if (cbor_value_get_type(&alg) != CborIntegerType) + { + printf("Error, parse_pub_key could not find alg param\n"); + return -1; + } + + ret = cbor_value_copy_text_string(&cred, type_str, &sz, NULL); + check_ret(ret); + + type_str[sizeof(type_str) - 1] = 0; + + if (strcmp(type_str, "public-key") == 0) + { + *cred_type = PUB_KEY_CRED_PUB_KEY; + } + else + { + *cred_type = PUB_KEY_CRED_UNKNOWN; + } + + ret = cbor_value_get_int_checked(&alg, alg_type); + check_ret(ret); + + return 0; +} + +// Check if public key credential+algorithm type is supported +static int pub_key_cred_param_supported(uint8_t cred, int32_t alg) +{ + if (cred == PUB_KEY_CRED_PUB_KEY) + { + if (alg == COSE_ALG_ES256) + { + return CREDENTIAL_IS_SUPPORTED; + } + } + + return CREDENTIAL_NOT_SUPPORTED; +} + +static int parse_pub_key_cred_params(CTAP_makeCredential * MC, CborValue * val) +{ + size_t sz, arr_length; + uint8_t cred_type; + int32_t alg_type; + uint8_t key[8]; + int ret; + int i; + CborValue arr; + + + if (cbor_value_get_type(val) != CborArrayType) + { + printf("error, expecting array type\n"); + return -1; + } + + ret = cbor_value_enter_container(val,&arr); + check_ret(ret); + + ret = cbor_value_get_array_length(val, &arr_length); + check_ret(ret); + + for (i = 0; i < arr_length; i++) + { + if (parse_pub_key_cred_param(&arr, &cred_type, &alg_type) == 0) + { + if (pub_key_cred_param_supported(cred_type, alg_type) == CREDENTIAL_IS_SUPPORTED) + { + MC->publicKeyCredentialType = cred_type; + MC->COSEAlgorithmIdentifier = alg_type; + MC->paramsParsed |= PARAM_pubKeyCredParams; + return 0; + } + } + else + { + // Continue? fail? + } + ret = cbor_value_advance(&arr); + check_ret(ret); + } + + printf("Error, no public key credential parameters are supported!\n"); + return -1; +} + + +static int parse_rp(CTAP_makeCredential * MC, CborValue * val) +{ + size_t sz, map_length; + uint8_t key[8]; + int ret; + int i; + CborValue map; + + + if (cbor_value_get_type(val) != CborMapType) + { + printf("error, wrong type\n"); + return -1; + } + + ret = cbor_value_enter_container(val,&map); + check_ret(ret); + + 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) != CborTextStringType) + { + printf("Error, expecting text string type for rp map key, got %s\n", cbor_value_get_type_string(&map)); + return -1; + } + + sz = sizeof(key); + ret = cbor_value_copy_text_string(&map, key, &sz, NULL); + + if (ret == CborErrorOutOfMemory) + { + printf("Error, rp map key is too large\n"); + return -1; + } + check_ret(ret); + key[sizeof(key) - 1] = 0; + + ret = cbor_value_advance(&map); + check_ret(ret); + + if (cbor_value_get_type(&map) != CborTextStringType) + { + printf("Error, expecting text string type for rp map value\n"); + return -1; + } + + if (strcmp(key, "id") == 0) + { + sz = DOMAIN_NAME_MAX_SIZE; + ret = cbor_value_copy_text_string(&map, MC->rpId, &sz, NULL); + if (ret == CborErrorOutOfMemory) + { + printf("Error, RP_ID is too large\n"); + return -1; + } + MC->rpId[DOMAIN_NAME_MAX_SIZE] = 0; // Extra byte defined in struct. + MC->rpIdSize = sz; + check_ret(ret); + } + else if (strcmp(key, "name") == 0) + { + sz = RP_NAME_LIMIT; + ret = cbor_value_copy_text_string(&map, MC->rpName, &sz, NULL); + if (ret != CborErrorOutOfMemory) + { // Just truncate the name it's okay + check_ret(ret); + } + MC->rpName[RP_NAME_LIMIT - 1] = 0; + } + else + { + printf("ignoring key %s for RP map\n", key); + } + + ret = cbor_value_advance(&map); + check_ret(ret); + + } + + MC->paramsParsed |= PARAM_rp; + + + return 0; +} + + +static int ctap_parse_make_credential(CTAP_makeCredential * MC, CborEncoder * encoder, uint8_t * request, int length) +{ + int ret; + int i; + int key; + size_t map_length; + size_t sz; + CborParser parser; + CborValue it,map; + + memset(MC, 0, sizeof(CTAP_makeCredential)); + ret = cbor_parser_init(request, length, CborValidateCanonicalFormat, &parser, &it); + check_ret(ret); + + CborType type = cbor_value_get_type(&it); + if (type != CborMapType) + { + printf("Error, expecting cbor map\n"); + return -1; + } + + ret = cbor_value_enter_container(&it,&map); + check_ret(ret); + + ret = cbor_value_get_map_length(&it, &map_length); + check_ret(ret); + + printf("map has %d elements\n",map_length); + + for (i = 0; i < map_length; i++) + { + type = cbor_value_get_type(&map); + if (type != CborIntegerType) + { + printf("Error, expecting int for map key\n"); + } + ret = cbor_value_get_int_checked(&map, &key); + check_ret(ret); + + ret = cbor_value_advance(&map); + check_ret(ret); + + switch(key) + { + + case MC_clientDataHash: + printf("CTAP_clientDataHash\n"); + + ret = parse_client_data_hash(MC, &map); + + printf(" "); dump_hex(MC->clientDataHash, 32); + break; + case MC_rp: + printf("CTAP_rp\n"); + + ret = parse_rp(MC, &map); + + printf(" ID: %s\n", MC->rpId); + printf(" name: %s\n", MC->rpName); + break; + case MC_user: + printf("CTAP_user\n"); + + ret = parse_user(MC, &map); + + printf(" ID: "); dump_hex(MC->userId, MC->userIdSize); + printf(" name: %s\n", MC->userName); + + break; + case MC_pubKeyCredParams: + printf("CTAP_pubKeyCredParams\n"); + + ret = parse_pub_key_cred_params(MC, &map); + + printf(" cred_type: 0x%02x\n", MC->publicKeyCredentialType); + printf(" alg_type: %d\n", MC->COSEAlgorithmIdentifier); + + break; + case MC_excludeList: + printf("CTAP_excludeList\n"); + break; + case MC_extensions: + printf("CTAP_extensions\n"); + break; + case MC_options: + printf("CTAP_options\n"); + break; + case MC_pinAuth: + printf("CTAP_pinAuth\n"); + break; + case MC_pinProtocol: + printf("CTAP_pinProtocol\n"); + break; + default: + printf("invalid key %d\n", key); + + } + if (ret != 0) + { + return ret; + } + + cbor_value_advance(&map); + check_ret(ret); + } + + return 0; +} + + +void ctap_make_credential(CborEncoder * encoder, uint8_t * request, int length) +{ + CTAP_makeCredential MC; + CTAP_authData authData; + + int ret; + ret = ctap_parse_make_credential(&MC,encoder,request,length); + if (ret != 0) + { + printf("error, parse_make_credential failed\n"); + return; + } + if ((MC.paramsParsed & MC_requiredMask) != MC_requiredMask) + { + printf("error, required parameter(s) for makeCredential are missing\n"); + return; + } + + crypto_sha256_init(); + crypto_sha256_update(MC.rpId, MC.rpIdSize); + crypto_sha256_final(authData.rpIdHash); + + authData.flags = (ctap_user_presence_test() << 0); + authData.flags |= (ctap_user_verification(0) << 2); + authData.flags |= (1 << 6);//include attestation data + + authData.signCount = ctap_atomic_count(); + + memmove(authData.attest.aaguid, CTAP_AAGUID, 16); + authData.attest.credLenL = CREDENTIAL_ID_SIZE & 0x00FF; + authData.attest.credLenH = (CREDENTIAL_ID_SIZE & 0xFF00) >> 8; + +#if CREDENTIAL_ID_SIZE != 32 +#error "need to update credential ID layout" +#else + memmove(authData.attest.credentialId, authData.rpIdHash, 16); + ctap_generate_rng(authData.attest.credentialId + 16, 16); +#endif } + + uint8_t ctap_handle_packet(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp) { uint8_t cmd = *pkt_raw; @@ -73,11 +587,16 @@ uint8_t ctap_handle_packet(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp) CborEncoder encoder; cbor_encoder_init(&encoder, buf, sizeof(buf), 0); + printf("cbor req: "); dump_hex(pkt_raw, length - 1); + switch(cmd) { case CTAP_MAKE_CREDENTIAL: printf("CTAP_MAKE_CREDENTIAL\n"); + ctap_make_credential(&encoder, pkt_raw, length - 1); + dump_hex(buf, cbor_encoder_get_buffer_size(&encoder, buf)); + resp->length = cbor_encoder_get_buffer_size(&encoder, buf); break; case CTAP_GET_ASSERTION: printf("CTAP_GET_ASSERTION\n"); diff --git a/ctap.h b/ctap.h index 5c51fe1..97b7deb 100644 --- a/ctap.h +++ b/ctap.h @@ -13,15 +13,124 @@ #define CTAP_AAGUID ((uint8_t*)"\x00\x11\x22\x33\x44\x55\x66\x77\x88\x99\xaa\xbb\xcc\xdd\xee\xff") +#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 CP_pinProtocol 0x01 +#define CP_subCommand 0x02 +#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 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 USER_ID_MAX_SIZE 64 +#define USER_NAME_LIMIT 65 // Must be minimum of 64 bytes but can be more. + +#define CREDENTIAL_ID_SIZE 32 + +#define PUB_KEY_CRED_PUB_KEY 0x01 +#define PUB_KEY_CRED_UNKNOWN 0x3F + +#define CREDENTIAL_IS_SUPPORTED 1 +#define CREDENTIAL_NOT_SUPPORTED 0 + +#define COSE_ALG_ES256 -7 + +typedef struct +{ + uint8_t aaguid[16]; + uint8_t credLenL; + uint8_t credLenH; + uint8_t credentialId[CREDENTIAL_ID_SIZE]; +} __attribute__((packed)) CTAP_attestHeader; + + +typedef struct +{ + uint8_t rpIdHash[32]; + uint8_t flags; + uint32_t signCount; + CTAP_attestHeader attest; +} __attribute__((packed)) CTAP_authData; + + + typedef struct { uint8_t * data; uint16_t length; } CTAP_RESPONSE; +typedef struct +{ + uint32_t paramsParsed; + uint8_t clientDataHash[CLIENT_DATA_HASH_SIZE]; + + uint8_t rpId[DOMAIN_NAME_MAX_SIZE + 1]; // extra for NULL termination + size_t rpIdSize; + uint8_t rpName[RP_NAME_LIMIT]; + + uint8_t userId[USER_ID_MAX_SIZE]; + uint8_t userIdSize; + uint8_t userName[USER_NAME_LIMIT]; + + uint8_t publicKeyCredentialType; + int32_t COSEAlgorithmIdentifier; + + uint8_t pinProtocol; +} CTAP_makeCredential; uint8_t ctap_handle_packet(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp); +// Test for user presence +// Return 1 for user is present, 0 user not present +extern int ctap_user_presence_test(); + +// Generate @num bytes of random numbers to @dest +extern int ctap_generate_rng(uint8_t * dst, size_t num); + +// Increment atomic counter and return it +extern uint32_t ctap_atomic_count(); + +// Verify the user +// return 1 if user is verified, 0 if not +extern int ctap_user_verification(uint8_t arg); + // Must be implemented by application // data is HID_MESSAGE_SIZE long in bytes extern void ctap_write_block(uint8_t * data); diff --git a/ctap_device.c b/ctap_device.c index 4e7bf5e..26c999e 100644 --- a/ctap_device.c +++ b/ctap_device.c @@ -53,4 +53,31 @@ void ctap_write(void * _data, int len) } } +int ctap_user_presence_test() +{ + return 1; +} +int ctap_user_verification(uint8_t arg) +{ + return 1; +} + + +uint32_t ctap_atomic_count() +{ + static uint32_t counter = 25; + return counter++; +} + +int ctap_generate_rng(uint8_t * dst, size_t num) +{ + FILE * urand = fopen("/dev/urandom","r"); + if (urand == NULL) + { + perror("fopen"); + exit(1); + } + fread(dst, 1, num, urand); + fclose(urand); +}