diff --git a/src/mc-fle-blob-subtype-private.h b/src/mc-fle-blob-subtype-private.h index 3fda8e974..498903428 100644 --- a/src/mc-fle-blob-subtype-private.h +++ b/src/mc-fle-blob-subtype-private.h @@ -42,7 +42,10 @@ typedef enum { MC_SUBTYPE_FLE2FindRangePayloadV2 = 13, MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2 = 14, MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2 = 15, - MC_SUBTYPE_FLE2UnindexedEncryptedValueV2 = 16 + MC_SUBTYPE_FLE2UnindexedEncryptedValueV2 = 16, + + /* Text Search Subtypes */ + MC_SUBTYPE_FLE2IndexedTextEncryptedValue = 17, } mc_fle_blob_subtype_t; #endif /* MC_FLE_BLOB_SUBTYPE_PRIVATE_H */ diff --git a/src/mc-fle2-payload-iev-private-v2.h b/src/mc-fle2-payload-iev-private-v2.h index 36d916af8..a2042defb 100644 --- a/src/mc-fle2-payload-iev-private-v2.h +++ b/src/mc-fle2-payload-iev-private-v2.h @@ -24,7 +24,7 @@ #include "mongocrypt-status-private.h" /* - * FLE2IndexedEqualityEncryptedValueV2 and FLE2IndexedRangeEncryptedValueV2 + * FLE2IndexedEqualityEncryptedValueV2, FLE2IndexedRangeEncryptedValueV2, and FLEIndexedTextEncryptedValue * share a common internal implementation. * * Lifecycle: @@ -78,12 +78,34 @@ * 1/ `edge_count` is introduced as an octet following `original_bson_type`. * 2/ Rather than a single metadata block, we have {edge_count} blocks. * + * FLE2IndexedTextEncryptedValue has the following data layout: + * + * struct FLE2IndexedTextEncryptedValue { + * uint8_t fle_blob_subtype = 17; + * uint8_t S_KeyId[16]; + * uint8_t original_bson_type; + * uint8_t edge_count; + * uint8_t substr_tag_count; + * uint8_t suffix_tag_count; + * uint8_t ServerEncryptedValue[ServerEncryptedValue.length]; + * FLE2TagAndEncryptedMetadataBlock exact_metadata; + * FLE2TagAndEncryptedMetadataBlock substr_metadata[substr_tag_count]; + * FLE2TagAndEncryptedMetadataBlock suffix_metadata[suffix_tag_count]; + * FLE2TagAndEncryptedMetadataBlock prefix_metadata[edge_count - suffix_tag_count - substr_tag_count - 1]; + * } + * The main difference in this format is that we split `metadata` into 4 + * sections, one for each text search index type. We add two octets, + * `substr_tag_count` and `suffix_tag_count`, following `edge_count` + * in order to track the delineation of the metadata. Similarly to + * FLE2IndexedEqualityEncryptedValueV2, we have `edge_count` total + * blocks. */ typedef enum { kFLE2IEVTypeInitV2, kFLE2IEVTypeEqualityV2, kFLE2IEVTypeRangeV2, + kFLE2IEVTypeText, } _mc_fle2_iev_v2_type; typedef struct _mc_FLE2IndexedEncryptedValueV2_t { @@ -91,6 +113,8 @@ typedef struct _mc_FLE2IndexedEncryptedValueV2_t { uint8_t fle_blob_subtype; uint8_t bson_value_type; uint8_t edge_count; + uint8_t substr_tag_count; + uint8_t suffix_tag_count; _mongocrypt_buffer_t S_KeyId; _mongocrypt_buffer_t ServerEncryptedValue; @@ -182,6 +206,18 @@ const _mongocrypt_buffer_t *mc_FLE2IndexedEncryptedValueV2_get_ClientValue(const uint8_t mc_FLE2IndexedEncryptedValueV2_get_edge_count(const mc_FLE2IndexedEncryptedValueV2_t *iev, mongocrypt_status_t *status); +bool mc_FLE2IndexedEncryptedValueV2_get_substr_tag_count(const mc_FLE2IndexedEncryptedValueV2_t *iev, + uint8_t *count, + mongocrypt_status_t *status); + +bool mc_FLE2IndexedEncryptedValueV2_get_suffix_tag_count(const mc_FLE2IndexedEncryptedValueV2_t *iev, + uint8_t *count, + mongocrypt_status_t *status); + +bool mc_FLE2IndexedEncryptedValueV2_get_prefix_tag_count(const mc_FLE2IndexedEncryptedValueV2_t *iev, + uint8_t *count, + mongocrypt_status_t *status); + bool mc_FLE2IndexedEncryptedValueV2_get_edge(const mc_FLE2IndexedEncryptedValueV2_t *iev, mc_FLE2TagAndEncryptedMetadataBlock_t *out, const uint8_t edge_index, @@ -191,6 +227,25 @@ bool mc_FLE2IndexedEncryptedValueV2_get_metadata(const mc_FLE2IndexedEncryptedVa mc_FLE2TagAndEncryptedMetadataBlock_t *out, mongocrypt_status_t *status); +bool mc_FLE2IndexedEncryptedValueV2_get_exact_metadata(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mc_FLE2TagAndEncryptedMetadataBlock_t *out, + mongocrypt_status_t *status); + +bool mc_FLE2IndexedEncryptedValueV2_get_substr_metadata(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mc_FLE2TagAndEncryptedMetadataBlock_t *out, + const uint8_t block_index, + mongocrypt_status_t *status); + +bool mc_FLE2IndexedEncryptedValueV2_get_suffix_metadata(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mc_FLE2TagAndEncryptedMetadataBlock_t *out, + const uint8_t block_index, + mongocrypt_status_t *status); + +bool mc_FLE2IndexedEncryptedValueV2_get_prefix_metadata(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mc_FLE2TagAndEncryptedMetadataBlock_t *out, + const uint8_t block_index, + mongocrypt_status_t *status); + void mc_FLE2IndexedEncryptedValueV2_destroy(mc_FLE2IndexedEncryptedValueV2_t *iev); #endif /* MONGOCRYPT_INDEXED_ENCRYPTED_VALUE_PRIVATE_V2_H */ diff --git a/src/mc-fle2-payload-iev-v2.c b/src/mc-fle2-payload-iev-v2.c index 9f22b2ce6..bab503764 100644 --- a/src/mc-fle2-payload-iev-v2.c +++ b/src/mc-fle2-payload-iev-v2.c @@ -288,14 +288,80 @@ uint8_t mc_FLE2IndexedEncryptedValueV2_get_edge_count(const mc_FLE2IndexedEncryp return 0; } - if (iev->type != kFLE2IEVTypeRangeV2) { - CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_edge_count must be called with type range"); + if (!(iev->type == kFLE2IEVTypeRangeV2 || iev->type == kFLE2IEVTypeText)) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_edge_count must be called with type range or text"); return 0; } return iev->edge_count; } +bool mc_FLE2IndexedEncryptedValueV2_get_substr_tag_count(const mc_FLE2IndexedEncryptedValueV2_t *iev, + uint8_t *count, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(iev); + BSON_ASSERT_PARAM(count); + + if (iev->type == kFLE2IEVTypeInitV2) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_substr_tag_count " + "must be called after " + "mc_FLE2IndexedEncryptedValueV2_parse"); + return false; + } + + if (iev->type != kFLE2IEVTypeText) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_substr_tag_count must be called with type text"); + return false; + } + + *count = iev->substr_tag_count; + return true; +} + +bool mc_FLE2IndexedEncryptedValueV2_get_suffix_tag_count(const mc_FLE2IndexedEncryptedValueV2_t *iev, + uint8_t *count, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(iev); + BSON_ASSERT_PARAM(count); + + if (iev->type == kFLE2IEVTypeInitV2) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_suffix_tag_count " + "must be called after " + "mc_FLE2IndexedEncryptedValueV2_parse"); + return false; + } + + if (iev->type != kFLE2IEVTypeText) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_suffix_tag_count must be called with type text"); + return false; + } + + *count = iev->suffix_tag_count; + return true; +} + +bool mc_FLE2IndexedEncryptedValueV2_get_prefix_tag_count(const mc_FLE2IndexedEncryptedValueV2_t *iev, + uint8_t *count, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(iev); + BSON_ASSERT_PARAM(count); + + if (iev->type == kFLE2IEVTypeInitV2) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_prefix_tag_count " + "must be called after " + "mc_FLE2IndexedEncryptedValueV2_parse"); + return false; + } + + if (iev->type != kFLE2IEVTypeText) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_prefix_tag_count must be called with type text"); + return false; + } + + *count = (uint8_t)(iev->edge_count - iev->substr_tag_count - iev->suffix_tag_count - 1); + return true; +} + bool mc_FLE2IndexedEncryptedValueV2_get_edge(const mc_FLE2IndexedEncryptedValueV2_t *iev, mc_FLE2TagAndEncryptedMetadataBlock_t *out, const uint8_t edge_index, @@ -349,6 +415,112 @@ bool mc_FLE2IndexedEncryptedValueV2_get_metadata(const mc_FLE2IndexedEncryptedVa return true; } +bool mc_FLE2IndexedEncryptedValueV2_get_exact_metadata(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mc_FLE2TagAndEncryptedMetadataBlock_t *out, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(iev); + BSON_ASSERT_PARAM(out); + + if (iev->type == kFLE2IEVTypeInitV2) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_exact_metadata " + "must be called after " + "mc_FLE2IndexedEncryptedValueV2_parse"); + return false; + } + + if (iev->type != kFLE2IEVTypeText) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_exact_metadata must be called with type text"); + return false; + } + + // Write edge into out struct + *out = iev->metadata[0]; + return true; +} + +bool mc_FLE2IndexedEncryptedValueV2_get_substr_metadata(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mc_FLE2TagAndEncryptedMetadataBlock_t *out, + const uint8_t block_index, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(iev); + BSON_ASSERT_PARAM(out); + + if (iev->type == kFLE2IEVTypeInitV2) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_substr_metadata " + "must be called after " + "mc_FLE2IndexedEncryptedValueV2_parse"); + return false; + } + + if (iev->type != kFLE2IEVTypeText) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_substr_metadata must be called with type text"); + return false; + } + + if (block_index >= iev->substr_tag_count) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_substr_metadata must be called with index block_index less " + "than substr tag count"); + return false; + } + + // Write edge into out struct + *out = iev->metadata[block_index + 1 /* exact block */]; + return true; +} + +bool mc_FLE2IndexedEncryptedValueV2_get_suffix_metadata(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mc_FLE2TagAndEncryptedMetadataBlock_t *out, + const uint8_t block_index, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(iev); + BSON_ASSERT_PARAM(out); + + if (iev->type == kFLE2IEVTypeInitV2) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_suffix_metadata " + "must be called after " + "mc_FLE2IndexedEncryptedValueV2_parse"); + return false; + } + + if (iev->type != kFLE2IEVTypeText) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_suffix_metadata must be called with type text"); + return false; + } + + if (block_index >= iev->suffix_tag_count) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_suffix_metadata must be called with index block_index less " + "than suffix tag count"); + return false; + } + + // Write edge into out struct + *out = iev->metadata[block_index + iev->substr_tag_count + 1 /* exact block */]; + return true; +} + +bool mc_FLE2IndexedEncryptedValueV2_get_prefix_metadata(const mc_FLE2IndexedEncryptedValueV2_t *iev, + mc_FLE2TagAndEncryptedMetadataBlock_t *out, + const uint8_t block_index, + mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(iev); + BSON_ASSERT_PARAM(out); + + // We can skip the check for text type because get_prefix_tag_count does it for us. + uint8_t prefix_tag_count; + if (!mc_FLE2IndexedEncryptedValueV2_get_prefix_tag_count(iev, &prefix_tag_count, status)) { + return false; + } + if (block_index >= prefix_tag_count) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_get_prefix_metadata must be called with index block_index less " + "than prefix tag count"); + return false; + } + + // Write edge into out struct + *out = iev->metadata[block_index + iev->suffix_tag_count + iev->substr_tag_count + 1 /* exact block */]; + return true; +} + bool mc_FLE2IndexedEncryptedValueV2_parse(mc_FLE2IndexedEncryptedValueV2_t *iev, const _mongocrypt_buffer_t *buf, mongocrypt_status_t *status) { @@ -375,9 +547,11 @@ bool mc_FLE2IndexedEncryptedValueV2_parse(mc_FLE2IndexedEncryptedValueV2_t *iev, iev->type = kFLE2IEVTypeEqualityV2; } else if (iev->fle_blob_subtype == MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2) { iev->type = kFLE2IEVTypeRangeV2; + } else if (iev->fle_blob_subtype == MC_SUBTYPE_FLE2IndexedTextEncryptedValue) { + iev->type = kFLE2IEVTypeText; } else { CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_parse expected " - "fle_blob_subtype MC_SUBTYPE_FLE2Indexed(Equality|Range)EncryptedValueV2 got: %" PRIu8, + "fle_blob_subtype MC_SUBTYPE_FLE2Indexed(Equality|Range|Text)EncryptedValue[V2] got: %" PRIu8, iev->fle_blob_subtype); return false; } @@ -388,13 +562,30 @@ bool mc_FLE2IndexedEncryptedValueV2_parse(mc_FLE2IndexedEncryptedValueV2_t *iev, /* Read original_bson_type. */ CHECK_AND_RETURN(mc_reader_read_u8(&reader, &iev->bson_value_type, status)); - /* Read edge_count */ + /* Read edge_count, substr_tag_count, suffix_tag_count */ // Set equality edge_count to 1 as it doesn't technically exist but // there will be a singular metadata block + // Set substr/suffix_tag_count to 0 for all types besides text + iev->substr_tag_count = 0; + iev->suffix_tag_count = 0; if (iev->type == kFLE2IEVTypeEqualityV2) { iev->edge_count = 1; } else { CHECK_AND_RETURN(mc_reader_read_u8(&reader, &iev->edge_count, status)); + if (iev->type == kFLE2IEVTypeText) { + CHECK_AND_RETURN(mc_reader_read_u8(&reader, &iev->substr_tag_count, status)); + CHECK_AND_RETURN(mc_reader_read_u8(&reader, &iev->suffix_tag_count, status)); + // Upconvert so that addition doesn't overflow + if ((uint16_t)iev->edge_count < (uint16_t)iev->substr_tag_count + (uint16_t)iev->suffix_tag_count + 1) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_parse for text type expected edge count to be " + "at least substr_tag_count + suffix_count + 1, but: %" PRIu8 " < %" PRIu8 " + %" PRIu8 + " + 1", + iev->edge_count, + iev->substr_tag_count, + iev->suffix_tag_count); + return false; + } + } } // Maximum edge_count(255) times kMetadataLen(96) fits easily without @@ -433,11 +624,13 @@ static inline uint32_t mc_FLE2IndexedEncryptedValueV2_serialized_length(const mc // fle_blob_subtype: 1 byte // S_KeyId: UUID_LEN bytes // bson_value_type: 1 byte - // if range: edge_count: 1 byte + // if range or text: edge_count: 1 byte + // if text: tag counts: 2 bytes // ServerEncryptedValue: ServerEncryptedValue.len bytes // metadata: edge_count * kMetadataLen bytes - return iev->ServerEncryptedValue.len + 1 + UUID_LEN + 1 + (iev->type == kFLE2IEVTypeRangeV2 ? 1 : 0) - + iev->edge_count * kMetadataLen; + return iev->ServerEncryptedValue.len + 1 + UUID_LEN + 1 + + (iev->type == kFLE2IEVTypeRangeV2 || iev->type == kFLE2IEVTypeText ? 1 : 0) + + (iev->type == kFLE2IEVTypeText ? 2 : 0) + iev->edge_count * kMetadataLen; } bool mc_FLE2IndexedEncryptedValueV2_serialize(const mc_FLE2IndexedEncryptedValueV2_t *iev, @@ -446,8 +639,8 @@ bool mc_FLE2IndexedEncryptedValueV2_serialize(const mc_FLE2IndexedEncryptedValue BSON_ASSERT_PARAM(iev); BSON_ASSERT_PARAM(buf); - if (iev->type != kFLE2IEVTypeRangeV2 && iev->type != kFLE2IEVTypeEqualityV2) { - CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_serialize must be called with type equality or range"); + if (iev->type != kFLE2IEVTypeRangeV2 && iev->type != kFLE2IEVTypeEqualityV2 && iev->type != kFLE2IEVTypeText) { + CLIENT_ERR("mc_FLE2IndexedEncryptedValueV2_serialize must be called with type equality, range, or text"); return false; } @@ -465,9 +658,14 @@ bool mc_FLE2IndexedEncryptedValueV2_serialize(const mc_FLE2IndexedEncryptedValue // Serialize bson_value_type CHECK_AND_RETURN(mc_writer_write_u8(&writer, iev->bson_value_type, status)); - // Serialize edge_count (only serialized for type range) - if (iev->type == kFLE2IEVTypeRangeV2) { + if (iev->type == kFLE2IEVTypeRangeV2 || iev->type == kFLE2IEVTypeText) { + // Serialize edge_count (only serialized for types range and text) CHECK_AND_RETURN(mc_writer_write_u8(&writer, iev->edge_count, status)); + if (iev->type == kFLE2IEVTypeText) { + // Serialize substr/suffix_tag_count (only serialized for text) + CHECK_AND_RETURN(mc_writer_write_u8(&writer, iev->substr_tag_count, status)); + CHECK_AND_RETURN(mc_writer_write_u8(&writer, iev->suffix_tag_count, status)); + } } // Serialize encrypted value @@ -488,7 +686,7 @@ bool mc_FLE2IndexedEncryptedValueV2_serialize(const mc_FLE2IndexedEncryptedValue return true; } -bool is_fle2_equality_indexed_supported_type(int bson_type) { +static bool is_fle2_equality_indexed_supported_type(int bson_type) { switch (bson_type) { case BSON_TYPE_BINARY: case BSON_TYPE_CODE: @@ -509,6 +707,24 @@ bool is_fle2_equality_indexed_supported_type(int bson_type) { } } +static bool is_fle2_range_indexed_supported_type(int bson_type) { + switch (bson_type) { + case BSON_TYPE_INT32: + case BSON_TYPE_INT64: + case BSON_TYPE_DATE_TIME: + case BSON_TYPE_DOUBLE: +#if MONGOCRYPT_HAVE_DECIMAL128_SUPPORT + case BSON_TYPE_DECIMAL128: +#endif + return true; + default: return false; + } +} + +static bool is_fle2_text_indexed_supported_type(int bson_type) { + return bson_type == BSON_TYPE_UTF8; +} + #define CHECK(condition, msg) \ do { \ if (!(condition)) { \ @@ -517,13 +733,44 @@ bool is_fle2_equality_indexed_supported_type(int bson_type) { } \ } while (0) -bool mc_FLE2IndexedEncryptedValueV2_validate(const mc_FLE2IndexedEncryptedValueV2_t *iev, mongocrypt_status_t *status) { - BSON_ASSERT_PARAM(iev); - CHECK(iev->type == kFLE2IEVTypeEqualityV2, "validate only supports type equality"); +static bool validate_for_equality(const mc_FLE2IndexedEncryptedValueV2_t *iev, mongocrypt_status_t *status) { CHECK(iev->fle_blob_subtype == MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2, "fle_blob_subtype does not match type"); CHECK(is_fle2_equality_indexed_supported_type(iev->bson_value_type), "bson_value_type is invalid"); CHECK(iev->edge_count == 1, "edge_count must be 1 for equality"); + return true; +} + +static bool validate_for_range(const mc_FLE2IndexedEncryptedValueV2_t *iev, mongocrypt_status_t *status) { + CHECK(iev->fle_blob_subtype == MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2, "fle_blob_subtype does not match type"); + CHECK(is_fle2_range_indexed_supported_type(iev->bson_value_type), "bson_value_type is invalid"); + return true; +} + +static bool validate_for_text(const mc_FLE2IndexedEncryptedValueV2_t *iev, mongocrypt_status_t *status) { + CHECK(iev->fle_blob_subtype == MC_SUBTYPE_FLE2IndexedTextEncryptedValue, "fle_blob_subtype does not match type"); + CHECK(is_fle2_text_indexed_supported_type(iev->bson_value_type), "bson_value_type is invalid"); + CHECK((uint16_t)iev->edge_count >= (uint16_t)iev->substr_tag_count + (uint16_t)iev->suffix_tag_count + 1, + "edge_count is smaller than substr_tag_count + suffix_tag_count + 1"); + return true; +} + +bool mc_FLE2IndexedEncryptedValueV2_validate(const mc_FLE2IndexedEncryptedValueV2_t *iev, mongocrypt_status_t *status) { + BSON_ASSERT_PARAM(iev); + CHECK(iev->type == kFLE2IEVTypeEqualityV2 || iev->type == kFLE2IEVTypeRangeV2 || iev->type == kFLE2IEVTypeText, + "type was init or unknown"); + + if (iev->type == kFLE2IEVTypeEqualityV2) { + validate_for_equality(iev, status); + } else if (iev->type == kFLE2IEVTypeRangeV2) { + validate_for_range(iev, status); + } else { + validate_for_text(iev, status); + } + + if (!mongocrypt_status_ok(status)) { + return false; + } CHECK(iev->ServerEncryptedValue.len >= kMinServerEncryptedValueLen, "SEV.len is less than minimum"); CHECK(iev->S_KeyId.len == UUID_LEN, "S_KeyId is not the correct length for a UUID"); @@ -550,5 +797,11 @@ bool mc_FLE2IndexedEncryptedValueV2_validate(const mc_FLE2IndexedEncryptedValueV } CHECK(iev->ClientValue.len == ClientValueLen, "ClientValue.len was unexpected"); } - return mc_FLE2TagAndEncryptedMetadataBlock_validate(iev->metadata, status); + CHECK(iev->edge_count > 0, "edge_count must be at least 1"); + for (uint8_t i = 0; i < iev->edge_count; i++) { + if (!mc_FLE2TagAndEncryptedMetadataBlock_validate(&iev->metadata[i], status)) { + return false; + } + } + return true; } diff --git a/src/mongocrypt-ctx-decrypt.c b/src/mongocrypt-ctx-decrypt.c index cd696b3e9..3b6d7bfd2 100644 --- a/src/mongocrypt-ctx-decrypt.c +++ b/src/mongocrypt-ctx-decrypt.c @@ -381,7 +381,8 @@ static bool _replace_ciphertext_with_plaintext(void *ctx, BSON_ASSERT(in->data); switch (in->data[0]) { - // FLE2v2 + // FLE2v2 / Text Search + case MC_SUBTYPE_FLE2IndexedTextEncryptedValue: case MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2: case MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2: return _replace_FLE2IndexedEncryptedValueV2_with_plaintext(ctx, in, out, status); @@ -501,7 +502,8 @@ static bool _collect_K_KeyID_from_FLE2IndexedEncryptedValueV2(void *ctx, bool ret = false; BSON_ASSERT((in->data[0] == MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2) - || (in->data[0] == MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2)); + || (in->data[0] == MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2) + || (in->data[0] == MC_SUBTYPE_FLE2IndexedTextEncryptedValue)); mc_FLE2IndexedEncryptedValueV2_t *iev = mc_FLE2IndexedEncryptedValueV2_new(); _mongocrypt_buffer_t S_Key = {0}; @@ -573,7 +575,8 @@ static bool _collect_K_KeyIDs(void *ctx, _mongocrypt_buffer_t *in, mongocrypt_st BSON_ASSERT(in->data); switch (in->data[0]) { - // FLE2v2 + // FLE2v2 / Text Search + case MC_SUBTYPE_FLE2IndexedTextEncryptedValue: case MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2: case MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2: return _collect_K_KeyID_from_FLE2IndexedEncryptedValueV2(ctx, in, status); @@ -727,7 +730,8 @@ static bool _collect_key_from_ciphertext(void *ctx, _mongocrypt_buffer_t *in, mo BSON_ASSERT(in->data); switch (in->data[0]) { - // FLE2v2 + // FLE2v2 / Text Search + case MC_SUBTYPE_FLE2IndexedTextEncryptedValue: case MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2: case MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2: return _collect_S_KeyID_from_FLE2IndexedEncryptedValueV2(ctx, in, status); diff --git a/src/mongocrypt-traverse-util.c b/src/mongocrypt-traverse-util.c index c6e26775e..9a86ccf0a 100644 --- a/src/mongocrypt-traverse-util.c +++ b/src/mongocrypt-traverse-util.c @@ -45,7 +45,7 @@ static bool _check_first_byte(uint8_t byte, traversal_match_t match) { || byte == MC_SUBTYPE_FLE2InsertUpdatePayload || byte == MC_SUBTYPE_FLE2IndexedRangeEncryptedValue || byte == MC_SUBTYPE_FLE2InsertUpdatePayloadV2 || byte == MC_SUBTYPE_FLE2UnindexedEncryptedValueV2 || byte == MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2 - || byte == MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2; + || byte == MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2 || byte == MC_SUBTYPE_FLE2IndexedTextEncryptedValue; case TRAVERSE_MATCH_SUBTYPE6: return true; default: break; } diff --git a/test/data/iev-v2/FLECrudTest-insertOneText.json b/test/data/iev-v2/FLECrudTest-insertOneText.json new file mode 100644 index 000000000..79da0ba96 --- /dev/null +++ b/test/data/iev-v2/FLECrudTest-insertOneText.json @@ -0,0 +1,20 @@ +{ + "type": "text", + "payload": "111234567812349876123412345678901202050201d130af3cf5dd250c1a3bbc428b7f3b84ecf5774c9d0427d2b1de3f5c4de66e363fccce09a7ad904c88a447a284ae343e867b4cdbdc3cb728632065b7676d588b40925b49f887222c20f10c7f93198adff9c168ead38b6773fb2c198d5195c63334bc1b32ce6404f3e39c97fdb86b077d70c86896fa329faf3029a14012fe4b743d2267d98b888e9006c683039c1a1347baf18e50f02704b146bfc018cf41a55752815f53d5795c66ad0ed2f2bde7f6471da0b7effca4fe213695f2a16ab9be136678ff929f4f077b0096782fd4ac3c296bbf2aff2520aca5585cfa8c8d14d481c5701e8b2c684b255911d7c3e1a0f1a93d75b8994f6f7621091bd062d86c439ac0d0836b30e0c9d24ed80d233a09ef441f3e1f08b525c9d2682b23e432bc6f585de4c0ab39c87e4cb0b3a63e5ea878c10e705acc4c59126f862536ccd79683d318b9b583f23da5713244e4cff9ec8fba5a12adbbb7513ff0304586a4f4cfbe3014a4219179a78618cf889080262b4b96786fb73d4b2ab2c39ab5a0934d9b3106514d94c07296be6cbd9af53b45ee4507c923cdc230779a75c33772df7b884aa898d39eb1be108e0f285dcd2aa2f0e29dd87a04876db6ef9b88e4338e7a56f2df5267ce74d2ade2078fc5c4c2d2d341e9bfa921912e2768ffe7f81fd02a3fc26a774adf3322ba11a79d78bf82779ca2884563b4b2d7db084fa129ce9b3b62882101154aeacca3f2615873cf24a72cf39a791a43422c79c6509712dccdaa1632322a2dff00a448ceb0b98918cd1274fee83a7c33c1a40ff17693f78d785b334bdc", + "S_KeyId": "12345678123498761234123456789012", + "S_Key": "f502e66502ff5d4559452ce928a0f08557a9853c4dfeacca77cff482434f0ca1251fbd60d200bc35f24309521b45ad781b2d3c4df788cacef3c0e7beca8170b6cfc514ecfcf27e217ed697ae65c08272886324def514b14369c7c60414e80f22", + "K_KeyId": "ABCDEFAB123498761234123456789012", + "K_Key": "cbebdf05fe16099fef502a6d045c1cbb77d29d2fe19f51aec5079a81008305d8868358845d2e3ab38e4fa9cbffcd651a0fc07201d7c9ed9ca3279bfa7cd673ec37b362a0aaa92f95062405a999afd49e4b1f7f818f766c49715407011ac37fa9", + "bson_value_type": 2, + "bson_value": "0700000073656372657400", + "substr_tag_count": 2, + "suffix_tag_count": 1, + "metadata": + [ + "34bc1b32ce6404f3e39c97fdb86b077d70c86896fa329faf3029a14012fe4b743d2267d98b888e9006c683039c1a1347baf18e50f02704b146bfc018cf41a55752815f53d5795c66ad0ed2f2bde7f6471da0b7effca4fe213695f2a16ab9be13", + "6678ff929f4f077b0096782fd4ac3c296bbf2aff2520aca5585cfa8c8d14d481c5701e8b2c684b255911d7c3e1a0f1a93d75b8994f6f7621091bd062d86c439ac0d0836b30e0c9d24ed80d233a09ef441f3e1f08b525c9d2682b23e432bc6f58", + "5de4c0ab39c87e4cb0b3a63e5ea878c10e705acc4c59126f862536ccd79683d318b9b583f23da5713244e4cff9ec8fba5a12adbbb7513ff0304586a4f4cfbe3014a4219179a78618cf889080262b4b96786fb73d4b2ab2c39ab5a0934d9b3106", + "514d94c07296be6cbd9af53b45ee4507c923cdc230779a75c33772df7b884aa898d39eb1be108e0f285dcd2aa2f0e29dd87a04876db6ef9b88e4338e7a56f2df5267ce74d2ade2078fc5c4c2d2d341e9bfa921912e2768ffe7f81fd02a3fc26a", + "774adf3322ba11a79d78bf82779ca2884563b4b2d7db084fa129ce9b3b62882101154aeacca3f2615873cf24a72cf39a791a43422c79c6509712dccdaa1632322a2dff00a448ceb0b98918cd1274fee83a7c33c1a40ff17693f78d785b334bdc" + ] +} \ No newline at end of file diff --git a/test/test-mc-fle2-payload-iev-v2.c b/test/test-mc-fle2-payload-iev-v2.c index 50c09e265..57c9bf522 100644 --- a/test/test-mc-fle2-payload-iev-v2.c +++ b/test/test-mc-fle2-payload-iev-v2.c @@ -17,12 +17,10 @@ #include "mc-fle-blob-subtype-private.h" #include "mc-fle2-payload-iev-private-v2.h" #include "test-mongocrypt-assert-match-bson.h" +#include "test-mongocrypt-assert.h" #include "test-mongocrypt.h" -typedef enum { - kTypeEquality, - kTypeRange, -} _mc_fle2_iev_type; +typedef enum { kTypeEquality, kTypeRange, kTypeText } _mc_fle2_iev_type; typedef struct { _mc_fle2_iev_type type; @@ -34,10 +32,13 @@ typedef struct { uint8_t bson_value_type; _mongocrypt_buffer_t bson_value; uint8_t edge_count; + uint8_t substr_tag_count; + uint8_t suffix_tag_count; mc_FLE2TagAndEncryptedMetadataBlock_t *metadata; } _mc_fle2_iev_v2_test; #define kMetadataLen 96U +#define kMetadataFieldLen 32U #define kMinServerEncryptedValueLen 17U // IV(16) + EncryptCTR(1byte) static void _mc_fle2_iev_v2_test_destroy(_mc_fle2_iev_v2_test *test) { @@ -54,6 +55,115 @@ static void _mc_fle2_iev_v2_test_destroy(_mc_fle2_iev_v2_test *test) { bson_free(test->metadata); } +static _mongocrypt_buffer_t copy_buf(_mongocrypt_buffer_t *src) { + _mongocrypt_buffer_t dst; + _mongocrypt_buffer_init(&dst); + _mongocrypt_buffer_copy_to(src, &dst); + return dst; +} + +// Fill out the fields manually with no checks, then serialize. +static void +_mc_fle2_iev_v2_test_serialize_payload(mongocrypt_t *crypt, _mc_fle2_iev_v2_test *test, _mongocrypt_buffer_t *payload) { + mc_FLE2IndexedEncryptedValueV2_t *iev = mc_FLE2IndexedEncryptedValueV2_new(); + iev->type = test->type == kTypeEquality ? kFLE2IEVTypeEqualityV2 + : test->type == kTypeRange ? kFLE2IEVTypeRangeV2 + : kFLE2IEVTypeText; + iev->fle_blob_subtype = test->type == kTypeEquality ? MC_SUBTYPE_FLE2IndexedEqualityEncryptedValueV2 + : test->type == kTypeRange ? MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2 + : MC_SUBTYPE_FLE2IndexedTextEncryptedValue; + iev->bson_value_type = test->bson_value_type; + iev->edge_count = test->edge_count; + iev->substr_tag_count = test->substr_tag_count; + iev->suffix_tag_count = test->suffix_tag_count; + iev->S_KeyId = copy_buf(&test->S_KeyId); + _mongocrypt_buffer_init(&iev->ServerEncryptedValue); + + // Encrypt the client value (bson_value) into the SEV + // First, encrypt with K_Key -> CEV + mongocrypt_status_t *status = mongocrypt_status_new(); + const _mongocrypt_value_encryption_algorithm_t *fle2v2aead = _mcFLE2v2AEADAlgorithm(); + const uint32_t ClientEncryptedValueLen = fle2v2aead->get_ciphertext_len(test->bson_value.len, status); + ASSERT_OK_STATUS(ClientEncryptedValueLen, status); + + _mongocrypt_buffer_t clientEncryptedValue; + _mongocrypt_buffer_init_size(&clientEncryptedValue, ClientEncryptedValueLen); + + _mongocrypt_buffer_t iv; + _mongocrypt_buffer_init_size(&iv, MONGOCRYPT_IV_LEN); + ASSERT_OK_STATUS(_mongocrypt_random(crypt->crypto, &iv, MONGOCRYPT_IV_LEN, status), status); + + uint32_t bytes_written = 0; + ASSERT_OK_STATUS(fle2v2aead->do_encrypt(crypt->crypto, + &iv, + &test->K_KeyId, + &test->K_Key, + &test->bson_value, + &clientEncryptedValue, + &bytes_written, + status), + status); + ASSERT(bytes_written == ClientEncryptedValueLen); + + // Then, add K_Key_Id + CEV -> DSEV + _mongocrypt_buffer_t decryptedServerEncryptedValue; + _mongocrypt_buffer_init_size(&decryptedServerEncryptedValue, clientEncryptedValue.len + UUID_LEN); + memcpy(decryptedServerEncryptedValue.data, test->K_KeyId.data, UUID_LEN); + memcpy(decryptedServerEncryptedValue.data + UUID_LEN, clientEncryptedValue.data, clientEncryptedValue.len); + // Finally, encrypt DSEV -> SEV + /* Get the TokenKey from the last 32 bytes of S_Key */ + _mongocrypt_buffer_t TokenKey; + ASSERT(_mongocrypt_buffer_from_subrange(&TokenKey, + &test->S_Key, + test->S_Key.len - MONGOCRYPT_TOKEN_KEY_LEN, + MONGOCRYPT_TOKEN_KEY_LEN)); + + /* Use TokenKey to create ServerDataEncryptionLevel1Token and encrypt + * DSEV into ServerEncryptedValue */ + mc_ServerDataEncryptionLevel1Token_t *token = + mc_ServerDataEncryptionLevel1Token_new(crypt->crypto, &TokenKey, status); + ASSERT_OK_STATUS(token, status); + + const _mongocrypt_value_encryption_algorithm_t *fle2alg = _mcFLE2Algorithm(); + const uint32_t ServerEncryptedValueLen = fle2alg->get_ciphertext_len(decryptedServerEncryptedValue.len, status); + ASSERT_OK_STATUS(ServerEncryptedValueLen, status); + + _mongocrypt_buffer_resize(&iev->ServerEncryptedValue, ServerEncryptedValueLen); + bytes_written = 0; + ASSERT_OK_STATUS(fle2alg->do_encrypt(crypt->crypto, + &iv, + NULL /* aad */, + mc_ServerDataEncryptionLevel1Token_get(token), + &decryptedServerEncryptedValue, + &iev->ServerEncryptedValue, + &bytes_written, + status), + status); + ASSERT(bytes_written == ServerEncryptedValueLen); + + // Finally, add metadata, and we have a complete IEV. + iev->metadata = bson_malloc0(test->edge_count * sizeof(mc_FLE2TagAndEncryptedMetadataBlock_t)); + for (uint8_t i = 0; i < test->edge_count; i++) { + _mongocrypt_buffer_t tmp_buf; + _mongocrypt_buffer_init_size(&tmp_buf, kMetadataLen); + memcpy(tmp_buf.data, test->metadata[i].encryptedCount.data, kMetadataFieldLen); + memcpy(tmp_buf.data + kMetadataFieldLen, test->metadata[i].tag.data, kMetadataFieldLen); + memcpy(tmp_buf.data + kMetadataFieldLen * 2, test->metadata[i].encryptedZeros.data, kMetadataFieldLen); + + ASSERT_OK_STATUS(mc_FLE2TagAndEncryptedMetadataBlock_parse(&iev->metadata[i], &tmp_buf, status), status); + _mongocrypt_buffer_cleanup(&tmp_buf); + } + + // Now serialize our IEV to the payload. + ASSERT_OK_STATUS(mc_FLE2IndexedEncryptedValueV2_serialize(iev, payload, status), status); + mongocrypt_status_destroy(status); + _mongocrypt_buffer_cleanup(&clientEncryptedValue); + _mongocrypt_buffer_cleanup(&iv); + _mongocrypt_buffer_cleanup(&decryptedServerEncryptedValue); + mc_ServerDataEncryptionLevel1Token_destroy(token); + mc_FLE2IndexedEncryptedValueV2_destroy(iev); +} + static bool _mc_fle2_iev_v2_test_parse(_mc_fle2_iev_v2_test *test, bson_iter_t *iter) { bool hasType = false; @@ -82,6 +192,18 @@ static bool _mc_fle2_iev_v2_test_parse(_mc_fle2_iev_v2_test *test, bson_iter_t * int64_t value = bson_iter_as_int64(iter); ASSERT_OR_PRINT_MSG((value > 0) && (value < 128), "Field 'bson_value_type' must be 1..127"); test->bson_value_type = (uint8_t)value; + } else if (!strcmp(field, "substr_tag_count")) { + ASSERT_OR_PRINT_MSG(!test->substr_tag_count, "Duplicate field 'substr_tag_count'"); + ASSERT(BSON_ITER_HOLDS_INT32(iter) || BSON_ITER_HOLDS_INT64(iter)); + int64_t value = bson_iter_as_int64(iter); + ASSERT_OR_PRINT_MSG((value >= 0) && (value < 256), "Field 'substr_tag_count' must be 0..255"); + test->substr_tag_count = (uint8_t)value; + } else if (!strcmp(field, "suffix_tag_count")) { + ASSERT_OR_PRINT_MSG(!test->suffix_tag_count, "Duplicate field 'suffix_tag_count'"); + ASSERT(BSON_ITER_HOLDS_INT32(iter) || BSON_ITER_HOLDS_INT64(iter)); + int64_t value = bson_iter_as_int64(iter); + ASSERT_OR_PRINT_MSG((value >= 0) && (value < 256), "Field 'suffix_tag_count' must be 0..255"); + test->suffix_tag_count = (uint8_t)value; } else if (!strcmp(field, "type")) { ASSERT_OR_PRINT_MSG(!hasType, "Duplicate field 'type'"); ASSERT(BSON_ITER_HOLDS_UTF8(iter)); @@ -90,6 +212,8 @@ static bool _mc_fle2_iev_v2_test_parse(_mc_fle2_iev_v2_test *test, bson_iter_t * test->type = kTypeEquality; } else if (!strcmp(value, "range")) { test->type = kTypeRange; + } else if (!strcmp(value, "text")) { + test->type = kTypeText; } else { TEST_ERROR("Unknown type '%s'", value); } @@ -167,12 +291,16 @@ static bool _mc_fle2_iev_v2_test_parse(_mc_fle2_iev_v2_test *test, bson_iter_t * return true; } -static void _mc_fle2_iev_v2_test_run(_mongocrypt_tester_t *tester, _mc_fle2_iev_v2_test *test) { +static void _mc_fle2_iev_v2_test_run(mongocrypt_t *crypt, _mongocrypt_tester_t *tester, _mc_fle2_iev_v2_test *test) { mongocrypt_status_t *status = mongocrypt_status_new(); - mongocrypt_t *crypt = _mongocrypt_tester_mongocrypt(TESTER_MONGOCRYPT_DEFAULT); mc_FLE2IndexedEncryptedValueV2_t *iev = mc_FLE2IndexedEncryptedValueV2_new(); + if (!test->payload.data) { + // This is a self-test; i.e. we serialize the payload from the given fields, parse, and assert sameness. + _mc_fle2_iev_v2_test_serialize_payload(crypt, test, &test->payload); + } + // Parse payload. ASSERT_OK_STATUS(mc_FLE2IndexedEncryptedValueV2_parse(iev, &test->payload, status), status); @@ -210,17 +338,45 @@ static void _mc_fle2_iev_v2_test_run(_mongocrypt_tester_t *tester, _mc_fle2_iev_ ASSERT_CMPBUF(*bson_value, test->bson_value); uint8_t edge_count = 1; - if (test->type == kTypeRange) { + if (test->type == kTypeRange || test->type == kTypeText) { // Validate edge count edge_count = mc_FLE2IndexedEncryptedValueV2_get_edge_count(iev, status); ASSERT_OK_STATUS(edge_count, status); ASSERT_CMPINT(edge_count, ==, test->edge_count); } + uint8_t substr_tag_count = 0, suffix_tag_count = 0; + if (test->type == kTypeText) { + // Validate substr/suffix/prefix tag counts + ASSERT_OK_STATUS(mc_FLE2IndexedEncryptedValueV2_get_substr_tag_count(iev, &substr_tag_count, status), status); + ASSERT_CMPINT(substr_tag_count, ==, test->substr_tag_count); + ASSERT_OK_STATUS(mc_FLE2IndexedEncryptedValueV2_get_suffix_tag_count(iev, &suffix_tag_count, status), status); + ASSERT_CMPINT(suffix_tag_count, ==, test->suffix_tag_count); + uint8_t prefix_tag_count; + ASSERT_OK_STATUS(mc_FLE2IndexedEncryptedValueV2_get_prefix_tag_count(iev, &prefix_tag_count, status), status); + ASSERT_CMPINT(prefix_tag_count, ==, test->edge_count - test->substr_tag_count - test->suffix_tag_count - 1); + } + // Validate edges/metadata mc_FLE2TagAndEncryptedMetadataBlock_t metadata; for (int i = 0; i < edge_count; ++i) { - if (test->type == kTypeRange) { + if (test->type == kTypeText) { + if (i == 0) { + ASSERT(mc_FLE2IndexedEncryptedValueV2_get_exact_metadata(iev, &metadata, status)); + } else if (i < substr_tag_count + 1) { + ASSERT(mc_FLE2IndexedEncryptedValueV2_get_substr_metadata(iev, &metadata, i - 1, status)); + } else if (i < suffix_tag_count + substr_tag_count + 1) { + ASSERT(mc_FLE2IndexedEncryptedValueV2_get_suffix_metadata(iev, + &metadata, + i - substr_tag_count - 1, + status)); + } else { + ASSERT(mc_FLE2IndexedEncryptedValueV2_get_prefix_metadata(iev, + &metadata, + i - substr_tag_count - suffix_tag_count - 1, + status)); + } + } else if (test->type == kTypeRange) { ASSERT(mc_FLE2IndexedEncryptedValueV2_get_edge(iev, &metadata, i, status)); } else { ASSERT(mc_FLE2IndexedEncryptedValueV2_get_metadata(iev, &metadata, status)); @@ -232,7 +388,6 @@ static void _mc_fle2_iev_v2_test_run(_mongocrypt_tester_t *tester, _mc_fle2_iev_ // All done! mc_FLE2IndexedEncryptedValueV2_destroy(iev); - mongocrypt_destroy(crypt); mongocrypt_status_destroy(status); } @@ -304,17 +459,11 @@ static void _mc_fle2_iev_v2_validate(_mongocrypt_tester_t *tester, _mc_fle2_iev_ ASSERT_OK_STATUS(mc_FLE2IndexedEncryptedValueV2_parse(iev, &test->payload, status), status); // Assert valid IEV. - if (test->type == kTypeRange) { - ASSERT(!mc_FLE2IndexedEncryptedValueV2_validate(iev, status)); - // Validate function only supports equality type. - goto cleanup; - } else { - ASSERT_OK_STATUS(mc_FLE2IndexedEncryptedValueV2_validate(iev, status), status); - } + ASSERT_OK_STATUS(mc_FLE2IndexedEncryptedValueV2_validate(iev, status), status); // Modify each value on the IEV to be invalid and assert failure, then change back to valid value. mc_fle_blob_subtype_t temp_fle_blob_subtype = iev->fle_blob_subtype; - iev->fle_blob_subtype = MC_SUBTYPE_FLE2IndexedRangeEncryptedValueV2; + iev->fle_blob_subtype = MC_SUBTYPE_FLE2UnindexedEncryptedValueV2; ASSERT(!mc_FLE2IndexedEncryptedValueV2_validate(iev, status)); iev->fle_blob_subtype = temp_fle_blob_subtype; @@ -323,9 +472,26 @@ static void _mc_fle2_iev_v2_validate(_mongocrypt_tester_t *tester, _mc_fle2_iev_ ASSERT(!mc_FLE2IndexedEncryptedValueV2_validate(iev, status)); iev->bson_value_type = temp_bson_value_type; - iev->edge_count = 5; + uint8_t temp_edge_count = iev->edge_count; + iev->edge_count = 0; ASSERT(!mc_FLE2IndexedEncryptedValueV2_validate(iev, status)); - iev->edge_count = 1; + iev->edge_count = temp_edge_count; + if (test->type == kTypeEquality) { + iev->edge_count = 5; + ASSERT(!mc_FLE2IndexedEncryptedValueV2_validate(iev, status)); + iev->edge_count = temp_edge_count; + } else if (test->type == kTypeText) { + uint8_t temp_substr_count = iev->substr_tag_count; + uint8_t temp_suffix_count = iev->suffix_tag_count; + + iev->substr_tag_count = iev->edge_count; + ASSERT(!mc_FLE2IndexedEncryptedValueV2_validate(iev, status)); + iev->substr_tag_count = temp_substr_count; + + iev->suffix_tag_count = iev->edge_count; + ASSERT(!mc_FLE2IndexedEncryptedValueV2_validate(iev, status)); + iev->suffix_tag_count = temp_suffix_count; + } _mongocrypt_buffer_resize(&iev->ServerEncryptedValue, kMinServerEncryptedValueLen - 1); ASSERT(!mc_FLE2IndexedEncryptedValueV2_validate(iev, status)); @@ -373,12 +539,11 @@ static void _mc_fle2_iev_v2_validate(_mongocrypt_tester_t *tester, _mc_fle2_iev_ _mongocrypt_status_reset(status); ASSERT_OK_STATUS(mc_FLE2IndexedEncryptedValueV2_validate(iev, status), status); -cleanup: mc_FLE2IndexedEncryptedValueV2_destroy(iev); mongocrypt_status_destroy(status); } -static void test_fle2_iev_v2_test(_mongocrypt_tester_t *tester, const char *path) { +static void test_fle2_iev_v2_test(mongocrypt_t *crypt, _mongocrypt_tester_t *tester, const char *path) { TEST_PRINTF("Loading test from %s...\n", path); mongocrypt_binary_t *test_bin = TEST_FILE(path); @@ -397,7 +562,12 @@ static void test_fle2_iev_v2_test(_mongocrypt_tester_t *tester, const char *path bson_iter_t iter; ASSERT(bson_iter_init(&iter, &test_bson)); ASSERT(_mc_fle2_iev_v2_test_parse(&test, &iter)); - _mc_fle2_iev_v2_test_run(tester, &test); + // Run once with given payload, then run with empty payload (self-test). + _mc_fle2_iev_v2_test_run(crypt, tester, &test); + _mongocrypt_buffer_cleanup(&test.payload); + test.payload.data = NULL; + _mc_fle2_iev_v2_test_run(crypt, tester, &test); + _mc_fle2_iev_v2_test_explicit_ctx(tester, &test); _mc_fle2_iev_v2_validate(tester, &test); _mc_fle2_iev_v2_test_destroy(&test); @@ -408,11 +578,15 @@ static void test_fle2_iev_v2(_mongocrypt_tester_t *tester) { TEST_PRINTF("Common Crypto with no CTR support detected. Skipping."); return; } - - // Producted by Server test: (FLECrudTest, insertOneV2) - test_fle2_iev_v2_test(tester, "test/data/iev-v2/FLECrudTest-insertOneV2.json"); - // Producted by Server test: (FLECrudTest, insertOneRangeV2) - test_fle2_iev_v2_test(tester, "test/data/iev-v2/FLECrudTest-insertOneRangeV2.json"); + mongocrypt_t *crypt = _mongocrypt_tester_mongocrypt(TESTER_MONGOCRYPT_DEFAULT); + // Produced by Server test: (FLECrudTest, insertOneV2) + test_fle2_iev_v2_test(crypt, tester, "test/data/iev-v2/FLECrudTest-insertOneV2.json"); + // Produced by Server test: (FLECrudTest, insertOneRangeV2) + test_fle2_iev_v2_test(crypt, tester, "test/data/iev-v2/FLECrudTest-insertOneRangeV2.json"); + // Fields are modified from insertOneRangeV2.json, payload was produced by _mc_fle2_iev_v2_test_serialize_payload in + // this test + test_fle2_iev_v2_test(crypt, tester, "test/data/iev-v2/FLECrudTest-insertOneText.json"); + mongocrypt_destroy(crypt); } void _mongocrypt_tester_install_fle2_iev_v2_payloads(_mongocrypt_tester_t *tester) {