From f43bc9701ff0973f3dee07a0786210f0c1c7f8d4 Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Sat, 8 Feb 2025 15:00:12 +0100 Subject: [PATCH] Added support for silent authentication. Fixes #91. It requires FIDO22 credential protocol, meaning that old credentials have to be reissued. Signed-off-by: Pol Henarejos --- src/fido/cbor_get_assertion.c | 102 ++++++++++++++++++++++------------ src/fido/cmd_authenticate.c | 2 +- src/fido/credential.c | 82 ++++++++++++++++++--------- src/fido/credential.h | 18 +++++- 4 files changed, 140 insertions(+), 64 deletions(-) diff --git a/src/fido/cbor_get_assertion.c b/src/fido/cbor_get_assertion.c index b2a28dd..d4bb3b4 100644 --- a/src/fido/cbor_get_assertion.c +++ b/src/fido/cbor_get_assertion.c @@ -279,6 +279,8 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { } } + bool silent = (up == false && uv == false); + if (allowList_len > 0) { for (size_t e = 0; e < allowList_len; e++) { if (allowList[e].type.present == false || allowList[e].id.present == false) { @@ -288,7 +290,6 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { continue; } if (credential_load(allowList[e].id.data, allowList[e].id.len, rp_id_hash, &creds[creds_len]) != 0) { - CBOR_FREE_BYTE_STRING(allowList[e].id); credential_free(&creds[creds_len]); } else { @@ -342,15 +343,32 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { } } if (numberOfCredentials == 0) { - CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS); + if (silent && allowList_len > 0) { + for (size_t e = 0; e < allowList_len; e++) { + if (allowList[e].type.present == false || allowList[e].id.present == false) { + CBOR_ERROR(CTAP2_ERR_MISSING_PARAMETER); + } + if (strcmp(allowList[e].type.data, "public-key") != 0) { + continue; + } + if (credential_verify(allowList[e].id.data, allowList[e].id.len, rp_id_hash, true) == 0) { + numberOfCredentials++; + } + } + } + if (numberOfCredentials == 0) { + CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS); + } } - for (int i = 0; i < numberOfCredentials; i++) { - for (int j = i + 1; j < numberOfCredentials; j++) { - if (creds[j].creation > creds[i].creation) { - Credential tmp = creds[j]; - creds[j] = creds[i]; - creds[i] = tmp; + if (!silent) { + for (int i = 0; i < numberOfCredentials; i++) { + for (int j = i + 1; j < numberOfCredentials; j++) { + if (creds[j].creation > creds[i].creation) { + Credential tmp = creds[j]; + creds[j] = creds[i]; + creds[i] = tmp; + } } } } @@ -380,8 +398,8 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { CBOR_ERROR(CTAP2_ERR_INVALID_OPTION); } - if (up == false && uv == false) { - selcred = &creds[0]; + if (silent && !resident) { + // Silent authentication, do nothing } else { selcred = &creds[0]; @@ -410,16 +428,18 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { int ret = 0; uint8_t largeBlobKey[32] = {0}; - if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) { - ret = credential_derive_large_blob_key(selcred->id.data, selcred->id.len, largeBlobKey); - if (ret != 0) { - CBOR_ERROR(CTAP2_ERR_PROCESSING); + if (selcred) { + if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) { + ret = credential_derive_large_blob_key(selcred->id.data, selcred->id.len, largeBlobKey); + if (ret != 0) { + CBOR_ERROR(CTAP2_ERR_PROCESSING); + } } } size_t ext_len = 0; uint8_t ext[512] = {0}; - if (extensions.present == true) { + if (selcred && extensions.present == true) { cbor_encoder_init(&encoder, ext, sizeof(ext), 0); int l = 0; if (options.up == pfalse) { @@ -530,32 +550,39 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { const mbedtls_md_info_t *md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); mbedtls_ecdsa_context ekey; mbedtls_ecdsa_init(&ekey); - ret = fido_load_key((int)selcred->curve, selcred->id.data, &ekey); - if (ret != 0) { - if (derive_key(rp_id_hash, false, selcred->id.data, MBEDTLS_ECP_DP_SECP256R1, &ekey) != 0) { - mbedtls_ecdsa_free(&ekey); - CBOR_ERROR(CTAP1_ERR_OTHER); - } - } - if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1) { - md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); - } - else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) { - md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); - } - ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash); size_t olen = 0; - ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_gen, NULL); + if (selcred) { + ret = fido_load_key((int)selcred->curve, selcred->id.data, &ekey); + if (ret != 0) { + if (derive_key(rp_id_hash, false, selcred->id.data, MBEDTLS_ECP_DP_SECP256R1, &ekey) != 0) { + mbedtls_ecdsa_free(&ekey); + CBOR_ERROR(CTAP1_ERR_OTHER); + } + } + if (ekey.grp.id == MBEDTLS_ECP_DP_SECP384R1) { + md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + } + else if (ekey.grp.id == MBEDTLS_ECP_DP_SECP521R1) { + md = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + } + ret = mbedtls_md(md, aut_data, aut_data_len + clientDataHash.len, hash); + ret = mbedtls_ecdsa_write_signature(&ekey, mbedtls_md_get_type(md), hash, mbedtls_md_get_size(md), sig, sizeof(sig), &olen, random_gen, NULL); + } + else { + // Bogus signature + olen = 64; + memset(sig, 0x0B, olen); + } mbedtls_ecdsa_free(&ekey); uint8_t lfields = 3; - if (selcred->opts.present == true && selcred->opts.rk == ptrue) { + if (selcred && selcred->opts.present == true && selcred->opts.rk == ptrue) { lfields++; } if (numberOfCredentials > 1 && next == false) { lfields++; } - if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) { + if (selcred && extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) { lfields++; } cbor_encoder_init(&encoder, ctap_resp->init.data + 1, CTAP_MAX_CBOR_PAYLOAD, 0); @@ -564,7 +591,12 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, 2)); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "id")); - CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len)); + if (selcred) { + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, selcred->id.data, selcred->id.len)); + } + else { + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, (uint8_t *)"\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01\x00\x01", 16)); + } CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "type")); CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "public-key")); CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2)); @@ -574,7 +606,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03)); CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, sig, olen)); - if (selcred->opts.present == true && selcred->opts.rk == ptrue) { + if (selcred && selcred->opts.present == true && selcred->opts.rk == ptrue) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04)); uint8_t lu = 1; if (numberOfCredentials > 1 && allowList_len == 0) { @@ -605,7 +637,7 @@ int cbor_get_assertion(const uint8_t *data, size_t len, bool next) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05)); CBOR_CHECK(cbor_encode_uint(&mapEncoder, numberOfCredentials)); } - if (extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) { + if (selcred && extensions.largeBlobKey == ptrue && selcred->extensions.largeBlobKey == ptrue) { CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07)); CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, largeBlobKey, sizeof(largeBlobKey))); } diff --git a/src/fido/cmd_authenticate.c b/src/fido/cmd_authenticate.c index ea74e47..2e7808c 100644 --- a/src/fido/cmd_authenticate.c +++ b/src/fido/cmd_authenticate.c @@ -43,7 +43,7 @@ int cmd_authenticate() { int ret = 0; uint8_t *tmp_kh = (uint8_t *) calloc(1, req->keyHandleLen); memcpy(tmp_kh, req->keyHandle, req->keyHandleLen); - if (credential_verify(tmp_kh, req->keyHandleLen, req->appId) == 0) { + if (credential_verify(tmp_kh, req->keyHandleLen, req->appId, false) == 0) { ret = fido_load_key(FIDO2_CURVE_P256, req->keyHandle, &key); } else { diff --git a/src/fido/credential.c b/src/fido/credential.c index 4eea40c..ea2e431 100644 --- a/src/fido/credential.c +++ b/src/fido/credential.c @@ -27,22 +27,52 @@ #include "random.h" #include "files.h" #include "pico_keys.h" +#include "otp.h" -int credential_derive_chacha_key(uint8_t *outk); +int credential_derive_chacha_key(uint8_t *outk, const uint8_t *); -int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash) { +static int credential_silent_tag(const uint8_t *cred_id, size_t cred_id_len, uint8_t *outk) { + if (otp_key_1) { + memcpy(outk, otp_key_1, 32); + } + else { + mbedtls_sha256(pico_serial.id, PICO_UNIQUE_BOARD_ID_SIZE_BYTES, outk, 0); + } + return mbedtls_md_hmac(mbedtls_md_info_from_type(MBEDTLS_MD_SHA256), outk, 32, cred_id, cred_id_len - CRED_SILENT_TAG_LEN, outk); +} + +int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash, bool silent) { if (cred_id_len < 4 + 12 + 16) { return -1; } - uint8_t key[32], *iv = cred_id + 4, *cipher = cred_id + 4 + 12, - *tag = cred_id + cred_id_len - 16; - memset(key, 0, sizeof(key)); - credential_derive_chacha_key(key); - mbedtls_chachapoly_context chatx; - mbedtls_chachapoly_init(&chatx); - mbedtls_chachapoly_setkey(&chatx, key); - int ret = mbedtls_chachapoly_auth_decrypt(&chatx, cred_id_len - (4 + 12 + 16), iv, rp_id_hash, 32, tag, cipher, cipher); - mbedtls_chachapoly_free(&chatx); + uint8_t key[32] = {0}, *iv = cred_id + CRED_PROTO_LEN, *cipher = cred_id + CRED_PROTO_LEN + CRED_IV_LEN, + *tag = cred_id + cred_id_len - CRED_TAG_LEN; + cred_proto_t proto = CRED_PROTO_21; + if (memcmp(cred_id, CRED_PROTO_22_S, CRED_PROTO_LEN) == 0) { // New format + tag = cred_id + cred_id_len - CRED_SILENT_TAG_LEN - CRED_TAG_LEN; + proto = CRED_PROTO_22; + } + int ret = 0; + if (!silent) { + int hdr_len = CRED_PROTO_LEN + CRED_IV_LEN + CRED_TAG_LEN; + if (proto == CRED_PROTO_22) { + hdr_len += CRED_SILENT_TAG_LEN; + } + credential_derive_chacha_key(key, cred_id); + mbedtls_chachapoly_context chatx; + mbedtls_chachapoly_init(&chatx); + mbedtls_chachapoly_setkey(&chatx, key); + ret = mbedtls_chachapoly_auth_decrypt(&chatx, cred_id_len - hdr_len, iv, rp_id_hash, 32, tag, cipher, cipher); + mbedtls_chachapoly_free(&chatx); + } + else { + if (proto <= CRED_PROTO_21) { + return -1; + } + uint8_t outk[32]; + ret = credential_silent_tag(cred_id, cred_id_len, outk); + ret = memcmp(outk, cred_id + cred_id_len - CRED_SILENT_TAG_LEN, CRED_SILENT_TAG_LEN); + } return ret; } @@ -113,25 +143,25 @@ int credential_create(CborCharString *rpId, } CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); size_t rs = cbor_encoder_get_buffer_size(&encoder, cred_id); - *cred_id_len = 4 + 12 + rs + 16; - uint8_t key[32]; - memset(key, 0, sizeof(key)); - credential_derive_chacha_key(key); - uint8_t iv[12]; + *cred_id_len = CRED_PROTO_LEN + CRED_IV_LEN + rs + CRED_TAG_LEN + CRED_SILENT_TAG_LEN; + uint8_t key[32] = {0}; + credential_derive_chacha_key(key, (const uint8_t *)CRED_PROTO); + uint8_t iv[CRED_IV_LEN] = {0}; random_gen(NULL, iv, sizeof(iv)); mbedtls_chachapoly_context chatx; mbedtls_chachapoly_init(&chatx); mbedtls_chachapoly_setkey(&chatx, key); int ret = mbedtls_chachapoly_encrypt_and_tag(&chatx, rs, iv, rp_id_hash, 32, - cred_id + 4 + 12, - cred_id + 4 + 12, - cred_id + 4 + 12 + rs); + cred_id + CRED_PROTO_LEN + CRED_IV_LEN, + cred_id + CRED_PROTO_LEN + CRED_IV_LEN, + cred_id + CRED_PROTO_LEN + CRED_IV_LEN + rs); mbedtls_chachapoly_free(&chatx); if (ret != 0) { CBOR_ERROR(CTAP1_ERR_OTHER); } - memcpy(cred_id, CRED_PROTO, 4); - memcpy(cred_id + 4, iv, 12); + memcpy(cred_id, CRED_PROTO, CRED_PROTO_LEN); + memcpy(cred_id + CRED_PROTO_LEN, iv, CRED_IV_LEN); + credential_silent_tag(cred_id, *cred_id_len, cred_id + CRED_PROTO_LEN + CRED_IV_LEN + rs + CRED_TAG_LEN); err: if (error != CborNoError) { @@ -152,7 +182,7 @@ int credential_load(const uint8_t *cred_id, size_t cred_id_len, const uint8_t *r } memset(cred, 0, sizeof(Credential)); memcpy(copy_cred_id, cred_id, cred_id_len); - ret = credential_verify(copy_cred_id, cred_id_len, rp_id_hash); + ret = credential_verify(copy_cred_id, cred_id_len, rp_id_hash, false); if (ret != 0) { // U2F? if (cred_id_len != KEY_HANDLE_LEN || verify_key(rp_id_hash, cred_id, NULL) != 0) { CBOR_ERROR(CTAP2_ERR_INVALID_CREDENTIAL); @@ -350,13 +380,13 @@ int credential_derive_hmac_key(const uint8_t *cred_id, size_t cred_id_len, uint8 const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "SLIP-0022", 9, outk); - mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) CRED_PROTO, 4, outk); + mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) cred_id, CRED_PROTO_LEN, outk); mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "hmac-secret", 11, outk); mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk); return 0; } -int credential_derive_chacha_key(uint8_t *outk) { +int credential_derive_chacha_key(uint8_t *outk, const uint8_t *proto) { memset(outk, 0, 32); int r = 0; if ((r = load_keydev(outk)) != 0) { @@ -365,7 +395,7 @@ int credential_derive_chacha_key(uint8_t *outk) { const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "SLIP-0022", 9, outk); - mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) CRED_PROTO, 4, outk); + mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) (proto ? proto : (const uint8_t *)CRED_PROTO), CRED_PROTO_LEN, outk); mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "Encryption key", 14, outk); return 0; } @@ -379,7 +409,7 @@ int credential_derive_large_blob_key(const uint8_t *cred_id, size_t cred_id_len, const mbedtls_md_info_t *md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "SLIP-0022", 9, outk); - mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) CRED_PROTO, 4, outk); + mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) cred_id, CRED_PROTO_LEN, outk); mbedtls_md_hmac(md_info, outk, 32, (uint8_t *) "largeBlobKey", 12, outk); mbedtls_md_hmac(md_info, outk, 32, cred_id, cred_id_len, outk); return 0; diff --git a/src/fido/credential.h b/src/fido/credential.h index 313077b..c5cfc93 100644 --- a/src/fido/credential.h +++ b/src/fido/credential.h @@ -56,9 +56,23 @@ typedef struct Credential { #define CRED_PROT_UV_OPTIONAL_WITH_LIST 0x02 #define CRED_PROT_UV_REQUIRED 0x03 -#define CRED_PROTO "\xf1\xd0\x02\x01" +#define CRED_PROTO_21_S "\xf1\xd0\x02\x01" +#define CRED_PROTO_22_S "\xf1\xd0\x02\x02" -extern int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash); +#define CRED_PROTO CRED_PROTO_22_S + +#define CRED_PROTO_LEN 4 +#define CRED_IV_LEN 12 +#define CRED_TAG_LEN 16 +#define CRED_SILENT_TAG_LEN 16 + +typedef enum +{ + CRED_PROTO_21 = 0x01, + CRED_PROTO_22 = 0x02, +} cred_proto_t; + +extern int credential_verify(uint8_t *cred_id, size_t cred_id_len, const uint8_t *rp_id_hash, bool silent); extern int credential_create(CborCharString *rpId, CborByteString *userId, CborCharString *userName,