Skip to content

Commit b718216

Browse files
MONGOCRYPT-824 allow setting deterministic contention factor during FLE2 field encryption
1 parent 7387a6a commit b718216

4 files changed

Lines changed: 129 additions & 7 deletions

File tree

src/mongocrypt-marking.c

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,27 @@ static bool _mongocrypt_fle2_placeholder_common(_mongocrypt_key_broker_t *kb,
728728
return false;
729729
}
730730

731+
static bool _fle2_choose_contention_factor(mongocrypt_t *crypt,
732+
int64_t maxContentionFactor,
733+
int64_t *out,
734+
mongocrypt_status_t *status) {
735+
BSON_ASSERT_PARAM(crypt);
736+
BSON_ASSERT_PARAM(out);
737+
if (crypt->opts.contention_factor_fn) {
738+
if (!crypt->opts.contention_factor_fn(maxContentionFactor + 1, out)) {
739+
CLIENT_ERR("contention_factor_fn failed");
740+
return false;
741+
}
742+
if (*out < 0 || *out > maxContentionFactor) {
743+
CLIENT_ERR("chosen contentionFactor out of range");
744+
return false;
745+
}
746+
return true;
747+
}
748+
749+
return _mongocrypt_random_int64(crypt->crypto, maxContentionFactor + 1, out, status);
750+
}
751+
731752
// Shared implementation for insert/update and insert/update ForRange (v2)
732753
static bool _mongocrypt_fle2_placeholder_to_insert_update_common(_mongocrypt_key_broker_t *kb,
733754
mc_FLE2InsertUpdatePayloadV2_t *out,
@@ -749,9 +770,12 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_common(_mongocrypt_key
749770

750771
out->contentionFactor = 0; // k
751772
if (placeholder->maxContentionFactor > 0) {
752-
/* Choose a random contentionFactor in the inclusive range [0,
773+
/* Choose a contentionFactor in the inclusive range [0,
753774
* placeholder->maxContentionFactor] */
754-
if (!_mongocrypt_random_int64(crypto, placeholder->maxContentionFactor + 1, &out->contentionFactor, status)) {
775+
if (!_fle2_choose_contention_factor(kb->crypt,
776+
placeholder->maxContentionFactor,
777+
&out->contentionFactor,
778+
status)) {
755779
goto fail;
756780
}
757781
}
@@ -1545,12 +1569,12 @@ static bool _mongocrypt_fle2_placeholder_to_insert_update_ciphertextForTextSearc
15451569
// k
15461570
payload.contentionFactor = 0;
15471571
if (placeholder->maxContentionFactor > 0) {
1548-
/* Choose a random contentionFactor in the inclusive range [0,
1572+
/* Choose a contentionFactor in the inclusive range [0,
15491573
* placeholder->maxContentionFactor] */
1550-
if (!_mongocrypt_random_int64(kb->crypt->crypto,
1551-
placeholder->maxContentionFactor + 1,
1552-
&payload.contentionFactor,
1553-
status)) {
1574+
if (!_fle2_choose_contention_factor(kb->crypt,
1575+
placeholder->maxContentionFactor,
1576+
&payload.contentionFactor,
1577+
status)) {
15541578
goto fail;
15551579
}
15561580
}

src/mongocrypt-opts-private.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,8 @@ typedef struct {
8484
mc_array_t named_mut;
8585
} _mongocrypt_opts_kms_providers_t;
8686

87+
typedef bool (*_mongocrypt_contention_factor_fn)(int64_t exclusive_upper_bound, int64_t *out);
88+
8789
void _mongocrypt_opts_kms_providers_init(_mongocrypt_opts_kms_providers_t *kms_providers);
8890

8991
bool _mongocrypt_parse_kms_providers(mongocrypt_binary_t *kms_providers_definition,
@@ -105,6 +107,9 @@ typedef struct {
105107
mongocrypt_hmac_fn sign_rsaes_pkcs1_v1_5;
106108
void *sign_ctx;
107109

110+
// Support overriding random contentionFactor (when null, default) with a custom setter.
111+
_mongocrypt_contention_factor_fn contention_factor_fn;
112+
108113
/// Keep an array of search paths for finding the crypt_shared library
109114
/// during mongocrypt_init()
110115
int n_crypt_shared_lib_search_paths;
@@ -138,6 +143,10 @@ bool _mongocrypt_opts_kms_providers_validate(_mongocrypt_opts_t *opts,
138143
_mongocrypt_opts_kms_providers_t *kms_providers,
139144
mongocrypt_status_t *status) MONGOCRYPT_WARN_UNUSED_RESULT;
140145

146+
// For testing only: register a custom contention factor function with crypt.
147+
void _mongocrypt_opts_set_contention_factor_fn(mongocrypt_t *crypt,
148+
_mongocrypt_contention_factor_fn contention_factor_fn);
149+
141150
/*
142151
* Parse an optional UTF-8 value from BSON.
143152
* @dotkey may be a dot separated key like: "a.b.c".

src/mongocrypt-opts.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,12 @@ bool _mongocrypt_opts_kms_providers_lookup(const _mongocrypt_opts_kms_providers_
320320
return false;
321321
}
322322

323+
void _mongocrypt_opts_set_contention_factor_fn(mongocrypt_t *crypt,
324+
_mongocrypt_contention_factor_fn contention_factor_fn) {
325+
BSON_ASSERT_PARAM(crypt);
326+
crypt->opts.contention_factor_fn = contention_factor_fn;
327+
}
328+
323329
bool _mongocrypt_parse_optional_utf8(const bson_t *bson, const char *dotkey, char **out, mongocrypt_status_t *status) {
324330
bson_iter_t iter;
325331
bson_iter_t child;

test/test-mongocrypt-ctx-encrypt.c

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
#include <mongocrypt-marking-private.h>
1818

1919
#include "kms_message/kms_b64.h"
20+
#include "mc-fle2-insert-update-payload-private-v2.h"
2021
#include "mongocrypt-binary-private.h"
2122
#include "mongocrypt-crypto-private.h" // MONGOCRYPT_KEY_LEN
2223
#include "mongocrypt.h"
@@ -5976,6 +5977,87 @@ static void _test_lookup(_mongocrypt_tester_t *tester) {
59765977
#undef TF
59775978
}
59785979

5980+
static bool _deterministic_contention(int64_t exclusive_upper_bound, int64_t *out) {
5981+
ASSERT(out);
5982+
(void)exclusive_upper_bound;
5983+
*out = 1;
5984+
return true;
5985+
}
5986+
5987+
static void _test_deterministic_contention(_mongocrypt_tester_t *tester) {
5988+
mongocrypt_status_t *const status = mongocrypt_status_new();
5989+
5990+
mongocrypt_t *const crypt = _mongocrypt_tester_mongocrypt(TESTER_MONGOCRYPT_SKIP_INIT);
5991+
ASSERT_OK(mongocrypt_init(crypt), crypt);
5992+
_mongocrypt_opts_set_contention_factor_fn(crypt, &_deterministic_contention); // register deterministic fn wth crypt
5993+
5994+
// Expect the callback returns 1.
5995+
{
5996+
int64_t out;
5997+
ASSERT(crypt->opts.contention_factor_fn(4, &out));
5998+
ASSERT_CMPINT64(out, ==, 1);
5999+
}
6000+
6001+
// Start explicit encryption:
6002+
mongocrypt_ctx_t *const ctx = mongocrypt_ctx_new(crypt);
6003+
6004+
// Use the QE algorithm "Indexed", which uses a contention factor:
6005+
ASSERT_OK(mongocrypt_ctx_setopt_algorithm(ctx, MONGOCRYPT_ALGORITHM_INDEXED_STR, -1), ctx);
6006+
6007+
{
6008+
_mongocrypt_buffer_t keyABC_id;
6009+
_mongocrypt_buffer_copy_from_hex(&keyABC_id, "ABCDEFAB123498761234123456789012");
6010+
ASSERT_OK(mongocrypt_ctx_setopt_key_id(ctx, _mongocrypt_buffer_as_binary(&keyABC_id)), ctx);
6011+
_mongocrypt_buffer_cleanup(&keyABC_id);
6012+
}
6013+
6014+
// Set max contention factor of 4:
6015+
ASSERT_OK(mongocrypt_ctx_setopt_contention_factor(ctx, 4), ctx);
6016+
6017+
// Encrypt the value 123:
6018+
ASSERT_OK(mongocrypt_ctx_explicit_encrypt_init(ctx, TEST_BSON("{'v': 123}")), ctx);
6019+
6020+
// Expect key is needed:
6021+
ASSERT_STATE_EQUAL(mongocrypt_ctx_state(ctx), MONGOCRYPT_CTX_NEED_MONGO_KEYS);
6022+
{
6023+
mongocrypt_binary_t *const keyABC =
6024+
TEST_FILE("./test/data/keys/ABCDEFAB123498761234123456789012-local-document.json");
6025+
ASSERT_OK(mongocrypt_ctx_mongo_feed(ctx, keyABC), ctx);
6026+
ASSERT_OK(mongocrypt_ctx_mongo_done(ctx), ctx);
6027+
}
6028+
6029+
// Expect ready to encrypt:
6030+
ASSERT_STATE_EQUAL(mongocrypt_ctx_state(ctx), MONGOCRYPT_CTX_READY);
6031+
{
6032+
mongocrypt_binary_t *got = mongocrypt_binary_new();
6033+
bool ret = mongocrypt_ctx_finalize(ctx, got);
6034+
ASSERT_OK(ret, ctx);
6035+
6036+
// The result is represented in a BSON document: { "v": <binary> }.
6037+
// Do the long-winded conversion: binary -> BSON -> buffer:
6038+
_mongocrypt_buffer_t got_buffer;
6039+
{
6040+
bson_t got_bson;
6041+
ASSERT(_mongocrypt_binary_to_bson(got, &got_bson));
6042+
bson_iter_t got_iter;
6043+
ASSERT(bson_iter_init_find(&got_iter, &got_bson, "v"));
6044+
ASSERT(_mongocrypt_buffer_from_binary_iter(&got_buffer, &got_iter));
6045+
}
6046+
6047+
// Check the contention factor in the resulting payload:
6048+
mc_FLE2InsertUpdatePayloadV2_t got_payload;
6049+
mc_FLE2InsertUpdatePayloadV2_init(&got_payload);
6050+
ASSERT_OK_STATUS(mc_FLE2InsertUpdatePayloadV2_parse(&got_payload, &got_buffer, status), status);
6051+
ASSERT_CMPINT64(got_payload.contentionFactor, ==, 1); // Set by _deterministic_contention.
6052+
mc_FLE2InsertUpdatePayloadV2_cleanup(&got_payload);
6053+
mongocrypt_binary_destroy(got);
6054+
}
6055+
6056+
mongocrypt_ctx_destroy(ctx);
6057+
mongocrypt_destroy(crypt);
6058+
mongocrypt_status_destroy(status);
6059+
}
6060+
59796061
void _mongocrypt_tester_install_ctx_encrypt(_mongocrypt_tester_t *tester) {
59806062
INSTALL_TEST(_test_explicit_encrypt_init);
59816063
INSTALL_TEST(_test_encrypt_init);
@@ -6074,4 +6156,5 @@ void _mongocrypt_tester_install_ctx_encrypt(_mongocrypt_tester_t *tester) {
60746156
INSTALL_TEST(_test_fle2_encrypted_fields_with_unmatching_str_encode_version);
60756157
INSTALL_TEST(_test_fle2_collinfo_with_bad_str_encode_version);
60766158
INSTALL_TEST(_test_lookup);
6159+
INSTALL_TEST(_test_deterministic_contention);
60776160
}

0 commit comments

Comments
 (0)