From ffd99790219ac2853d455cdcc4b5e4c58bcfaf9a Mon Sep 17 00:00:00 2001 From: Pol Henarejos Date: Thu, 6 Aug 2026 18:44:38 +0200 Subject: [PATCH] Add credential metadata export since it cannot be de-embedded Signed-off-by: Pol Henarejos --- src/fido/cbor_config.c | 2 +- src/fido/cbor_vendor.c | 155 ++++++++++++++++++++++++++++++++++------- 2 files changed, 132 insertions(+), 25 deletions(-) diff --git a/src/fido/cbor_config.c b/src/fido/cbor_config.c index f811525..cf3e31a 100644 --- a/src/fido/cbor_config.c +++ b/src/fido/cbor_config.c @@ -186,7 +186,7 @@ int cbor_config(const uint8_t *data, size_t len) { mbedtls_chachapoly_context chatx; int ret = mse_decrypt_ct(vendorParamByteString.data, vendorParamByteString.len); - if (ret != 0) { + if (ret != PICOKEYS_OK) { CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); } diff --git a/src/fido/cbor_vendor.c b/src/fido/cbor_vendor.c index 064c55b..1c6b7f8 100644 --- a/src/fido/cbor_vendor.c +++ b/src/fido/cbor_vendor.c @@ -41,6 +41,7 @@ mse_t mse = { .init = false }; #define VAULT_X448_BYTES 56 #define VAULT_CHANNEL_KEY_BYTES 32 #define VAULT_CHANNEL_INFO "pico-fido-vault-v1" +#define VAULT_CREDENTIAL_METADATA_MAX 512 static const uint8_t picokeys_vault_ca_der[] = { 0x30, 0x82, 0x01, 0xEB, 0x30, 0x82, 0x01, 0x6B, 0xA0, 0x03, 0x02, 0x01, @@ -133,7 +134,10 @@ static int vault_x448_generate(uint8_t private_key[VAULT_X448_BYTES], uint8_t pu ret = mbedtls_ecp_point_write_binary(&hkey.ctx.mbed_ecdh.grp, &hkey.ctx.mbed_ecdh.Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &public_len, public_key, VAULT_X448_BYTES); } mbedtls_ecdh_free(&hkey); - return ret == 0 && public_len == VAULT_X448_BYTES ? 0 : -1; + if (ret != 0) { + return PICOKEYS_EXEC_ERROR; + } + return public_len == VAULT_X448_BYTES ? PICOKEYS_OK : PICOKEYS_WRONG_LENGTH; } static int vault_x448_shared(const uint8_t private_key[VAULT_X448_BYTES], const uint8_t peer_public[VAULT_X448_BYTES], uint8_t shared[VAULT_X448_BYTES]) { @@ -169,10 +173,16 @@ static int vault_x448_shared(const uint8_t private_key[VAULT_X448_BYTES], const mbedtls_ecdh_free(&ecdh); mbedtls_ecp_keypair_free(&ours); mbedtls_ecp_keypair_free(&theirs); - return ret == 0 && shared_len == VAULT_X448_BYTES ? 0 : -1; + if (ret != 0) { + return PICOKEYS_EXEC_ERROR; + } + return shared_len == VAULT_X448_BYTES ? PICOKEYS_OK : PICOKEYS_WRONG_LENGTH; } static int vault_validate_certificate(mbedtls_x509_crt *certificate) { + if (!certificate) { + return PICOKEYS_ERR_NULL_PARAM; + } mbedtls_x509_crt ca; uint32_t flags = 0; mbedtls_x509_crt_init(&ca); @@ -181,26 +191,111 @@ static int vault_validate_certificate(mbedtls_x509_crt *certificate) { ret = mbedtls_x509_crt_verify(certificate, &ca, NULL, NULL, &flags, NULL, NULL); } mbedtls_x509_crt_free(&ca); - return ret == 0 && flags == 0 ? 0 : -1; + return ret != 0 ? PICOKEYS_EXEC_ERROR : (flags == 0 ? PICOKEYS_OK : PICOKEYS_VERIFICATION_FAILED); } int mse_decrypt_ct(uint8_t *data, size_t len) { if (data == NULL || len < 16) { - return -1; + return PICOKEYS_ERR_NULL_PARAM; } mbedtls_chachapoly_context chatx; mbedtls_chachapoly_init(&chatx); mbedtls_chachapoly_setkey(&chatx, mse.key_enc + 12); int ret = mbedtls_chachapoly_auth_decrypt(&chatx, len - 16, mse.key_enc, mse.Qpt, 65, data + len - 16, data, data); mbedtls_chachapoly_free(&chatx); - return ret; + return ret == 0 ? PICOKEYS_OK : PICOKEYS_VERIFICATION_FAILED; +} + +static int vault_encode_credential_metadata(const Credential *credential, const uint8_t rp_id_hash[RP_ID_HASH_LEN], uint8_t *buffer, size_t buffer_len, size_t *metadata_len) { + if (!credential || !rp_id_hash || !buffer || !metadata_len) { + return PICOKEYS_ERR_NULL_PARAM; + } + CborEncoder encoder, mapEncoder, mapEncoder2; + CborError error = CborNoError; + cbor_encoder_init(&encoder, buffer, buffer_len, 0); + CBOR_CHECK(cbor_encoder_create_map(&encoder, &mapEncoder, CborIndefiniteLength)); + if (credential->rpId.present) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x01)); + CBOR_CHECK(cbor_encode_text_string(&mapEncoder, credential->rpId.data, credential->rpId.len)); + } + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x02)); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, rp_id_hash, RP_ID_HASH_LEN)); + if (credential->userId.present) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x03)); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, credential->userId.data, credential->userId.len)); + } + if (credential->userName.present) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x04)); + CBOR_CHECK(cbor_encode_text_string(&mapEncoder, credential->userName.data, credential->userName.len)); + } + if (credential->userDisplayName.present) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x05)); + CBOR_CHECK(cbor_encode_text_string(&mapEncoder, credential->userDisplayName.data, credential->userDisplayName.len)); + } + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x06)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, credential->board_creation)); + if (credential->extensions.present) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x07)); + CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, CborIndefiniteLength)); + if (credential->extensions.credBlob.present) { + CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "credBlob")); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder2, credential->extensions.credBlob.data, credential->extensions.credBlob.len)); + } + if (credential->extensions.credProtect != 0) { + CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "credProtect")); + CBOR_CHECK(cbor_encode_uint(&mapEncoder2, credential->extensions.credProtect)); + } + if (credential->extensions.hmac_secret != NULL) { + CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "hmac-secret")); + CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, *credential->extensions.hmac_secret)); + } + if (credential->extensions.largeBlobKey == ptrue) { + CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "largeBlobKey")); + CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, true)); + } + if (credential->extensions.thirdPartyPayment == ptrue) { + CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "thirdPartyPayment")); + CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, true)); + } + CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2)); + } + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x08)); + CBOR_CHECK(cbor_encode_boolean(&mapEncoder, credential->use_sign_count == ptrue)); + if (credential->alg != FIDO2_ALG_ES256 || credential->curve != FIDO2_CURVE_P256) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x09)); + CBOR_CHECK(cbor_encode_int(&mapEncoder, credential->alg)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0A)); + CBOR_CHECK(cbor_encode_int(&mapEncoder, credential->curve)); + } + if (credential->opts.present) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0B)); + CBOR_CHECK(cbor_encoder_create_map(&mapEncoder, &mapEncoder2, CborIndefiniteLength)); + if (credential->opts.rk != NULL) { + CBOR_CHECK(cbor_encode_text_stringz(&mapEncoder2, "rk")); + CBOR_CHECK(cbor_encode_boolean(&mapEncoder2, credential->opts.rk == ptrue)); + } + CBOR_CHECK(cbor_encoder_close_container(&mapEncoder, &mapEncoder2)); + } + if (credential->rtc_creation != 0) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0C)); + CBOR_CHECK(cbor_encode_uint(&mapEncoder, credential->rtc_creation)); + } + if (credential->residentId.present) { + CBOR_CHECK(cbor_encode_uint(&mapEncoder, 0x0D)); + CBOR_CHECK(cbor_encode_byte_string(&mapEncoder, credential->residentId.data, credential->residentId.len)); + } + CBOR_CHECK(cbor_encoder_close_container(&encoder, &mapEncoder)); + *metadata_len = cbor_encoder_get_buffer_size(&encoder, buffer); + return PICOKEYS_OK; +err: + return PICOKEYS_ERR_NO_MEMORY; } static int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { CborParser parser; CborValue map; CborError error = CborNoError; - CborByteString pinUvAuthParam = { 0 }, vendorParam = { 0 }, kax = { 0 }, kay = { 0 }; + CborByteString pinUvAuthParam = { 0 }, vendorParam = { 0 }, kax = { 0 }, kay = { 0 }, requested_id = { 0 }; size_t resp_size = 0; uint64_t vendorCmd = 0, pinUvAuthProtocol = 0; int64_t kty = 0, alg = 0, crv = 0; @@ -345,7 +440,7 @@ static int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { mbedtls_chachapoly_context chatx; int ret = mse_decrypt_ct(vendorParam.data, vendorParam.len); - if (ret != 0) { + if (ret != PICOKEYS_OK) { CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); } @@ -376,7 +471,7 @@ static int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { } int ret = mbedtls_ecp_read_key(MBEDTLS_ECP_DP_SECP256R1, &ekey, keydev, 32); mbedtls_platform_zeroize(keydev, sizeof(keydev)); - if (ret != 0) { + if (ret != PICOKEYS_OK) { mbedtls_ecdsa_free(&ekey); CBOR_ERROR(CTAP2_ERR_PROCESSING); } @@ -425,11 +520,11 @@ static int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { mbedtls_x509_crt cert; mbedtls_x509_crt_init(&cert); int ret = mbedtls_x509_crt_parse(&cert, vendorParam.data, vendorParam.len); - if (ret == 0) { + if (ret == PICOKEYS_OK) { ret = vault_validate_certificate(&cert); } mbedtls_x509_crt_free(&cert); - if (ret != 0) { + if (ret != PICOKEYS_OK) { CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); } int auth_ret = vault_pin_auth((uint8_t)pinUvAuthProtocol, &pinUvAuthParam, raw_vendor_params, raw_vendor_params_len, vendorCmd); @@ -455,33 +550,33 @@ static int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { mbedtls_x509_crt cert; mbedtls_x509_crt_init(&cert); int ret = mbedtls_x509_crt_parse(&cert, vendorParam.data, vendorParam.len); - if (ret == 0) { + if (ret == PICOKEYS_OK) { ret = vault_validate_certificate(&cert); } uint8_t vault_public[VAULT_X448_BYTES] = {0}; size_t vault_public_len = 0; - if (ret == 0 && (mbedtls_pk_get_type(&cert.pk) == MBEDTLS_PK_ECKEY || mbedtls_pk_get_type(&cert.pk) == MBEDTLS_PK_ECKEY_DH)) { + if (ret == PICOKEYS_OK && (mbedtls_pk_get_type(&cert.pk) == MBEDTLS_PK_ECKEY || mbedtls_pk_get_type(&cert.pk) == MBEDTLS_PK_ECKEY_DH)) { mbedtls_ecp_keypair *key = mbedtls_pk_ec(cert.pk); if (key && key->grp.id == MBEDTLS_ECP_DP_CURVE448) { ret = mbedtls_ecp_point_write_binary(&key->grp, &key->Q, MBEDTLS_ECP_PF_UNCOMPRESSED, &vault_public_len, vault_public, sizeof(vault_public)); } else { - ret = -1; + ret = PICOKEYS_WRONG_DATA; } } else { - ret = -1; + ret = PICOKEYS_WRONG_DATA; } mbedtls_x509_crt_free(&cert); - if (ret != 0 || vault_public_len != VAULT_X448_BYTES) { + if (ret != PICOKEYS_OK || vault_public_len != VAULT_X448_BYTES) { CBOR_ERROR(CTAP1_ERR_INVALID_PARAMETER); } uint8_t ephemeral_private[VAULT_X448_BYTES] = {0}; uint8_t ephemeral_public[VAULT_X448_BYTES] = {0}; uint8_t shared[VAULT_X448_BYTES] = {0}; - if (vault_x448_generate(ephemeral_private, ephemeral_public) != 0 || - vault_x448_shared(ephemeral_private, vault_public, shared) != 0) { + if (vault_x448_generate(ephemeral_private, ephemeral_public) != PICOKEYS_OK || + vault_x448_shared(ephemeral_private, vault_public, shared) != PICOKEYS_OK) { mbedtls_platform_zeroize(ephemeral_private, sizeof(ephemeral_private)); mbedtls_platform_zeroize(shared, sizeof(shared)); CBOR_ERROR(CTAP2_ERR_PROCESSING); @@ -494,7 +589,7 @@ static int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { mbedtls_platform_zeroize(ephemeral_private, sizeof(ephemeral_private)); mbedtls_platform_zeroize(shared, sizeof(shared)); mbedtls_platform_zeroize(info, sizeof(info)); - if (ret != 0) { + if (ret != PICOKEYS_OK) { CBOR_ERROR(CTAP2_ERR_PROCESSING); } vault_channel_init = true; @@ -528,7 +623,6 @@ static int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { free(request); CBOR_ERROR(CTAP2_ERR_INTEGRITY_FAILURE); } - CborByteString requested_id = {0}; CborParser request_parser; CborValue request_map; DEBUG_DATA(request, request_len); @@ -573,7 +667,6 @@ static int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { } } } - CBOR_FREE_BYTE_STRING(requested_id); if (!found) { credential_free(&credential); CBOR_ERROR(CTAP2_ERR_NO_CREDENTIALS); @@ -601,10 +694,18 @@ static int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { mbedtls_platform_zeroize(private_key, sizeof(private_key)); CBOR_ERROR(CTAP2_ERR_PROCESSING); } - uint8_t plain[MAX_CRED_ID_LENGTH + 256] = {0}; + uint8_t credential_metadata[VAULT_CREDENTIAL_METADATA_MAX] = {0}; + size_t metadata_len = 0; + ret = vault_encode_credential_metadata(&credential, rp_id_hash, credential_metadata, sizeof(credential_metadata), &metadata_len); + if (ret != PICOKEYS_OK) { + credential_free(&credential); + mbedtls_platform_zeroize(private_key, sizeof(private_key)); + CBOR_ERROR(CTAP2_ERR_PROCESSING); + } + uint8_t plain[MAX_CRED_ID_LENGTH + VAULT_CREDENTIAL_METADATA_MAX] = {0}; cbor_encoder_init(&encoder, plain, sizeof(plain), 0); CborEncoder plain_map; - CBOR_CHECK(cbor_encoder_create_map(&encoder, &plain_map, 4)); + CBOR_CHECK(cbor_encoder_create_map(&encoder, &plain_map, 6)); CBOR_CHECK(cbor_encode_uint(&plain_map, 0x01)); CBOR_CHECK(cbor_encode_uint(&plain_map, 1)); CBOR_CHECK(cbor_encode_uint(&plain_map, 0x02)); @@ -613,10 +714,14 @@ static int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { CBOR_CHECK(cbor_encode_byte_string(&plain_map, private_key, private_len)); CBOR_CHECK(cbor_encode_uint(&plain_map, 0x04)); CBOR_CHECK(cbor_encode_text_string(&plain_map, credential.rpId.data, credential.rpId.len)); + CBOR_CHECK(cbor_encode_uint(&plain_map, 0x05)); + CBOR_CHECK(cbor_encode_byte_string(&plain_map, credential_metadata, metadata_len)); + CBOR_CHECK(cbor_encode_uint(&plain_map, 0x06)); + CBOR_CHECK(cbor_encode_byte_string(&plain_map, requested_id.data, requested_id.len)); CBOR_CHECK(cbor_encoder_close_container(&encoder, &plain_map)); size_t plain_len = cbor_encoder_get_buffer_size(&encoder, plain); - printf("Vault credential plain length: %zu\n", plain_len); - uint8_t envelope[MAX_CRED_ID_LENGTH + 256 + 28] = {0}; + printf("Vault credential plain length: %zu metadata length: %zu\n", plain_len, metadata_len); + uint8_t envelope[MAX_CRED_ID_LENGTH + VAULT_CREDENTIAL_METADATA_MAX + 28] = {0}; random_fill_buffer(BYTE_ARRAY(envelope, 12)); mbedtls_chachapoly_init(&chatx); mbedtls_chachapoly_setkey(&chatx, vault_channel_key); @@ -624,6 +729,7 @@ static int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { mbedtls_chachapoly_free(&chatx); credential_free(&credential); mbedtls_platform_zeroize(private_key, sizeof(private_key)); + mbedtls_platform_zeroize(credential_metadata, sizeof(credential_metadata)); mbedtls_platform_zeroize(plain, sizeof(plain)); if (ret != 0) { CBOR_ERROR(CTAP2_ERR_PROCESSING); @@ -645,6 +751,7 @@ static int cbor_vendor_generic(uint8_t cmd, const uint8_t *data, size_t len) { printf("CBOR vendor response size: %zu\n", resp_size); err: + CBOR_FREE_BYTE_STRING(requested_id); CBOR_FREE_BYTE_STRING(pinUvAuthParam); CBOR_FREE_BYTE_STRING(vendorParam);