Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/mc-efc-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ typedef struct _mc_EncryptedField_t {
* for the server IDL definition of EncryptedFieldConfig. */
typedef struct {
mc_EncryptedField_t *fields;
uint8_t str_encode_version;
} mc_EncryptedFieldConfig_t;

/* mc_EncryptedFieldConfig_parse parses a subset of the fields from @efc_bson
Expand Down
30 changes: 29 additions & 1 deletion src/mc-efc.c
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "mlib/str.h"
#include "mongocrypt-private.h"
#include "mongocrypt-util-private.h" // mc_iter_document_as_bson
#include <stdint.h>

static bool _parse_query_type_string(const char *queryType, supported_query_type_flags *out) {
BSON_ASSERT_PARAM(queryType);
Expand Down Expand Up @@ -175,13 +176,14 @@ bool mc_EncryptedFieldConfig_parse(mc_EncryptedFieldConfig_t *efc,
return false;
}
if (!BSON_ITER_HOLDS_ARRAY(&iter)) {
CLIENT_ERR("expected 'fields' to be type array, got: %d", bson_iter_type(&iter));
CLIENT_ERR("expected 'fields' to be type array, got: %s", mc_bson_type_to_string(bson_iter_type(&iter)));
return false;
}
if (!bson_iter_recurse(&iter, &iter)) {
CLIENT_ERR("unable to recurse into encrypted_field_config 'fields'");
return false;
}
supported_query_type_flags all_supported_queries = SUPPORTS_NO_QUERIES;
while (bson_iter_next(&iter)) {
bson_t field;
if (!mc_iter_document_as_bson(&iter, &field, status)) {
Expand All @@ -190,6 +192,32 @@ bool mc_EncryptedFieldConfig_parse(mc_EncryptedFieldConfig_t *efc,
if (!_parse_field(efc, &field, status, use_range_v2)) {
return false;
}
// The first element of efc->fields contains the newly parsed field.
all_supported_queries |= efc->fields->supported_queries;
}

if (!bson_iter_init_find(&iter, efc_bson, "strEncodeVersion")) {
if (all_supported_queries
& (SUPPORTS_SUBSTRING_PREVIEW_QUERIES | SUPPORTS_SUFFIX_PREVIEW_QUERIES
| SUPPORTS_PREFIX_PREVIEW_QUERIES)) {
// Has at least one text search query type, set to latest by default.
efc->str_encode_version = LATEST_STR_ENCODE_VERSION;
} else {
// Set to 0 to indicate no text search, and thus no strEncodeVersion needed.
efc->str_encode_version = 0;
}
} else {
if (!BSON_ITER_HOLDS_INT32(&iter)) {
CLIENT_ERR("expected 'strEncodeVersion' to be type int32, got: %s",
mc_bson_type_to_string(bson_iter_type(&iter)));
return false;
}
int32_t version = bson_iter_int32(&iter);
if (version > LATEST_STR_ENCODE_VERSION || version < MIN_STR_ENCODE_VERSION) {
CLIENT_ERR("'strEncodeVersion' of %" PRId32 " is not supported", version);
return false;
}
efc->str_encode_version = (uint8_t)version;
}
return true;
}
Expand Down
121 changes: 120 additions & 1 deletion src/mongocrypt-ctx-encrypt.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,13 @@
#include "mongocrypt-ctx-private.h"
#include "mongocrypt-key-broker-private.h"
#include "mongocrypt-marking-private.h"
#include "mongocrypt-private.h"
#include "mongocrypt-traverse-util-private.h"
#include "mongocrypt-util-private.h" // mc_iter_document_as_bson
#include "mongocrypt.h"

/* _fle2_append_encryptedFieldConfig copies encryptedFieldConfig and applies
* default state collection names for escCollection, and ecocCollection if required. */
* default state collection names for escCollection and ecocCollection, and default strEncodeVersion, if required. */
static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx,
bson_t *dst,
bson_t *encryptedFieldConfig,
Expand All @@ -36,7 +37,9 @@ static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx,
bson_iter_t iter;
bool has_escCollection = false;
bool has_ecocCollection = false;
bool has_strEncodeVersion = false;

BSON_ASSERT_PARAM(ctx);
BSON_ASSERT_PARAM(dst);
BSON_ASSERT_PARAM(encryptedFieldConfig);
BSON_ASSERT_PARAM(target_coll);
Expand All @@ -53,6 +56,9 @@ static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx,
if (strcmp(bson_iter_key(&iter), "ecocCollection") == 0) {
has_ecocCollection = true;
}
if (strcmp(bson_iter_key(&iter), "strEncodeVersion") == 0) {
has_strEncodeVersion = true;
}
if (!BSON_APPEND_VALUE(dst, bson_iter_key(&iter), bson_iter_value(&iter))) {
CLIENT_ERR("unable to append field: %s", bson_iter_key(&iter));
return false;
Expand All @@ -77,6 +83,18 @@ static bool _fle2_append_encryptedFieldConfig(const mongocrypt_ctx_t *ctx,
}
bson_free(default_ecocCollection);
}
if (!has_strEncodeVersion) {
_mongocrypt_ctx_encrypt_t *ectx = (_mongocrypt_ctx_encrypt_t *)ctx;
// Check str_encode_version on the EncryptedFieldConfig object to see whether we should append or not. 0
// indicates that there was no text search query in the EFC and the strEncodeVersion was not set on the EFC; in
// this case, we should not append strEncodeVersion, as mongocryptd/mongod may not understand it.
if (ectx->efc.str_encode_version != 0) {
if (!BSON_APPEND_INT32(dst, "strEncodeVersion", (int32_t)ectx->efc.str_encode_version)) {
CLIENT_ERR("unable to append strEncodeVersion");
return false;
}
}
}
return true;
}

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

