Skip to content

Commit 74fc7d6

Browse files
authored
MONGOCRYPT-772 Add strEncodeVersion to the encrypted field config (#949)
1 parent 46cc64b commit 74fc7d6

File tree

50 files changed

+1951
-72
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

50 files changed

+1951
-72
lines changed

src/mc-efc-private.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ typedef struct _mc_EncryptedField_t {
4646
* for the server IDL definition of EncryptedFieldConfig. */
4747
typedef struct {
4848
mc_EncryptedField_t *fields;
49+
uint8_t str_encode_version;
4950
} mc_EncryptedFieldConfig_t;
5051

5152
/* mc_EncryptedFieldConfig_parse parses a subset of the fields from @efc_bson

src/mc-efc.c

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
#include "mlib/str.h"
2020
#include "mongocrypt-private.h"
2121
#include "mongocrypt-util-private.h" // mc_iter_document_as_bson
22+
#include <stdint.h>
2223

2324
static bool _parse_query_type_string(const char *queryType, supported_query_type_flags *out) {
2425
BSON_ASSERT_PARAM(queryType);
@@ -175,13 +176,14 @@ bool mc_EncryptedFieldConfig_parse(mc_EncryptedFieldConfig_t *efc,
175176
return false;
176177
}
177178
if (!BSON_ITER_HOLDS_ARRAY(&iter)) {
178-
CLIENT_ERR("expected 'fields' to be type array, got: %d", bson_iter_type(&iter));
179+
CLIENT_ERR("expected 'fields' to be type array, got: %s", mc_bson_type_to_string(bson_iter_type(&iter)));
179180
return false;
180181
}
181182
if (!bson_iter_recurse(&iter, &iter)) {
182183
CLIENT_ERR("unable to recurse into encrypted_field_config 'fields'");
183184
return false;
184185
}
186+
supported_query_type_flags all_supported_queries = SUPPORTS_NO_QUERIES;
185187
while (bson_iter_next(&iter)) {
186188
bson_t field;
187189
if (!mc_iter_document_as_bson(&iter, &field, status)) {
@@ -190,6 +192,32 @@ bool mc_EncryptedFieldConfig_parse(mc_EncryptedFieldConfig_t *efc,
190192
if (!_parse_field(efc, &field, status, use_range_v2)) {
191193
return false;
192194
}
195+
// The first element of efc->fields contains the newly parsed field.
196+
all_supported_queries |= efc->fields->supported_queries;
197+
}
198+
199+
if (!bson_iter_init_find(&iter, efc_bson, "strEncodeVersion")) {
200+
if (all_supported_queries
201+
& (SUPPORTS_SUBSTRING_PREVIEW_QUERIES | SUPPORTS_SUFFIX_PREVIEW_QUERIES
202+
| SUPPORTS_PREFIX_PREVIEW_QUERIES)) {
203+
// Has at least one text search query type, set to latest by default.
204+
efc->str_encode_version = LATEST_STR_ENCODE_VERSION;
205+
} else {
206+
// Set to 0 to indicate no text search, and thus no strEncodeVersion needed.
207+
efc->str_encode_version = 0;
208+
}
209+
} else {
210+
if (!BSON_ITER_HOLDS_INT32(&iter)) {
211+
CLIENT_ERR("expected 'strEncodeVersion' to be type int32, got: %s",
212+
mc_bson_type_to_string(bson_iter_type(&iter)));
213+
return false;
214+
}
215+
int32_t version = bson_iter_int32(&iter);
216+
if (version > LATEST_STR_ENCODE_VERSION || version < MIN_STR_ENCODE_VERSION) {
217+
CLIENT_ERR("'strEncodeVersion' of %" PRId32 " is not supported", version);
218+
return false;
219+
}
220+
efc->str_encode_version = (uint8_t)version;
193221
}
194222
return true;
195223
}

src/mongocrypt-ctx-encrypt.c

Lines changed: 120 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,13 @@
2222
#include "mongocrypt-ctx-private.h"
2323
#include "mongocrypt-key-broker-private.h"
2424
#include "mongocrypt-marking-private.h"
25+
#include "mongocrypt-private.h"
2526
#include "mongocrypt-traverse-util-private.h"
2627
#include "mongocrypt-util-private.h" // mc_iter_document_as_bson
2728
#include "mongocrypt.h"
2829

2930
/* _fle2_append_encryptedFieldConfig copies encryptedFieldConfig and applies
30-
* default state collection names for escCollection, and ecocCollection if required. */
31+
* default state collection names for escCollection and ecocCollection, and default strEncodeVersion, if required. */
3132
static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx,
3233
bson_t *dst,
3334
bson_t *encryptedFieldConfig,
@@ -36,7 +37,9 @@ static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx,
3637
bson_iter_t iter;
3738
bool has_escCollection = false;
3839
bool has_ecocCollection = false;
40+
bool has_strEncodeVersion = false;
3941

42+
BSON_ASSERT_PARAM(ctx);
4043
BSON_ASSERT_PARAM(dst);
4144
BSON_ASSERT_PARAM(encryptedFieldConfig);
4245
BSON_ASSERT_PARAM(target_coll);
@@ -53,6 +56,9 @@ static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx,
5356
if (strcmp(bson_iter_key(&iter), "ecocCollection") == 0) {
5457
has_ecocCollection = true;
5558
}
59+
if (strcmp(bson_iter_key(&iter), "strEncodeVersion") == 0) {
60+
has_strEncodeVersion = true;
61+
}
5662
if (!BSON_APPEND_VALUE(dst, bson_iter_key(&iter), bson_iter_value(&iter))) {
5763
CLIENT_ERR("unable to append field: %s", bson_iter_key(&iter));
5864
return false;
@@ -77,6 +83,18 @@ static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx,
7783
}
7884
bson_free(default_ecocCollection);
7985
}
86+
if (!has_strEncodeVersion) {
87+
_mongocrypt_ctx_encrypt_t *ectx = (_mongocrypt_ctx_encrypt_t *)ctx;
88+
// Check str_encode_version on the EncryptedFieldConfig object to see whether we should append or not. 0
89+
// indicates that there was no text search query in the EFC and the strEncodeVersion was not set on the EFC; in
90+
// this case, we should not append strEncodeVersion, as mongocryptd/mongod may not understand it.
91+
if (ectx->efc.str_encode_version != 0) {
92+
if (!BSON_APPEND_INT32(dst, "strEncodeVersion", (int32_t)ectx->efc.str_encode_version)) {
93+
CLIENT_ERR("unable to append strEncodeVersion");
94+
return false;
95+
}
96+
}
97+
}
8098
return true;
8199
}
82100

