diff --git a/crypto.c b/crypto.c index 3184be2..c887396 100644 --- a/crypto.c +++ b/crypto.c @@ -43,6 +43,10 @@ void crypto_sha256_update(uint8_t * data, size_t len) sha256_update(&sha256_ctx, data, len); } +void crypto_sha256_update_secret() +{ + sha256_update(&sha256_ctx, master_secret, 32); +} void crypto_sha256_final(uint8_t * hash) { @@ -71,24 +75,24 @@ void crypto_ecc256_sign(uint8_t * data, int len, uint8_t * sig) } } -static void generate_private_key(uint8_t * rpId, int len1, uint8_t * entropy, int len2, uint8_t * privkey) +void generate_private_key(uint8_t * data, int len, uint8_t * data2, int len2, uint8_t * privkey) { // poor man's hmac crypto_sha256_init(); - crypto_sha256_update(rpId, len1); - crypto_sha256_update(entropy, len2); + crypto_sha256_update(data, len); + crypto_sha256_update(data2, len2); crypto_sha256_update(master_secret, 32); crypto_sha256_final(privkey); } /*int uECC_compute_public_key(const uint8_t *private_key, uint8_t *public_key, uECC_Curve curve);*/ -void crypto_ecc256_derive_public_key(uint8_t * rpId, int len1, uint8_t * entropy, int len2, uint8_t * x, uint8_t * y) +void crypto_ecc256_derive_public_key(uint8_t * data, int len, uint8_t * x, uint8_t * y) { uint8_t privkey[32]; uint8_t pubkey[64]; - generate_private_key(rpId,len1,entropy,len2,privkey); + generate_private_key(data,len,NULL,0,privkey); memset(pubkey,0,sizeof(pubkey)); uECC_compute_public_key(privkey, pubkey, _es256_curve); @@ -96,10 +100,10 @@ void crypto_ecc256_derive_public_key(uint8_t * rpId, int len1, uint8_t * entropy memmove(y,pubkey+32,32); } -void crypto_ecc256_load_key(uint8_t * rpId, int len1, uint8_t * entropy, int len2) +void crypto_ecc256_load_key(uint8_t * data, int len) { static uint8_t privkey[32]; - generate_private_key(rpId,len1,entropy,len2,privkey); + generate_private_key(data,len,NULL,0,privkey); _signing_key = privkey; } diff --git a/crypto.h b/crypto.h index 84435fc..de3fc12 100644 --- a/crypto.h +++ b/crypto.h @@ -6,16 +6,20 @@ void crypto_sha256_init(); void crypto_sha256_update(uint8_t * data, size_t len); +void crypto_sha256_update_secret(); void crypto_sha256_final(uint8_t * hash); void crypto_ecc256_init(); -void crypto_ecc256_derive_public_key(uint8_t * rpId, int len1, uint8_t * entropy, int len2, uint8_t * x, uint8_t * y); +void crypto_ecc256_derive_public_key(uint8_t * data, int len, uint8_t * x, uint8_t * y); -void crypto_ecc256_load_key(uint8_t * rpId, int len1, uint8_t * entropy, int len2); +void crypto_ecc256_load_key(uint8_t * data, int len); void crypto_ecc256_load_attestation_key(); void crypto_ecc256_sign(uint8_t * data, int len, uint8_t * sig); + +void generate_private_key(uint8_t * data, int len, uint8_t * data2, int len2, uint8_t * privkey); + extern const uint8_t attestation_cert_der[]; extern const uint16_t attestation_cert_der_size; diff --git a/ctap.c b/ctap.c index d780673..dc49a4f 100644 --- a/ctap.c +++ b/ctap.c @@ -11,11 +11,14 @@ #define check_ret(r) _check_ret(r,__LINE__, __FILE__) +static CborEncoder * _ENCODER; static void _check_ret(CborError ret, int line, const char * filename) { if (ret != CborNoError) { printf("CborError: 0x%x: %s: %d: %s\n", ret, filename, line, cbor_error_string(ret)); + if (ret == 0x80000000) + printf(" need %d more bytes\n",cbor_encoder_get_extra_bytes_needed(_ENCODER)); exit(1); } } @@ -105,7 +108,7 @@ void ctap_get_info(CborEncoder * encoder) check_ret(ret); } -static int parse_client_data_hash(CTAP_makeCredential * MC, CborValue * val) +static int parse_client_data_hash(uint8_t * clientDataHash, CborValue * val) { size_t sz; int ret; @@ -121,11 +124,9 @@ static int parse_client_data_hash(CTAP_makeCredential * MC, CborValue * val) printf("error, wrong size for client data hash\n"); return -1; } - ret = cbor_value_copy_byte_string(val, MC->clientDataHash, &sz, NULL); + ret = cbor_value_copy_byte_string(val, clientDataHash, &sz, NULL); check_ret(ret); - MC->paramsParsed |= PARAM_clientDataHash; - return 0; } @@ -183,24 +184,24 @@ static int parse_user(CTAP_makeCredential * MC, CborValue * val) } sz = USER_ID_MAX_SIZE; - ret = cbor_value_copy_byte_string(&map, MC->userId, &sz, NULL); + ret = cbor_value_copy_byte_string(&map, MC->user.id, &sz, NULL); if (ret == CborErrorOutOfMemory) { printf("Error, USER_ID is too large\n"); return -1; } - MC->userIdSize = sz; + MC->user.id_size = 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); + ret = cbor_value_copy_text_string(&map, MC->user.name, &sz, NULL); if (ret != CborErrorOutOfMemory) { // Just truncate the name it's okay check_ret(ret); } - MC->userName[USER_NAME_LIMIT - 1] = 0; + MC->user.name[USER_NAME_LIMIT - 1] = 0; } else { @@ -330,7 +331,22 @@ static int parse_pub_key_cred_params(CTAP_makeCredential * MC, CborValue * val) } -static int parse_rp(CTAP_makeCredential * MC, CborValue * val) +static int parse_rp_id(struct rpId * rp, CborValue * val) +{ + size_t sz = DOMAIN_NAME_MAX_SIZE; + int ret = cbor_value_copy_text_string(val, rp->id, &sz, NULL); + if (ret == CborErrorOutOfMemory) + { + printf("Error, RP_ID is too large\n"); + return -1; + } + rp->id[DOMAIN_NAME_MAX_SIZE] = 0; // Extra byte defined in struct. + rp->size = sz; + check_ret(ret); + return 0; +} + +static int parse_rp(struct rpId * rp, CborValue * val) { size_t sz, map_length; uint8_t key[8]; @@ -351,6 +367,8 @@ static int parse_rp(CTAP_makeCredential * MC, CborValue * val) ret = cbor_value_get_map_length(val, &map_length); check_ret(ret); + rp->size = 0; + for (i = 0; i < map_length; i++) { if (cbor_value_get_type(&map) != CborTextStringType) @@ -381,26 +399,21 @@ static int parse_rp(CTAP_makeCredential * MC, CborValue * val) if (strcmp(key, "id") == 0) { - sz = DOMAIN_NAME_MAX_SIZE; - ret = cbor_value_copy_text_string(&map, MC->rpId, &sz, NULL); - if (ret == CborErrorOutOfMemory) + ret = parse_rp_id(rp, &map); + if (ret != 0) { - printf("Error, RP_ID is too large\n"); - return -1; + return ret; } - 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); + ret = cbor_value_copy_text_string(&map, rp->name, &sz, NULL); if (ret != CborErrorOutOfMemory) { // Just truncate the name it's okay check_ret(ret); } - MC->rpName[RP_NAME_LIMIT - 1] = 0; + rp->name[RP_NAME_LIMIT - 1] = 0; } else { @@ -411,8 +424,11 @@ static int parse_rp(CTAP_makeCredential * MC, CborValue * val) check_ret(ret); } - - MC->paramsParsed |= PARAM_rp; + if (rp->size == 0) + { + printf("Error, no RPID provided\n"); + return -1; + } return 0; @@ -460,6 +476,7 @@ static int ctap_parse_make_credential(CTAP_makeCredential * MC, CborEncoder * en ret = cbor_value_advance(&map); check_ret(ret); + ret = 0; switch(key) { @@ -467,25 +484,34 @@ static int ctap_parse_make_credential(CTAP_makeCredential * MC, CborEncoder * en case MC_clientDataHash: printf("CTAP_clientDataHash\n"); - ret = parse_client_data_hash(MC, &map); + ret = parse_client_data_hash(MC->clientDataHash, &map); + if (ret == 0) + { + MC->paramsParsed |= PARAM_clientDataHash; + } printf(" "); dump_hex(MC->clientDataHash, 32); break; case MC_rp: printf("CTAP_rp\n"); - ret = parse_rp(MC, &map); + ret = parse_rp(&MC->rp, &map); + if (ret == 0) + { + MC->paramsParsed |= PARAM_rp; + } - printf(" ID: %s\n", MC->rpId); - printf(" name: %s\n", MC->rpName); + + printf(" ID: %s\n", MC->rp.id); + printf(" name: %s\n", MC->rp.name); 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); + printf(" ID: "); dump_hex(MC->user.id, MC->user.id_size); + printf(" name: %s\n", MC->user.name); break; case MC_pubKeyCredParams: @@ -528,29 +554,30 @@ static int ctap_parse_make_credential(CTAP_makeCredential * MC, CborEncoder * en return 0; } -static int ctap_generate_cose_key(CTAP_makeCredential * MC, CborEncoder * cose_key, uint8_t * rpId, int l1, uint8_t * entropy, int l2) +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]; int ret; CborEncoder map; ret = cbor_encoder_create_map(cose_key, &map, 5); + int extra = cbor_encoder_get_extra_bytes_needed(&map); + printf(" extra? %d\n", extra); check_ret(ret); - if (MC->publicKeyCredentialType != PUB_KEY_CRED_PUB_KEY) + if (credtype != PUB_KEY_CRED_PUB_KEY) { printf("Error, pubkey credential type not supported\n"); return -1; } - switch(MC->COSEAlgorithmIdentifier) + switch(algtype) { case COSE_ALG_ES256: crypto_ecc256_init(); - crypto_ecc256_derive_public_key(rpId, l1, - entropy, l2, x, y); + crypto_ecc256_derive_public_key(hmac_input, len, x, y); break; default: - printf("Error, COSE alg %d not supported\n", MC->COSEAlgorithmIdentifier); + printf("Error, COSE alg %d not supported\n", algtype); return -1; } @@ -564,7 +591,7 @@ static int ctap_generate_cose_key(CTAP_makeCredential * MC, CborEncoder * cose_k { ret = cbor_encode_int(&map, COSE_KEY_LABEL_ALG); check_ret(ret); - ret = cbor_encode_int(&map, MC->COSEAlgorithmIdentifier); + ret = cbor_encode_int(&map, algtype); check_ret(ret); } @@ -594,60 +621,92 @@ static int ctap_generate_cose_key(CTAP_makeCredential * MC, CborEncoder * cose_k check_ret(ret); } -static int ctap_make_auth_data(CTAP_makeCredential * MC, CborEncoder * map, uint8_t * auth_data_buf, int len) +void make_auth_tag(struct rpId * rp, CTAP_userEntity * user, uint32_t count, uint8_t * tag) +{ + uint8_t hashbuf[32]; + crypto_sha256_init(); + crypto_sha256_update(rp->id, rp->size); + crypto_sha256_update(user->id, user->id_size); + crypto_sha256_update(user->name, strnlen(user->name, USER_NAME_LIMIT)); + crypto_sha256_update((uint8_t*)&count, 4); + crypto_sha256_update_secret(); + crypto_sha256_final(hashbuf); + + memmove(tag, hashbuf, CREDENTIAL_TAG_SIZE); +} + +static int ctap_make_auth_data(struct rpId * rp, CborEncoder * map, uint8_t * auth_data_buf, int len, CTAP_userEntity * user, uint8_t credtype, int32_t algtype) { CborEncoder cose_key; int auth_data_sz, ret; CTAP_authData * authData = (CTAP_authData *)auth_data_buf; uint8_t * cose_key_buf = auth_data_buf + sizeof(CTAP_authData); - cbor_encoder_init(&cose_key, cose_key_buf, len - sizeof(CTAP_authData), 0); + + if((sizeof(CTAP_authData) - sizeof(CTAP_attestHeader)) > len) + { + printf("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(MC->rpId, MC->rpIdSize); + crypto_sha256_update(rp->id, rp->size); 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(); + authData->signCount = ctap_atomic_count( 0 ); - memmove(authData->attest.aaguid, CTAP_AAGUID, 16); - authData->attest.credLenL = CREDENTIAL_ID_SIZE & 0x00FF; - authData->attest.credLenH = (CREDENTIAL_ID_SIZE & 0xFF00) >> 8; + if (credtype != 0) + { + // add attestedCredentialData + authData->flags |= (1 << 6);//include attestation data -#if CREDENTIAL_ID_SIZE != 48 + cbor_encoder_init(&cose_key, cose_key_buf, len - sizeof(CTAP_authData), 0); + _ENCODER = &cose_key; + + 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 != 150 #error "need to update credential ID layout" #else - memmove(authData->attest.credentialId, authData->rpIdHash, 16); - ctap_generate_rng(authData->attest.credentialId + 16, 32); + memset(authData->attest.credential.id, 0, CREDENTIAL_ID_SIZE); - ctap_generate_cose_key(MC, &cose_key, authData->attest.credentialId, 16, - authData->attest.credentialId, 32); + // Make a tag we can later check to make sure this is a token we made + make_auth_tag(rp, user, authData->signCount, authData->attest.credential.fields.tag); - printf("COSE_KEY: "); dump_hex(cose_key_buf, cbor_encoder_get_buffer_size(&cose_key, cose_key_buf)); + memmove(&authData->attest.credential.fields.user, user, sizeof(CTAP_userEntity)); //TODO encrypt this - auth_data_sz = sizeof(CTAP_authData) + cbor_encoder_get_buffer_size(&cose_key, cose_key_buf); + authData->attest.credential.fields.count = authData->signCount; + + ctap_generate_cose_key(&cose_key, authData->attest.credential.id, CREDENTIAL_ID_SIZE, credtype, algtype); + + printf("COSE_KEY: "); dump_hex(cose_key_buf, cbor_encoder_get_buffer_size(&cose_key, cose_key_buf)); + + auth_data_sz = sizeof(CTAP_authData) + cbor_encoder_get_buffer_size(&cose_key, cose_key_buf); #endif + } + else + { + auth_data_sz = sizeof(CTAP_authData) - sizeof(CTAP_attestHeader); + } + { ret = cbor_encode_int(map,RESP_authData); check_ret(ret); ret = cbor_encode_byte_string(map, auth_data_buf, auth_data_sz); - check_ret(ret); } - { - ret = cbor_encode_int(map,RESP_fmt); - check_ret(ret); - ret = cbor_encode_text_stringz(map, "packed"); - check_ret(ret); - } + return auth_data_sz; } +// 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) @@ -662,7 +721,6 @@ int ctap_calculate_signature(uint8_t * data, int datalen, uint8_t * clientDataHa crypto_sha256_update(clientDataHash, CLIENT_DATA_HASH_SIZE); crypto_sha256_final(hashbuf); - crypto_ecc256_load_attestation_key(); crypto_ecc256_sign(hashbuf, 32, sigbuf); /*printf("signature hash: "); dump_hex(hashbuf, 32);*/ @@ -735,7 +793,7 @@ void ctap_make_credential(CborEncoder * encoder, uint8_t * request, int length) { CTAP_makeCredential MC; int ret; - uint8_t auth_data_buf[200]; + uint8_t auth_data_buf[300]; uint8_t * hashbuf = auth_data_buf + 0; uint8_t * sigbuf = auth_data_buf + 32; uint8_t * sigder = auth_data_buf + 32 + 64; @@ -757,18 +815,360 @@ void ctap_make_credential(CborEncoder * encoder, uint8_t * request, int length) ret = cbor_encoder_create_map(encoder, &map, 3); check_ret(ret); - int auth_data_sz = ctap_make_auth_data(&MC, &map, auth_data_buf, sizeof(auth_data_buf)); + int auth_data_sz = ctap_make_auth_data(&MC.rp, &map, auth_data_buf, sizeof(auth_data_buf), + &MC.user, MC.publicKeyCredentialType, MC.COSEAlgorithmIdentifier); + crypto_ecc256_load_attestation_key(); int sigder_sz = ctap_calculate_signature(auth_data_buf, auth_data_sz, MC.clientDataHash, auth_data_buf, sigbuf, sigder); printf("der sig [%d]: ", sigder_sz); dump_hex(sigder, sigder_sz); ctap_add_attest_statement(&map, sigder, sigder_sz); + { + ret = cbor_encode_int(&map,RESP_fmt); + check_ret(ret); + ret = cbor_encode_text_stringz(&map, "packed"); + check_ret(ret); + } + ret = cbor_encoder_close_container(encoder, &map); check_ret(ret); } +static int parse_allow_list(CTAP_getAssertion * GA, CborValue * it) +{ + CborValue arr, val; + size_t len,buflen; + uint8_t type[12]; + int i,ret; + + if (cbor_value_get_type(it) != CborArrayType) + { + printf("Error, expecting cbor array\n"); + return -1; + } + + ret = cbor_value_enter_container(it,&arr); + check_ret(ret); + + ret = cbor_value_get_array_length(it, &len); + check_ret(ret); + + GA->credLen = 0; + + for(i = 0; i < len; i++) + { + if (i >= ALLOW_LIST_MAX_SIZE) + { + printf("Warning, out of memory for allow list, truncating.\n"); + break; + } + GA->credLen += 1; + + if (cbor_value_get_type(&arr) != CborMapType) + { + printf("Error, CborMapType expected in allow_list\n"); + return -1; + } + + ret = cbor_value_map_find_value(&arr, "id", &val); + check_ret(ret); + + if (cbor_value_get_type(&val) != CborByteStringType) + { + printf("Error, No valid ID field (%s)\n", cbor_value_get_type_string(&val)); + return -1; + } + + buflen = CREDENTIAL_ID_SIZE; + cbor_value_copy_byte_string(&val, GA->creds[i].credential.id, &buflen, NULL); + if (buflen != CREDENTIAL_ID_SIZE) + { + printf("Error, credential is incorrect length\n"); + return -1; // maybe just skip it instead of fail? + } + + ret = cbor_value_map_find_value(&arr, "type", &val); + check_ret(ret); + + if (cbor_value_get_type(&val) != CborTextStringType) + { + printf("Error, No valid type field\n"); + return -1; + } + + buflen = sizeof(type); + cbor_value_copy_text_string(&val, type, &buflen, NULL); + + if (strcmp(type, "public-key") == 0) + { + GA->creds[i].type = PUB_KEY_CRED_PUB_KEY; + } + else + { + GA->creds[i].type = PUB_KEY_CRED_UNKNOWN; + } + + ret = cbor_value_advance(&arr); + check_ret(ret); + } + return 0; +} + +// Return 1 if credential belongs to this token +int ctap_authenticate_credential(struct rpId * rp, CTAP_credentialDescriptor * desc) +{ + uint8_t tag[16]; + if (desc->type != PUB_KEY_CRED_PUB_KEY) + { + printf("unsupported credential type: %d\n", desc->type); + return 0; + } + + make_auth_tag(rp, &desc->credential.fields.user, desc->credential.fields.count, tag); + + return (memcmp(desc->credential.fields.tag, tag, CREDENTIAL_TAG_SIZE) == 0); +} + +int ctap_parse_get_assertion(CTAP_getAssertion * GA, uint8_t * request, int length) +{ + int ret; + int i,j; + int key; + size_t map_length; + size_t sz; + CborParser parser; + CborValue it,map; + + memset(GA, 0, sizeof(CTAP_getAssertion)); + 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("GA 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); + ret = 0; + + switch(key) + { + + case GA_clientDataHash: + printf("GA_clientDataHash\n"); + + ret = parse_client_data_hash(GA->clientDataHash, &map); + + printf(" "); dump_hex(GA->clientDataHash, 32); + break; + case GA_rpId: + printf("GA_rpId\n"); + + ret = parse_rp_id(&GA->rp, &map); + + printf(" ID: %s\n", GA->rp.id); + break; + case GA_allowList: + printf("GA_allowList\n"); + ret = parse_allow_list(GA, &map); + if (ret == 0) + { + for (j = 0; j < GA->credLen; j++) + { + printf("CRED ID (# %d): ", GA->creds[j].credential.fields.count); + dump_hex(GA->creds[j].credential.id, CREDENTIAL_ID_SIZE); + if (ctap_authenticate_credential(&GA->rp, &GA->creds[j])) // warning encryption will break this + { + printf(" Authenticated.\n"); + } + else + { + printf(" NOT authentic.\n"); + } + } + } + break; + case GA_extensions: + printf("GA_extensions\n"); + break; + case GA_options: + printf("GA_options\n"); + break; + case GA_pinAuth: + printf("GA_pinAuth\n"); + break; + case GA_pinProtocol: + printf("GA_pinProtocol\n"); + break; + default: + printf("invalid key %d\n", key); + + } + if (ret != 0) + { + printf("error, parsing failed\n"); + return ret; + } + + cbor_value_advance(&map); + check_ret(ret); + } + + + return 0; +} + +static int pick_first_authentic_credential(CTAP_getAssertion * GA) +{ + int i; + for (i = 0; i < GA->credLen; i++) + { + if (ctap_authenticate_credential(&GA->rp, &GA->creds[i])) + { + return i; + } + } + return -1; +} + +static void ctap_add_credential_descriptor(CborEncoder * map, CTAP_credentialDescriptor * cred) +{ + CborEncoder desc; + int ret = cbor_encode_int(map, RESP_credential); + check_ret(ret); + + ret = cbor_encoder_create_map(map, &desc, 2); + check_ret(ret); + + { + ret = cbor_encode_text_string(&desc, "type", 4); + check_ret(ret); + + ret = cbor_encode_int(&desc, cred->type); + check_ret(ret); + } + { + ret = cbor_encode_text_string(&desc, "id", 2); + check_ret(ret); + + ret = cbor_encode_byte_string(&desc, cred->credential.id, CREDENTIAL_ID_SIZE); + check_ret(ret); + } + + ret = cbor_encoder_close_container(map, &desc); + check_ret(ret); +} + +void ctap_add_user_entity(CborEncoder * map, CTAP_userEntity * user) +{ + CborEncoder entity; + int ret = cbor_encode_int(map, RESP_publicKeyCredentialUserEntity); + check_ret(ret); + + ret = cbor_encoder_create_map(map, &entity, 2); + check_ret(ret); + + { + ret = cbor_encode_text_string(&entity, "id", 2); + check_ret(ret); + + ret = cbor_encode_byte_string(&entity, user->id, user->id_size); + check_ret(ret); + } + + + { + ret = cbor_encode_text_string(&entity, "displayName", 11); + check_ret(ret); + + ret = cbor_encode_text_stringz(&entity, user->name); + check_ret(ret); + } + + ret = cbor_encoder_close_container(map, &entity); + check_ret(ret); + +} + +void ctap_get_assertion(CborEncoder * encoder, uint8_t * request, int length) +{ + CTAP_getAssertion GA; + uint8_t auth_data_buf[32 + 1 + 4]; + uint8_t sigbuf[64]; + uint8_t sigder[72]; + + int ret = ctap_parse_get_assertion(&GA,request,length); + + if (ret != 0) + { + printf("error, parse_get_assertion failed\n"); + return; + } + + CborEncoder map; + ret = cbor_encoder_create_map(encoder, &map, 5); + check_ret(ret); + + ctap_make_auth_data(&GA.rp, &map, auth_data_buf, sizeof(auth_data_buf), NULL, 0,0); + + int pick = pick_first_authentic_credential(&GA); // TODO let this handle decryption? lazy? + if (pick == -1) + { + printf("Error, no authentic credential\n"); + return; + } + + ctap_add_credential_descriptor(&map, &GA.creds[pick]); + + ctap_add_user_entity(&map, &GA.creds[pick].credential.fields.user); + + crypto_ecc256_load_key(GA.creds[pick].credential.id, CREDENTIAL_ID_SIZE); + + int sigder_sz = ctap_calculate_signature(auth_data_buf, sizeof(auth_data_buf), GA.clientDataHash, auth_data_buf, sigbuf, sigder); + + { + ret = cbor_encode_int(&map, RESP_signature); + check_ret(ret); + ret = cbor_encode_byte_string(&map, sigbuf, 64); + check_ret(ret); + } + + { + ret = cbor_encode_int(&map, RESP_numberOfCredentials); + check_ret(ret); + ret = cbor_encode_int(&map, GA.credLen); + check_ret(ret); + } + + ret = cbor_encoder_close_container(encoder, &map); + check_ret(ret); + + + +} uint8_t ctap_handle_packet(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp) { @@ -784,6 +1184,7 @@ uint8_t ctap_handle_packet(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp) CborEncoder encoder; cbor_encoder_init(&encoder, buf, sizeof(buf), 0); + _ENCODER = &encoder; printf("cbor req: "); dump_hex(pkt_raw, length - 1); @@ -798,6 +1199,10 @@ uint8_t ctap_handle_packet(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp) break; case CTAP_GET_ASSERTION: printf("CTAP_GET_ASSERTION\n"); + ctap_get_assertion(&encoder, pkt_raw, length - 1); + resp->length = cbor_encoder_get_buffer_size(&encoder, buf); + + printf("cbor [%d]: \n", cbor_encoder_get_buffer_size(&encoder, buf)); dump_hex(buf, cbor_encoder_get_buffer_size(&encoder, buf)); break; case CTAP_CANCEL: printf("CTAP_CANCEL\n"); diff --git a/ctap.h b/ctap.h index ac5fad4..e9c96d5 100644 --- a/ctap.h +++ b/ctap.h @@ -44,6 +44,12 @@ #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 PARAM_clientDataHash (1 << 0) @@ -67,7 +73,9 @@ #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 48 +#define CREDENTIAL_TAG_SIZE 16 +#define CREDENTIAL_COUNTER_SIZE (4) +#define CREDENTIAL_ID_SIZE (CREDENTIAL_TAG_SIZE + USER_ID_MAX_SIZE + USER_NAME_LIMIT + CREDENTIAL_COUNTER_SIZE + 1) #define PUB_KEY_CRED_PUB_KEY 0x01 #define PUB_KEY_CRED_UNKNOWN 0x3F @@ -75,13 +83,30 @@ #define CREDENTIAL_IS_SUPPORTED 1 #define CREDENTIAL_NOT_SUPPORTED 0 +#define ALLOW_LIST_MAX_SIZE 20 + +typedef struct +{ + uint8_t id[USER_ID_MAX_SIZE]; + uint8_t id_size; + uint8_t name[USER_NAME_LIMIT]; +} CTAP_userEntity; + +union _credential { + struct { + uint8_t tag[CREDENTIAL_TAG_SIZE]; + CTAP_userEntity user; + uint32_t count; + }__attribute__((packed)) fields; + uint8_t id[CREDENTIAL_ID_SIZE]; +}; typedef struct { uint8_t aaguid[16]; uint8_t credLenH; uint8_t credLenL; - uint8_t credentialId[CREDENTIAL_ID_SIZE]; + union _credential credential; } __attribute__((packed)) CTAP_attestHeader; @@ -93,26 +118,25 @@ typedef struct CTAP_attestHeader attest; } __attribute__((packed)) CTAP_authData; - - typedef struct { uint8_t * data; uint16_t length; } CTAP_RESPONSE; +struct rpId +{ + uint8_t id[DOMAIN_NAME_MAX_SIZE + 1]; // extra for NULL termination + size_t size; + uint8_t name[RP_NAME_LIMIT]; +}; + 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]; + struct rpId rp; + CTAP_userEntity user; uint8_t publicKeyCredentialType; int32_t COSEAlgorithmIdentifier; @@ -121,6 +145,33 @@ typedef struct } CTAP_makeCredential; +typedef struct +{ + uint8_t type; + union _credential credential; +} CTAP_credentialDescriptor; + +typedef struct +{ + uint32_t paramsParsed; + uint8_t clientDataHash[CLIENT_DATA_HASH_SIZE]; + + struct rpId rp; + + CTAP_credentialDescriptor creds[ALLOW_LIST_MAX_SIZE]; + int credLen; + + //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_getAssertion; + uint8_t ctap_handle_packet(uint8_t * pkt_raw, int length, CTAP_RESPONSE * resp); // Test for user presence @@ -130,8 +181,9 @@ 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(); +// Increment atomic counter and return it. +// Must support two counters, @sel selects counter0 or counter1. +uint32_t ctap_atomic_count(int sel); // Verify the user // return 1 if user is verified, 0 if not diff --git a/ctap_device.c b/ctap_device.c index 8cf565e..12f0af0 100644 --- a/ctap_device.c +++ b/ctap_device.c @@ -30,10 +30,18 @@ int ctap_user_verification(uint8_t arg) } -uint32_t ctap_atomic_count() +uint32_t ctap_atomic_count(int sel) { - static uint32_t counter = 25; - return counter++; + static uint32_t counter1 = 25; + static uint32_t counter2 = 25; + if (sel == 0) + { + return counter1++; + } + else + { + return counter2++; + } } int ctap_generate_rng(uint8_t * dst, size_t num)