/*
* Checks the "encryptedFields.strEncodeVersion" field for "create" commands for validity, and sets it to the default if
* it does not exist.
*/
static bool _fle2_fixup_encryptedFields_strEncodeVersion(const char *cmd_name,
bson_t *cmd /* in and out */,
const mc_EncryptedFieldConfig_t *efc,
mongocrypt_status_t *status) {
BSON_ASSERT_PARAM(cmd_name);
BSON_ASSERT_PARAM(cmd);
BSON_ASSERT_PARAM(efc);

if (0 == strcmp(cmd_name, "create")) {
bson_iter_t ef_iter;
if (!bson_iter_init_find(&ef_iter, cmd, "encryptedFields")) {
// No encryptedFields, nothing to check or fix
return true;
}
if (!BSON_ITER_HOLDS_DOCUMENT(&ef_iter)) {
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Expected encryptedFields to be type obj, got: %s",
mc_bson_type_to_string(bson_iter_type(&ef_iter)));
return false;
}
bson_iter_t sev_iter;
if (!bson_iter_recurse(&ef_iter, &sev_iter)) {
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to recurse bson_iter");
return false;
}
if (!bson_iter_find(&sev_iter, "strEncodeVersion")) {
if (efc->str_encode_version == 0) {
// Unset StrEncodeVersion matches the EFC, nothing to fix.
return true;
}

// No strEncodeVersion and the EFC has a nonzero strEncodeVersion, add it.
// Initialize the new cmd object from the old one, excluding encryptedFields.
bson_t fixed = BSON_INITIALIZER;
bson_copy_to_excluding_noinit(cmd, &fixed, "encryptedFields", NULL);

// Recurse the original encryptedFields and copy everything over.
bson_iter_t copy_iter;
if (!bson_iter_recurse(&ef_iter, &copy_iter)) {
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to recurse bson_iter");
goto fail;
}
bson_t fixed_ef;
if (!BSON_APPEND_DOCUMENT_BEGIN(&fixed, "encryptedFields", &fixed_ef)) {
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to start appending encryptedFields");
goto fail;
}
while (bson_iter_next(&copy_iter)) {
if (!bson_append_iter(&fixed_ef, NULL, 0, &copy_iter)) {
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to copy element");
goto fail;
}
}

// Add the EFC's strEncodeVersion to encryptedFields.
if (!BSON_APPEND_INT32(&fixed_ef, "strEncodeVersion", efc->str_encode_version)) {
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to append strEncodeVersion");
goto fail;
}
if (!bson_append_document_end(&fixed, &fixed_ef)) {
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to finish appending encryptedFields");
goto fail;
}

bson_destroy(cmd);
if (!bson_steal(cmd, &fixed)) {
CLIENT_ERR("_fle2_fixup_encryptedFields_strEncodeVersion: Failed to steal BSON");
goto fail;
}
return true;
fail:
bson_destroy(&fixed);
return false;
} else {
// Check strEncodeVersion for match against EFC
if (!BSON_ITER_HOLDS_INT32(&sev_iter)) {
CLIENT_ERR("expected 'strEncodeVersion' to be type int32, got: %d", bson_iter_type(&sev_iter));
return false;
}
int32_t version = bson_iter_int32(&sev_iter);
if (version != efc->str_encode_version) {
CLIENT_ERR("'strEncodeVersion' of %d does not match efc->str_encode_version of %d",
version,
efc->str_encode_version);
return false;
}
}
}
return true;
}