@@ -1433,6 +1451,100 @@ _fle2_strip_encryptionInformation(const char *cmd_name, bson_t *cmd /* in and ou
14331451
return ok;
14341452
}
14351453

1454+
/*
1455+
* Checks the "encryptedFields.strEncodeVersion" field for "create" commands for validity, and sets it to the default if
1456+
* it does not exist.
1457+
*/
1458+
static bool _fle2_fixup_encryptedFields_strEncodeVersion(const char *cmd_name,
1459+
bson_t *cmd /* in and out */,
1460+
const mc_EncryptedFieldConfig_t *efc,
1461+
mongocrypt_status_t *status) {
1462+
BSON_ASSERT_PARAM(cmd_name);
1463+
BSON_ASSERT_PARAM(cmd);
1464+
BSON_ASSERT_PARAM(efc);
1465+
1466+
if (0 == strcmp(cmd_name, "create")) {
1467+
bson_iter_t ef_iter;
1468+
if (!bson_iter_init_find(&ef_iter, cmd, "encryptedFields")) {
1469+
// No encryptedFields, nothing to check or fix
1470+
return true;
1471+
}
1472+
if (!BSON_ITER_HOLDS_DOCUMENT(&ef_iter)) {
1473+
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Expected encryptedFields to be type obj, got: %s",
1474+
mc_bson_type_to_string(bson_iter_type(&ef_iter)));
1475+
return false;
1476+
}
1477+
bson_iter_t sev_iter;
1478+
if (!bson_iter_recurse(&ef_iter, &sev_iter)) {
1479+
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to recurse bson_iter");
1480+
return false;
1481+
}
1482+
if (!bson_iter_find(&sev_iter, "strEncodeVersion")) {
1483+
if (efc->str_encode_version == 0) {
1484+
// Unset StrEncodeVersion matches the EFC, nothing to fix.
1485+
return true;
1486+
}
1487+
1488+
// No strEncodeVersion and the EFC has a nonzero strEncodeVersion, add it.
1489+
// Initialize the new cmd object from the old one, excluding encryptedFields.
1490+
bson_t fixed = BSON_INITIALIZER;
1491+
bson_copy_to_excluding_noinit(cmd, &fixed, "encryptedFields", NULL);
1492+
1493+
// Recurse the original encryptedFields and copy everything over.
1494+
bson_iter_t copy_iter;
1495+
if (!bson_iter_recurse(&ef_iter, &copy_iter)) {
1496+
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to recurse bson_iter");
1497+
goto fail;
1498+
}
1499+
bson_t fixed_ef;
1500+
if (!BSON_APPEND_DOCUMENT_BEGIN(&fixed, "encryptedFields", &fixed_ef)) {
1501+
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to start appending encryptedFields");
1502+
goto fail;
1503+
}
1504+
while (bson_iter_next(&copy_iter)) {
1505+
if (!bson_append_iter(&fixed_ef, NULL, 0, &copy_iter)) {
1506+
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to copy element");
1507+
goto fail;
1508+
}
1509+
}
1510+
1511+
// Add the EFC's strEncodeVersion to encryptedFields.
1512+
if (!BSON_APPEND_INT32(&fixed_ef, "strEncodeVersion", efc->str_encode_version)) {
1513+
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to append strEncodeVersion");
1514+
goto fail;
1515+
}
1516+
if (!bson_append_document_end(&fixed, &fixed_ef)) {
1517+
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to finish appending encryptedFields");
1518+
goto fail;
1519+
}
1520+
1521+
bson_destroy(cmd);
1522+
if (!bson_steal(cmd, &fixed)) {
1523+
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to steal BSON");
1524+
goto fail;
1525+
}
1526+
return true;
1527+
fail:
1528+
bson_destroy(&fixed);
1529+
return false;
1530+
} else {
1531+
// Check strEncodeVersion for match against EFC
1532+
if (!BSON_ITER_HOLDS_INT32(&sev_iter)) {
1533+
CLIENT_ERR("expected 'strEncodeVersion' to be type int32, got: %d", bson_iter_type(&sev_iter));
1534+
return false;
1535+
}
1536+
int32_t version = bson_iter_int32(&sev_iter);
1537+
if (version != efc->str_encode_version) {
1538+
CLIENT_ERR("'strEncodeVersion' of %d does not match efc->str_encode_version of %d",
1539+
version,
1540+
efc->str_encode_version);
1541+
return false;
1542+
}
1543+
}
1544+
}
1545+
return true;
1546+
}
1547+
14361548
/* Process a call to mongocrypt_ctx_finalize when an encryptedFieldConfig is
14371549
* associated with the command. */
14381550
static bool _fle2_finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) {
@@ -1505,6 +1617,13 @@ static bool _fle2_finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) {
15051617
return _mongocrypt_ctx_fail(ctx);
15061618
}
15071619

