From fccd269fe2fd00ae96b29d2d5ae3193635310ac9 Mon Sep 17 00:00:00 2001 From: Kevin Albertson Date: Mon, 1 Jul 2024 17:14:05 -0400 Subject: [PATCH] MONGOCRYPT-699 Omit `encryptionInformation` in compact if no range fields are used (#852) --- .../compact/success/encrypted-payload.json | 40 ++++++------- src/mongocrypt-ctx-encrypt.c | 27 +++++++-- test/data/compact/no-range/cmd.json | 1 + test/data/compact/no-range/collinfo.json | 49 ++++++++++++++++ .../no-range/encrypted-field-config-map.json | 47 +++++++++++++++ .../compact/no-range/encrypted-payload.json | 23 ++++++++ test/test-mongocrypt-compact.c | 58 +++++++++++++++++++ 7 files changed, 220 insertions(+), 25 deletions(-) create mode 100644 test/data/compact/no-range/cmd.json create mode 100644 test/data/compact/no-range/collinfo.json create mode 100644 test/data/compact/no-range/encrypted-field-config-map.json create mode 100644 test/data/compact/no-range/encrypted-payload.json diff --git a/bindings/python/test/data/compact/success/encrypted-payload.json b/bindings/python/test/data/compact/success/encrypted-payload.json index f861ac775..7eb7ea7cb 100644 --- a/bindings/python/test/data/compact/success/encrypted-payload.json +++ b/bindings/python/test/data/compact/success/encrypted-payload.json @@ -1,23 +1,23 @@ { - "compactStructuredEncryptionData": "test", - "compactionTokens": { - "nested.notindexed": { - "$binary": { - "base64": "27J6DZqcjkRzZ3lWEsxH7CsQHr4CZirrGmuPS8ZkRO0=", - "subType": "00" - } - }, - "nested.encrypted": { - "$binary": { - "base64": "SWO8WEoZ2r2Kx/muQKb7+COizy85nIIUFiHh4K9kcvA=", - "subType": "00" - } - }, - "encrypted": { - "$binary": { - "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", - "subType": "00" - } + "compactStructuredEncryptionData": "test", + "compactionTokens": { + "nested.notindexed": { + "$binary": { + "base64": "27J6DZqcjkRzZ3lWEsxH7CsQHr4CZirrGmuPS8ZkRO0=", + "subType": "00" } - } + }, + "nested.encrypted": { + "$binary": { + "base64": "SWO8WEoZ2r2Kx/muQKb7+COizy85nIIUFiHh4K9kcvA=", + "subType": "00" + } + }, + "encrypted": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } } diff --git a/src/mongocrypt-ctx-encrypt.c b/src/mongocrypt-ctx-encrypt.c index 0ca1aa9a7..dbc080439 100644 --- a/src/mongocrypt-ctx-encrypt.c +++ b/src/mongocrypt-ctx-encrypt.c @@ -1370,6 +1370,7 @@ typedef struct { static moe_result must_omit_encryptionInformation(const char *command_name, const bson_t *command, bool use_range_v2, + const mc_EncryptedFieldConfig_t *efc, mongocrypt_status_t *status) { // eligible_commands may omit encryptionInformation if the command does not // contain payloads requiring encryption. @@ -1382,10 +1383,23 @@ static moe_result must_omit_encryptionInformation(const char *command_name, BSON_ASSERT_PARAM(command_name); BSON_ASSERT_PARAM(command); + BSON_ASSERT_PARAM(efc); - // Before range v2, compact is a prohibited command. After, it must have encryption information. - if (!use_range_v2 && 0 == strcmp("compactStructuredEncryptionData", command_name)) { - return (moe_result){.ok = true, .must_omit = true}; + if (0 == strcmp("compactStructuredEncryptionData", command_name)) { + // `compactStructuredEncryptionData` is a special case: + // - Server 7.0 prohibits `encryptionInformation`. + // - Server 8.0 requires `encryptionInformation` if "range" fields are referenced. Otherwise ignores. + // Only send `encryptionInformation` if "range" fields are present to support both server versions. + bool uses_range_fields = false; + if (use_range_v2) { + for (const mc_EncryptedField_t *ef = efc->fields; ef != NULL; ef = ef->next) { + if (ef->supported_queries & SUPPORTS_RANGE_QUERIES) { + uses_range_fields = true; + break; + } + } + } + return (moe_result){.ok = true, .must_omit = !uses_range_fields}; } for (i = 0; i < sizeof(prohibited_commands) / sizeof(prohibited_commands[0]); i++) { @@ -1727,8 +1741,11 @@ static bool _fle2_finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) { } } - moe_result result = - must_omit_encryptionInformation(command_name, &converted, ctx->crypt->opts.use_range_v2, ctx->status); + moe_result result = must_omit_encryptionInformation(command_name, + &converted, + ctx->crypt->opts.use_range_v2, + &ectx->efc, + ctx->status); if (!result.ok) { bson_destroy(&converted); bson_destroy(deleteTokens); diff --git a/test/data/compact/no-range/cmd.json b/test/data/compact/no-range/cmd.json new file mode 100644 index 000000000..ddf4cfde1 --- /dev/null +++ b/test/data/compact/no-range/cmd.json @@ -0,0 +1 @@ +{ "compactStructuredEncryptionData": "test" } diff --git a/test/data/compact/no-range/collinfo.json b/test/data/compact/no-range/collinfo.json new file mode 100644 index 000000000..262768aa1 --- /dev/null +++ b/test/data/compact/no-range/collinfo.json @@ -0,0 +1,49 @@ +{ + "options": { + "encryptedFields": { + "escCollection": "esc", + "eccCollection": "ecc", + "ecocCollection": "ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encrypted", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": 0 + } + }, + { + "keyId": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "nested.encrypted", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": 0 + } + }, + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEw==", + "subType": "04" + } + }, + "path": "nested.notindexed", + "bsonType": "string" + } + ] + } + } +} diff --git a/test/data/compact/no-range/encrypted-field-config-map.json b/test/data/compact/no-range/encrypted-field-config-map.json new file mode 100644 index 000000000..f183f7d30 --- /dev/null +++ b/test/data/compact/no-range/encrypted-field-config-map.json @@ -0,0 +1,47 @@ +{ + "db.test": { + "escCollection": "esc", + "eccCollection": "ecc", + "ecocCollection": "ecoc", + "fields": [ + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "encrypted", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": 0 + } + }, + { + "keyId": { + "$binary": { + "base64": "q83vqxI0mHYSNBI0VniQEg==", + "subType": "04" + } + }, + "path": "nested.encrypted", + "bsonType": "string", + "queries": { + "queryType": "equality", + "contention": 0 + } + }, + { + "keyId": { + "$binary": { + "base64": "EjRWeBI0mHYSNBI0VniQEw==", + "subType": "04" + } + }, + "path": "nested.notindexed", + "bsonType": "string" + } + ] + } +} diff --git a/test/data/compact/no-range/encrypted-payload.json b/test/data/compact/no-range/encrypted-payload.json new file mode 100644 index 000000000..f861ac775 --- /dev/null +++ b/test/data/compact/no-range/encrypted-payload.json @@ -0,0 +1,23 @@ +{ + "compactStructuredEncryptionData": "test", + "compactionTokens": { + "nested.notindexed": { + "$binary": { + "base64": "27J6DZqcjkRzZ3lWEsxH7CsQHr4CZirrGmuPS8ZkRO0=", + "subType": "00" + } + }, + "nested.encrypted": { + "$binary": { + "base64": "SWO8WEoZ2r2Kx/muQKb7+COizy85nIIUFiHh4K9kcvA=", + "subType": "00" + } + }, + "encrypted": { + "$binary": { + "base64": "noN+05JsuO1oDg59yypIGj45i+eFH6HOTXOPpeZ//Mk=", + "subType": "00" + } + } + } +} diff --git a/test/test-mongocrypt-compact.c b/test/test-mongocrypt-compact.c index 6f19a8ce6..255f709ce 100644 --- a/test/test-mongocrypt-compact.c +++ b/test/test-mongocrypt-compact.c @@ -81,6 +81,64 @@ static void _test_compact_success(_mongocrypt_tester_t *tester) { mongocrypt_destroy(crypt); } } + + // Test `compactStructuredEncryptionData` without range fields omits encryptionInformation. + // This is a regression test for MONGOCRYPT-699. + for (int use_range_v2 = 0; use_range_v2 <= 1; use_range_v2++) { + datapath[nullb] = 0; + strcat(datapath, "no-range/"); + strcpy(cmdfile, datapath); + strcat(cmdfile, "cmd.json"); + strcpy(collfile, datapath); + strcat(collfile, "collinfo.json"); + strcpy(payloadfile, datapath); + strcat(payloadfile, "encrypted-payload.json"); // Expect same result regardless of range v2. + + mongocrypt_t *crypt; + mongocrypt_ctx_t *ctx; + + crypt = + _mongocrypt_tester_mongocrypt(use_range_v2 ? TESTER_MONGOCRYPT_WITH_RANGE_V2 : TESTER_MONGOCRYPT_DEFAULT); + ctx = mongocrypt_ctx_new(crypt); + + ASSERT_OK(mongocrypt_ctx_encrypt_init(ctx, "db", -1, TEST_FILE(cmdfile)), ctx); + + ASSERT_STATE_EQUAL(mongocrypt_ctx_state(ctx), MONGOCRYPT_CTX_NEED_MONGO_COLLINFO); + { + ASSERT_OK(mongocrypt_ctx_mongo_feed(ctx, TEST_FILE(collfile)), ctx); + ASSERT_OK(mongocrypt_ctx_mongo_done(ctx), ctx); + } + + ASSERT_STATE_EQUAL(mongocrypt_ctx_state(ctx), MONGOCRYPT_CTX_NEED_MONGO_KEYS); + { + ASSERT_OK(mongocrypt_ctx_mongo_feed(ctx, + TEST_FILE("./test/data/keys/" + "12345678123498761234123456789012-local-document.json")), + ctx); + ASSERT_OK(mongocrypt_ctx_mongo_feed(ctx, + TEST_FILE("./test/data/keys/" + "ABCDEFAB123498761234123456789012-local-document.json")), + ctx); + ASSERT_OK(mongocrypt_ctx_mongo_feed(ctx, + TEST_FILE("./test/data/keys/" + "12345678123498761234123456789013-local-document.json")), + ctx); + ASSERT_OK(mongocrypt_ctx_mongo_done(ctx), ctx); + } + + ASSERT_STATE_EQUAL(mongocrypt_ctx_state(ctx), MONGOCRYPT_CTX_READY); + { + mongocrypt_binary_t *out = mongocrypt_binary_new(); + ASSERT_OK(mongocrypt_ctx_finalize(ctx, out), ctx); + ASSERT_MONGOCRYPT_BINARY_EQUAL_BSON(TEST_FILE(payloadfile), out); + mongocrypt_binary_destroy(out); + } + + ASSERT_STATE_EQUAL(mongocrypt_ctx_state(ctx), MONGOCRYPT_CTX_DONE); + + mongocrypt_ctx_destroy(ctx); + mongocrypt_destroy(crypt); + } } static void _test_compact_nonlocal_kms(_mongocrypt_tester_t *tester) {