/* Process a call to mongocrypt_ctx_finalize when an encryptedFieldConfig is
* associated with the command. */
static bool _fle2_finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) {
Expand Down Expand Up @@ -1505,6 +1617,13 @@ static bool _fle2_finalize(mongocrypt_ctx_t *ctx, mongocrypt_binary_t *out) {
return _mongocrypt_ctx_fail(ctx);
}

/* If this is a create command, append the encryptedFields.strEncodeVersion field if it's necessary. If the field
* already exists, check it against the EFC for correctness. */
if (!_fle2_fixup_encryptedFields_strEncodeVersion(command_name, &converted, &ectx->efc, ctx->status)) {
bson_destroy(&converted);
return _mongocrypt_ctx_fail(ctx);
}

/* Append a new 'encryptionInformation'. */
if (!result.must_omit && !ectx->used_empty_encryptedFields) {
if (!_fle2_insert_encryptionInformation(ctx,
Expand Down
4 changes: 4 additions & 0 deletions src/mongocrypt-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@

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

#define LATEST_STR_ENCODE_VERSION 1

#define MIN_STR_ENCODE_VERSION 1

/* TODO: Move these to mongocrypt-log-private.h? */
const char *tmp_json(const bson_t *bson);

Expand Down
23 changes: 23 additions & 0 deletions test/data/efc/efc-oneField-badVersionSet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"escCollection": "fle2.basic.esc",
"ecocCollection": "fle2.basic.ecoc",
"fields": [
{
"keyId": {
"$binary": {
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
"subType": "04"
}
},
"path": "firstName",
"bsonType": "string",
"queries": {
"queryType": "equality",
"contention": {
"$numberLong": "0"
}
}
}
],
"strEncodeVersion": 99
}
23 changes: 23 additions & 0 deletions test/data/efc/efc-oneField-goodVersionSet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"escCollection": "fle2.basic.esc",
"ecocCollection": "fle2.basic.ecoc",
"fields": [
{
"keyId": {
"$binary": {
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
"subType": "04"
}
},
"path": "firstName",
"bsonType": "string",
"queries": {
"queryType": "equality",
"contention": {
"$numberLong": "0"
}
}
}
],
"strEncodeVersion": 1
}
48 changes: 48 additions & 0 deletions test/data/efc/efc-textSearchFields-badVersionSet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"escCollection": "fle2.basic.esc",
"eccCollection": "fle2.basic.ecc",
"ecocCollection": "fle2.basic.ecoc",
"fields": [
{
"keyId": {
"$binary": {
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
"subType": "04"
}
},
"path": "firstName",
"bsonType": "string",
"queries": {
"queryType": "substringPreview",
"contention": {
"$numberLong": "0"
}
}
},
{
"keyId": {
"$binary": {
"base64": "q83vqxI0mHYSNBI0VniQEg==",
"subType": "04"
}
},
"path": "lastName",
"bsonType": "string",
"queries": [
{
"queryType": "suffixPreview",
"contention": {
"$numberLong": "0"
}
},
{
"queryType": "prefixPreview",
"contention": {
"$numberLong": "0"
}
}
]
}
],
"strEncodeVersion": 99
}
48 changes: 48 additions & 0 deletions test/data/efc/efc-textSearchFields-goodVersionSet.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"escCollection": "fle2.basic.esc",
"eccCollection": "fle2.basic.ecc",
"ecocCollection": "fle2.basic.ecoc",
"fields": [
{
"keyId": {
"$binary": {
"base64": "EjRWeBI0mHYSNBI0VniQEg==",
"subType": "04"
}
},
"path": "firstName",
"bsonType": "string",
"queries": {
"queryType": "substringPreview",
"contention": {
"$numberLong": "0"
}
}
},
{
"keyId": {
"$binary": {
"base64": "q83vqxI0mHYSNBI0VniQEg==",
"subType": "04"
}
},
"path": "lastName",
"bsonType": "string",
"queries": [
{
"queryType": "suffixPreview",
"contention": {
"$numberLong": "0"
}
},
{
"queryType": "prefixPreview",
"contention": {
"$numberLong": "0"
}
}
]
}
],
"strEncodeVersion": 1
}
Loading