1620+
/* If this is a create command, append the encryptedFields.strEncodeVersion field if it's necessary. If the field
1621+
* already exists, check it against the EFC for correctness. */
1622+
if (!_fle2_fixup_encryptedFields_strEncodeVersion(command_name, &converted, &ectx->efc, ctx->status)) {
1623+
bson_destroy(&converted);
1624+
return _mongocrypt_ctx_fail(ctx);
1625+
}
1626+
15081627
/* Append a new 'encryptionInformation'. */
15091628
if (!result.must_omit && !ectx->used_empty_encryptedFields) {
15101629
if (!_fle2_insert_encryptionInformation(ctx,

src/mongocrypt-private.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,10 @@
4949

5050
#define MONGOCRYPT_DATA_AND_LEN(x) ((uint8_t *)x), (sizeof(x) / sizeof((x)[0]) - 1)
5151

52+
#define LATEST_STR_ENCODE_VERSION 1
53+
54+
#define MIN_STR_ENCODE_VERSION 1
55+
5256
/* TODO: Move these to mongocrypt-log-private.h? */
5357
const char *tmp_json(const bson_t *bson);
5458

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"escCollection": "fle2.basic.esc",
3+
"ecocCollection": "fle2.basic.ecoc",
4+
"fields": [
5+
{
6+
"keyId": {
7+
"$binary": {
8+
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
9+
"subType": "04"
10+
}
11+
},
12+
"path": "firstName",
13+
"bsonType": "string",
14+
"queries": {
15+
"queryType": "equality",
16+
"contention": {
17+
"$numberLong": "0"
18+
}
19+
}
20+
}
21+
],
22+
"strEncodeVersion": 99
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"escCollection": "fle2.basic.esc",
3+
"ecocCollection": "fle2.basic.ecoc",
4+
"fields": [
5+
{
6+
"keyId": {
7+
"$binary": {
8+
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
9+
"subType": "04"
10+
}
11+
},
12+
"path": "firstName",
13+
"bsonType": "string",
14+
"queries": {
15+
"queryType": "equality",
16+
"contention": {
17+
"$numberLong": "0"
18+
}
19+
}
20+
}
21+
],
22+
"strEncodeVersion": 1
23+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"escCollection": "fle2.basic.esc",
3+
"eccCollection": "fle2.basic.ecc",
4+
"ecocCollection": "fle2.basic.ecoc",
5+
"fields": [
6+
{
7+
"keyId": {
8+
"$binary": {
9+
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
10+
"subType": "04"
11+
}
12+
},
13+
"path": "firstName",
14+
"bsonType": "string",
15+
"queries": {
16+
"queryType": "substringPreview",
17+
"contention": {
18+
"$numberLong": "0"
19+
}
20+
}
21+
},
22+
{
23+
"keyId": {
24+
"$binary": {
25+
"base64": "q83vqxI0mHYSNBI0VniQEg==",
26+
"subType": "04"
27+
}
28+
},
29+
"path": "lastName",
30+
"bsonType": "string",
31+
"queries": [
32+
{
33+
"queryType": "suffixPreview",
34+
"contention": {
35+
"$numberLong": "0"
36+
}
37+
},
38+
{
39+
"queryType": "prefixPreview",
40+
"contention": {
41+
"$numberLong": "0"
42+
}
43+
}
44+
]
45+
}
46+
],
47+
"strEncodeVersion": 99
48+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"escCollection": "fle2.basic.esc",
3+
"eccCollection": "fle2.basic.ecc",
4+
"ecocCollection": "fle2.basic.ecoc",
5+
"fields": [
6+
{
7+
"keyId": {
8+
"$binary": {
9+
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
10+
"subType": "04"
11+
}
12+
},
13+
"path": "firstName",
14+
"bsonType": "string",
15+
"queries": {
16+
"queryType": "substringPreview",
17+
"contention": {
18+
"$numberLong": "0"
19+
}
20+
}
21+
},
22+
{
23+
"keyId": {
24+
"$binary": {
25+
"base64": "q83vqxI0mHYSNBI0VniQEg==",
26+
"subType": "04"
27+
}
28+
},
29+
"path": "lastName",
30+
"bsonType": "string",
31+
"queries": [
32+
{
33+
"queryType": "suffixPreview",
34+
"contention": {
35+
"$numberLong": "0"
36+
}
37+
},
38+
{
39+
"queryType": "prefixPreview",
40+
"contention": {
41+
"$numberLong": "0"
42+
}
43+
}
44+
]
45+
}
46+
],
47+
"strEncodeVersion": 1
48+
}

0 commit comments

Comments
 (0)