Skip to content
1 change: 1 addition & 0 deletions src/mc-efc-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ typedef enum _supported_query_type_flags {
typedef struct _mc_EncryptedField_t {
supported_query_type_flags supported_queries;
_mongocrypt_buffer_t keyId;
const char *keyAltName;
const char *path;
struct _mc_EncryptedField_t *next;
} mc_EncryptedField_t;
Expand Down
49 changes: 41 additions & 8 deletions src/mc-efc.c
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,46 @@ static bool _parse_field(mc_EncryptedFieldConfig_t *efc, bson_t *field, mongocry
BSON_ASSERT_PARAM(efc);
BSON_ASSERT_PARAM(field);

if (!bson_iter_init_find(&field_iter, field, "keyId")) {
CLIENT_ERR("unable to find 'keyId' in 'field' document");
bool has_keyid = false;
bool has_keyaltname = false;
if (bson_iter_init_find(&field_iter, field, "keyId")) {
has_keyid = true;
}
if (bson_iter_init_find(&field_iter, field, "keyAltName")) {
has_keyaltname = true;
}
if (!(has_keyid || has_keyaltname)) {
CLIENT_ERR("unable to find 'keyId' or 'keyAltName' in 'field' document");
return false;
}
if (!BSON_ITER_HOLDS_BINARY(&field_iter)) {
CLIENT_ERR("expected 'fields.keyId' to be type binary, got: %d", (int)bson_iter_type(&field_iter));
if (has_keyid && has_keyaltname) {
CLIENT_ERR("only one of 'keyId' or 'keyAltName may be in 'field' document");
return false;
}

_mongocrypt_buffer_t field_keyid;
if (!_mongocrypt_buffer_from_uuid_iter(&field_keyid, &field_iter)) {
CLIENT_ERR("unable to parse uuid key from 'fields.keyId'");
return false;
if (has_keyid) {
BSON_ASSERT(bson_iter_init_find(&field_iter, field, "keyId"));
if (!BSON_ITER_HOLDS_BINARY(&field_iter)) {
CLIENT_ERR("expected 'fields.keyId' to be type binary, got: %d", (int)bson_iter_type(&field_iter));
return false;
}
if (!_mongocrypt_buffer_from_uuid_iter(&field_keyid, &field_iter)) {
CLIENT_ERR("unable to parse uuid key from 'fields.keyId'");
return false;
}
} else if (has_keyaltname) {
BSON_ASSERT(bson_iter_init_find(&field_iter, field, "keyAltName"));
}

const char *keyAltName = "";
if (has_keyaltname) {
BSON_ASSERT(bson_iter_init_find(&field_iter, field, "keyAltName"));
if (!BSON_ITER_HOLDS_UTF8(&field_iter)) {
CLIENT_ERR("expected 'fields.keyAltName' to be type UTF-8, got: %d", (int)bson_iter_type(&field_iter));
return false;
}
keyAltName = bson_iter_utf8(&field_iter, NULL);
}

const char *field_path;
Expand Down Expand Up @@ -151,7 +179,12 @@ static bool _parse_field(mc_EncryptedFieldConfig_t *efc, bson_t *field, mongocry

/* Prepend a new mc_EncryptedField_t */
mc_EncryptedField_t *ef = bson_malloc0(sizeof(mc_EncryptedField_t));
_mongocrypt_buffer_copy_to(&field_keyid, &ef->keyId);
if (has_keyid) {
_mongocrypt_buffer_copy_to(&field_keyid, &ef->keyId);
}
if (has_keyaltname) {
ef->keyAltName = bson_strdup(keyAltName);
}
ef->path = bson_strdup(field_path);
ef->next = efc->fields;
ef->supported_queries = query_types;
Expand Down
10 changes: 9 additions & 1 deletion src/mc-schema-broker-private.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include "mc-efc-private.h" // mc_EncryptedFieldConfig_t
#include "mongocrypt-cache-collinfo-private.h"
#include "mongocrypt-key-broker-private.h"
#include "mongocrypt.h"
#include <bson/bson.h>

Expand Down Expand Up @@ -102,6 +103,12 @@ bool mc_schema_broker_need_more_schemas(const mc_schema_broker_t *sb);
const mc_EncryptedFieldConfig_t *
mc_schema_broker_get_encryptedFields(const mc_schema_broker_t *sb, const char *coll, mongocrypt_status_t *status);

// mc_schema_broker_get_encryptedFields returns encryptedFields for a collection if any exists.
//
// Returns NULL if none is found.
const mc_EncryptedFieldConfig_t *
mc_schema_broker_maybe_get_encryptedFields(const mc_schema_broker_t *sb, const char *coll, mongocrypt_status_t *status);

typedef enum {
MC_CMD_SCHEMAS_FOR_CRYPT_SHARED, // target the crypt_shared library.
MC_CMD_SCHEMAS_FOR_MONGOCRYPTD, // target mongocryptd process.
Expand All @@ -118,7 +125,8 @@ typedef enum {
// - encryptionInformation: for QE.
//
// Set cmd_target to the intended command destination. This impacts if/how schema information is added.
bool mc_schema_broker_add_schemas_to_cmd(const mc_schema_broker_t *sb,
bool mc_schema_broker_add_schemas_to_cmd(mc_schema_broker_t *sb,
_mongocrypt_key_broker_t *kb,
bson_t *cmd /* in and out */,
mc_cmd_target_t cmd_target,
mongocrypt_status_t *status);
Expand Down
141 changes: 130 additions & 11 deletions src/mc-schema-broker.c
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
#include "mc-schema-broker-private.h"

#include "mc-efc-private.h" // mc_EncryptedFieldConfig_t
#include "mongocrypt-buffer-private.h"
#include "mongocrypt-cache-collinfo-private.h"
#include "mongocrypt-key-broker-private.h"
#include "mongocrypt-private.h"
Expand Down Expand Up @@ -607,7 +608,25 @@ mc_schema_broker_get_encryptedFields(const mc_schema_broker_t *sb, const char *c
return NULL;
}

const mc_EncryptedFieldConfig_t *
mc_schema_broker_maybe_get_encryptedFields(const mc_schema_broker_t *sb, const char *coll, mongocrypt_status_t *status) {
BSON_ASSERT_PARAM(sb);
BSON_ASSERT_PARAM(coll);

for (const mc_schema_entry_t *it = sb->ll; it != NULL; it = it->next) {
if (0 != strcmp(it->coll, coll)) {
continue;
}
if (!it->encryptedFields.set) {
return NULL;
}
return &it->encryptedFields.efc;
}
return NULL;
}

static bool append_encryptedFields(const bson_t *encryptedFields,
_mongocrypt_key_broker_t *kb,
const char *coll,
uint8_t default_strEncodeVersion,
bson_t *out,
Expand All @@ -633,7 +652,8 @@ static bool append_encryptedFields(const bson_t *encryptedFields,

// Copy all values. Check if state collections are present.
while (bson_iter_next(&iter)) {
if (strcmp(bson_iter_key(&iter), "escCollection") == 0) {
const char *iter_key = bson_iter_key(&iter);
if (strcmp(iter_key, "escCollection") == 0) {
has_escCollection = true;
}
if (strcmp(bson_iter_key(&iter), "ecocCollection") == 0) {
Expand All @@ -645,8 +665,92 @@ static bool append_encryptedFields(const bson_t *encryptedFields,
if (strcmp(bson_iter_key(&iter), "strEncodeVersion") == 0) {
has_strEncodeVersion = true;
}
TRY_BSON_OR(BSON_APPEND_VALUE(out, bson_iter_key(&iter), bson_iter_value(&iter))) {
goto fail;
/* Special-case the "fields" array: copy each element but omit the
* "keyAltName" key from each subdocument. For other keys, copy as-is. */
if (0 == strcmp(iter_key, "fields") && BSON_ITER_HOLDS_ARRAY(&iter)) {
uint32_t array_len = 0;
const uint8_t *array_data = NULL;
bson_t array_bson;

bson_iter_array(&iter, &array_len, &array_data);
bson_init_static(&array_bson, array_data, array_len);

bson_t new_array;
TRY_BSON_OR(BSON_APPEND_ARRAY_BEGIN(out, "fields", &new_array)) {
goto fail;
}

bson_iter_t arr_it;
if (!bson_iter_init(&arr_it, &array_bson)) {
CLIENT_ERR("failed to iterate 'fields' array");
goto fail;
}

size_t idx = 0;
while (bson_iter_next(&arr_it)) {
char idx_str[32];
const char *idx_str_ptr;
const size_t ret = bson_uint32_to_string((uint32_t)idx, &idx_str_ptr, idx_str, sizeof idx_str);
BSON_ASSERT(ret > 0 && ret <= (int)sizeof idx_str);

if (BSON_ITER_HOLDS_DOCUMENT(&arr_it)) {
uint32_t doc_len = 0;
const uint8_t *doc_data = NULL;
bson_iter_document(&arr_it, &doc_len, &doc_data);
bson_t elem_doc;
bson_init_static(&elem_doc, doc_data, doc_len);

bson_t new_doc;
char *keyAltName_dup = NULL;

/* Extract keyAltName (if present) and strdup it so caller can
* derive a keyId from it. */
bson_iter_t doc_it2;
if (bson_iter_init(&doc_it2, &elem_doc)) {
if (bson_iter_find(&doc_it2, "keyAltName") && BSON_ITER_HOLDS_UTF8(&doc_it2)) {
const char *kan = bson_iter_utf8(&doc_it2, NULL);
if (kan) {
keyAltName_dup = bson_strdup(kan);
}
}
}

bson_init(&new_doc);
/* Copy elem_doc into new_doc excluding "keyAltName". */
bson_copy_to_excluding_noinit(&elem_doc, &new_doc, "keyAltName", NULL);

if (keyAltName_dup) {
_mongocrypt_buffer_t unused, key_id_out;
bson_value_t key_alt_name_v;
_bson_value_from_string(keyAltName_dup, &key_alt_name_v);
BSON_ASSERT(_mongocrypt_key_broker_decrypted_key_by_name(kb, &key_alt_name_v, &unused, &key_id_out));
bson_append_binary(&new_doc, "keyId", -1, key_id_out.subtype, key_id_out.data, key_id_out.len);
}

TRY_BSON_OR(bson_append_document(&new_array, idx_str_ptr, -1, &new_doc)) {
bson_destroy(&new_doc);
bson_free(keyAltName_dup);
goto fail;
}
bson_destroy(&new_doc);
bson_free(keyAltName_dup);
} else {
/* Non-document elements: copy as-is. */
TRY_BSON_OR(BSON_APPEND_VALUE(&new_array, idx_str, bson_iter_value(&arr_it))) {
goto fail;
}
}

idx++;
}

TRY_BSON_OR(bson_append_array_end(out, &new_array)) {
goto fail;
}
} else {
TRY_BSON_OR(BSON_APPEND_VALUE(out, iter_key, bson_iter_value(&iter))) {
goto fail;
}
}
}

Expand Down Expand Up @@ -687,6 +791,7 @@ static bool append_encryptedFields(const bson_t *encryptedFields,
}

static bool append_encryptionInformation(const mc_schema_broker_t *sb,
_mongocrypt_key_broker_t *kb,
const char *cmd_name,
bson_t *out,
mongocrypt_status_t *status) {
Expand Down Expand Up @@ -728,7 +833,7 @@ static bool append_encryptionInformation(const mc_schema_broker_t *sb,
encryptedFields = &se->encryptedFields.bson;
default_strEncodeVersion = se->encryptedFields.efc.str_encode_version;
}
if (!append_encryptedFields(encryptedFields, se->coll, default_strEncodeVersion, &ns_to_schema_bson, status)) {
if (!append_encryptedFields(encryptedFields, kb, se->coll, default_strEncodeVersion, &ns_to_schema_bson, status)) {
goto loop_fail;
}

Expand Down Expand Up @@ -778,6 +883,7 @@ static const char *get_cmd_name(const bson_t *cmd, mongocrypt_status_t *status)
}

static bool insert_encryptionInformation(const mc_schema_broker_t *sb,
_mongocrypt_key_broker_t *kb,
const char *cmd_name,
bson_t *cmd /* in and out */,
mc_cmd_target_t cmd_target,
Expand Down Expand Up @@ -841,7 +947,7 @@ static bool insert_encryptionInformation(const mc_schema_broker_t *sb,
goto fail;
}
// And append `encryptionInformation`.
if (!append_encryptionInformation(sb, cmd_name, &nsInfo_array_0, status)) {
if (!append_encryptionInformation(sb, kb, cmd_name, &nsInfo_array_0, status)) {
goto fail;
}
if (!bson_append_document_end(&nsInfo_array, &nsInfo_array_0)) {
Expand Down Expand Up @@ -891,7 +997,7 @@ static bool insert_encryptionInformation(const mc_schema_broker_t *sb,
bson_copy_to(&tmp, &explain);
}

if (!append_encryptionInformation(sb, cmd_name, &explain, status)) {
if (!append_encryptionInformation(sb, kb, cmd_name, &explain, status)) {
goto fail;
}

Expand All @@ -914,7 +1020,7 @@ static bool insert_encryptionInformation(const mc_schema_broker_t *sb,
// "<command name>": { ... }
// "encryptionInformation": {}
// }
if (!append_encryptionInformation(sb, cmd_name, cmd, status)) {
if (!append_encryptionInformation(sb, kb, cmd_name, cmd, status)) {
goto fail;
}

Expand Down Expand Up @@ -1032,7 +1138,8 @@ static bool insert_csfleEncryptionSchemas(const mc_schema_broker_t *sb,
return true;
}

bool mc_schema_broker_add_schemas_to_cmd(const mc_schema_broker_t *sb,
bool mc_schema_broker_add_schemas_to_cmd(mc_schema_broker_t *sb,
_mongocrypt_key_broker_t *kb,
bson_t *cmd /* in and out */,
mc_cmd_target_t cmd_target,
mongocrypt_status_t *status) {
Expand All @@ -1053,6 +1160,18 @@ bool mc_schema_broker_add_schemas_to_cmd(const mc_schema_broker_t *sb,
if (it->encryptedFields.set) {
has_encryptedFields = true;
coll_with_encryptedFields = it->coll;
for (mc_EncryptedField_t *f = it->encryptedFields.efc.fields; f != NULL; f = f->next) {
if (f->keyAltName) {
bson_value_t key_alt_name;
_mongocrypt_buffer_t unused;
_bson_value_from_string(f->keyAltName, &key_alt_name);
const bool r = _mongocrypt_key_broker_decrypted_key_by_name(kb, &key_alt_name, &unused, &f->keyId);
if (!r) {
CLIENT_ERR("Could not find key by keyAltName: %s", f->keyAltName);
return false;
}
}
}
} else if (it->jsonSchema.set) {
has_jsonSchema = true;
coll_with_jsonSchema = it->coll;
Expand All @@ -1061,7 +1180,7 @@ bool mc_schema_broker_add_schemas_to_cmd(const mc_schema_broker_t *sb,

if (has_encryptedFields && has_jsonSchema) {
if (sb->schema_mixing_is_supported) {
return insert_encryptionInformation(sb, cmd_name, cmd, cmd_target, status)
return insert_encryptionInformation(sb, kb, cmd_name, cmd, cmd_target, status)
&& insert_csfleEncryptionSchemas(sb, cmd, cmd_target, status);
}

Expand All @@ -1078,7 +1197,7 @@ bool mc_schema_broker_add_schemas_to_cmd(const mc_schema_broker_t *sb,

if (has_encryptedFields) {
// Use encryptionInformation.
return insert_encryptionInformation(sb, cmd_name, cmd, cmd_target, status);
return insert_encryptionInformation(sb, kb, cmd_name, cmd, cmd_target, status);
}

if (has_jsonSchema) {
Expand All @@ -1089,7 +1208,7 @@ bool mc_schema_broker_add_schemas_to_cmd(const mc_schema_broker_t *sb,
// Collections have no QE or CSFLE schemas.
if (0 == strcmp(cmd_name, "bulkWrite")) {
// "bulkWrite" does not support the jsonSchema field. Use encryptionInformation with empty schemas.
return insert_encryptionInformation(sb, cmd_name, cmd, cmd_target, status);
return insert_encryptionInformation(sb, kb, cmd_name, cmd, cmd_target, status);
}

// Use csfleEncryptionSchemas / jsonSchema with empty schemas.
Expand Down
Loading